☰
规则合规的视觉空间规划:多模态大模型落地工程方法
2026/9/29 14:54:21 网站建设 项目流程

之前在做一个物体摆放的视觉应用时,反复卡在一个“不起眼但又很致命”的问题上:模型生成的摆放位置经常超出桌面边界,或者和已有物体严重重叠。试了很多提示词版本,结果时好时坏。后来把问题拆开才发现,真正的瓶颈并不是模型认不出场景,而是它输出的空间规划结果缺少一层“规则校验”兜底。网上关于多模态大模型做识别和问答的资料很多,但专门讲“规则合规的视觉空间规划”怎么落地实现的,几乎找不到一套系统方案。这篇文章就把这套工程方法完整拆解出来,包含核心概念、难点分析、可运行的参考代码和真实项目中常见的问题排查思路。适合正在做多模态应用、机器人视觉、智能家居或者 LLM Agent 的开发者阅读,零基础也能顺着步骤理解整体设计。

1. 背景与核心概念

1.1 什么是多模态大语言模型

多模态大语言模型(Multimodal Large Language Model,MLLM)是在传统文本大语言模型基础上,增加了图像、音频、视频等输入模态能力的模型。常见的形态是“文本 + 图像”输入,输出自然语言或结构化文本。

例如,输入一张办公室桌面照片,模型可以说出“桌面上有一台显示器、一个水杯、一个键盘”,也能回答“键盘在显示器的左边还是右边”这类空间关系问题。这种能力来自视觉编码器和语言模型的结合:视觉编码器把图像转换成特征向量,语言模型再基于这些特征做推理和回答。

从技术架构看,MLLM 并不是一个全新的模型类型,更像是“视觉理解模块 + 语言推理模块”的组合。当前主流做法包括:

  • 视觉编码器(如 CLIP、SigLIP 等)提取图像特征;
  • 视觉-语言连接模块(Projector / Adapter)把图像特征映射到语言模型的语义空间;
  • 语言模型(Decoder-Only 架构)完成推理、生成和输出。

这类模型在图像问答、内容描述、OCR 识别、视觉推理等任务上表现很强。但把它用于“给图像中的物体规划一个新位置”时,问题就开始出现了。

1.2 什么是视觉空间规划

视觉空间规划,指的是模型根据一张视觉输入(比如桌面照片、房间图、仓库俯视图),输出一个或一组空间布局决策。典型任务是:“桌面上已经放了水杯和笔记本,请把键盘放在哪里?”

这个任务与普通的视觉问答不同。问答只需要模型描述位置关系,而空间规划要求模型生成一个具体的、可执行的坐标或区域。这个输出往往是:

  • 二维坐标(x, y);
  • 目标检测框(x1, y1, x2, y2);
  • 区域名称(如“桌子左上角”);
  • 或者一段可执行代码(如“移动到坐标 400, 300 处摆放”)。

真正的难点在于,模型不能只“看着像合理”就可以,它还需要满足任务里给定的约束条件。例如“不能超出桌面”“不能与已有物体重叠”“易燃物不能靠近热源”。当这些约束变成硬性规则时,模型自由生成文本的方式就很难保证每次都对。

1.3 规则合规为什么是刚需

标题中的 Rule-Compliant(规则合规)强调一个关键点:模型的输出必须可以被验证、被约束、被修正,而不是“大致正确”。

在实际工程项目里,规则合规不是锦上添花,而是安全底线。一个机器人把杯子放到桌子边缘外,会直接导致物品掉落;一个仓储调度系统把重物规划到货架承重不足的区域,可能引发安全事故;一个室内导航系统把路径规划穿过墙,则根本无法执行。

更现实的问题是:多模态大语言模型是概率生成模型,同样的输入多次生成结果可能不同。如果没有规则校验层,模型的输出质量就很难控制。因此,工程上不能只依赖模型“自己理解规则”,必须把规则变成代码层面的检查逻辑,对模型输出做强制校验。这也是本文方案的核心:提示词让模型尽量输出合理结果,校验器保证输出真正合规,重生成机制在违规时自动修正。

2. 视觉空间规划的主要难点

2.1 空间关系的理解偏差

虽然 MLLM 能回答“杯子在笔记本左边”这种简单问题,但它对精确空间坐标的感知并不稳定。原因在于,视觉编码器输出的是语义特征,而不是像素级的空间几何信息。

比如模型可能知道“杯子在桌子左上区域”,但当要求它输出具体坐标(156, 89)时,它需要把语义位置映射到图像坐标系。这一步映射并不精确,常见表现是:

  • 坐标偏移几十像素;
  • 生成的坐标落在物体中心点之外;
  • 把“左边”理解为“左上”;
  • 忽略图像中桌面以外的区域。

这种误差在单纯问答场景可以接受,但在需要实际执行的空间规划中会直接导致失败。因此,工程方案不能假设模型坐标绝对准确,而必须叠加校验和修正步骤。

2.2 规则与生成之间的矛盾

语言模型的目标函数是“生成概率最高的文本”,而不是“生成满足规则约束的文本”。它内部并没有一个可微的规则检查器,所以规则只能通过提示词“软性”传入模型。

这带来一个矛盾:规则越精确,模型越容易在长文本生成过程中“忘记”或“选择性忽略”部分规则。例如你告诉它“物体中心必须距桌边至少 20 像素,任意两物体间距不小于 60 像素”,模型在生成 JSON 时可能记住了第一条,却违反了第二条。

换句话说,提示词约束是必要的,但远不够充分。想让输出规则合规,必须把“规则判断”从模型内部移到模型外部,用代码实现确定性的判断。

2.3 现有方案的短板

当前常见处理方式可以分成三档:

第一档:只在提示词里写规则。优点是无成本,缺点是模型不稳定,规则越多越容易出错。

第二档:提示词加输出格式约束。例如要求模型必须输出 JSON,可以解决解析问题,但不能解决空间规则问题。

第三档:生成后做校验,但校验失败只报错,不让模型重新生成。这种方式在离线分析中可用,在真实交互流程中效率低。

理想的方案是把三者结合:用提示词引导模型靠近正确结果,用结构化输出保证可解析性,用确定性校验器拦截一切违规输出,在校验失败后把错误信息反馈给模型继续修正。下面几个小节正是按这个思路展开的。

3. 核心技术思路:如何让规划输出真正“守规矩”

3.1 规则建模:先机器可检查,再语言可描述

很多项目会把规则写成自然语言:“请把东西放在桌面上,不要挡住其他物品。”这种描述对模型友好,但对程序无法执行。工程上需要把规则拆解成可计算的条件。

建议把每一条规则抽象成四个要素:

  • 规则名称;
  • 规则描述(用于提示词);
  • 判断函数(用于代码校验);
  • 违反时的反馈文本(用于重新生成)。

以“桌面边界”为例,可以这样建模:

  • 规则名称:inside_boundary
  • 描述:物体中心必须位于桌面区域内,且距离桌边至少safety_margin像素。
  • 判断:safety_margin <= center_x <= board_width - safety_margin且safety_margin <= center_y <= board_height - safety_margin
  • 反馈文本:物体 new_1 中心点超出边界或距桌边过近

同理,“物体间距”规则可以建模为任意两个物体中心点的欧氏距离大于等于min_distance。

在实际项目中,规则往往不止两三条。常见的空间规划规则还包括:

规则类型示例
边界约束物体不能超出可放置区域
间距约束物体之间保持最小安全距离
重叠约束物体边界框重叠面积比例不能超过阈值
类别冲突热源与易燃物不能相邻
优先级约束重物优先放置于承重区
可达性约束机器人末端必须能触及目标点

规则建模是后续所有工作的基础。规则定义得越清晰,提示词越好写,校验器也越好实现。

3.2 提示词工程:把规则写进上下文

规则建模之后,需要把规则翻译成模型能理解的提示词。这里有几个关键经验。

第一,规则要放在 System Prompt 中,而不是放在 User Prompt 中。System Prompt 的作用是定义模型角色和长期约束,模型在生成时会优先参考这部分内容。

第二,规则之外需要给出明确的输出格式。最好把 JSON Schema 直接写在提示词里,并给出一个空模板。这样能显著提升模型输出的可解析性。

第三,提示词中要加入“违反规则时如何输出”的说明。例如:“如果规则无法满足,将placement_valid设为 false,并说明原因。”这一步非常重要,否则模型在无法满足约束时会强行生成一个不合规的坐标,而不是主动承认失败。

第四,企业级项目中应该对提示词做版本管理。因为提示词的小改动也可能影响模型输出,建议把提示词模板保存为单独文件,并记录版本和变更原因。

3.3 结构化输出约束

结构化输出是让模型输出可被程序安全处理的必要条件。空间规划的目标输出通常是一个 JSON 对象,例如:

{ "reasoning": "键盘放在显示器前方空位,距离水杯约 120 像素,满足间距要求", "placements": [ { "object_id": "new_1", "category": "keyboard", "center": [400, 300] } ] }

要让模型稳定输出这种结构,提示词中必须给出完整示例。如果模型偶尔输出 Markdown 代码块或额外解释文本,程序端还需要做容错解析。

有些模型服务商提供了 JSON Mode 或 Function Calling 能力,可以进一步约束输出。但要注意,不同平台的实现差异很大,依赖这些能力之前应先确认目标平台的接口文档。通用方案是采用“提示词声明 + 代码解析兜底”的组合方式。

3.4 校验-重生成闭环

这是整套方案的核心。流程可以简单概括为:

  1. 模型生成候选规划结果;
  2. 解析模型输出;
  3. 校验器逐条检查规则;
  4. 如果全部通过,返回结果;
  5. 如果有违规,把违规信息拼进提示词,让模型重新生成;
  6. 超过最大重试次数仍然违规,则返回失败并上报人工处理。

为什么要做重生成而不是直接拒绝?因为模型是概率生成,一次采样失败不代表模型理解不了规则。你把“错误原因”明确告诉它之后,第二次生成的成功率会明显提升。这一点在项目实践中非常有用。

重生成时常见的做法有两种。一种是保留原始 user_prompt,在末尾追加“你上一轮的输出违反了以下规则:……请重新输出”。另一种是启用多轮对话,把前一回合的输出作为历史消息传入。前者实现简单,后者上下文更完整。对于空间规划任务,通常前者足够。

3.5 约束解码与后处理

除了“生成后校验”,学术界和工业界也在探索更强的约束方式,即约束解码。约束解码不是在模型生成完成后再检查,而是在模型解码每个 token 时,实时过滤那些会导致规则违反的候选 token。

这种方式的优点是准确率高,缺点是实现复杂度高,而且很多规则无法在 token 级别判断。比如“两个物体中心距离大于 60 像素”这种全局约束,只有生成完整 JSON 之后才能计算。因此,约束解码更适合简单格式约束,而本文的“校验-重生成”闭环更适合复杂空间规则。

后处理是另一个值得提的思路。假如模型输出的坐标只是轻微越界,可以直接做投影修正:把坐标“拉回”合法区域。例如把center_x钳位到[safety_margin, board_width - safety_margin]区间内。这种方法可以在不调用模型的情况下快速修复边界类规则,但对间距、重叠这类涉及多个物体的规则无效。更好的做法是把它作为校验失败后的“轻量修正层”,修正完再进入规则校验。

4. 完整实战:桌面物品摆放规划

4.1 任务定义

为了让方案可运行、可验证,我们设计一个具体的演示任务:

输入:一张桌面照片(或桌面的检测结果),桌面宽 800 像素、高 600 像素,桌面上已有若干物体。

输出:为待放置的新物体(比如键盘)给出一个位置坐标,要求同时满足四条规则:

  • 桌面边界规则:物体中心点距桌边至少 20 像素;
  • 最小间距规则:任意两个物体中心点距离不小于 60 像素;
  • 重叠规则:任意两个物体边界框的重叠面积比例不超过 0.2;
  • JSON 输出规则:模型只能输出合法 JSON,不能包含额外解释。

我们将使用一个兼容 OpenAI SDK 的多模态模型客户端来调用模型,用一个独立的校验器对象完成规则检查,再用一个反馈规划器闭环控制重生成。

4.2 环境准备

本文代码以 Python 3.9 及以上版本为例,核心依赖如下:

pip install openai pillow

如果模型调用走的是自有或云厂商的 HTTP 服务,需要确认该服务兼容 OpenAI 的 Chat Completions 接口;如果不兼容,只需要替换llm_client.py中的请求实现,其余代码结构可以保持不变。

本文示例中的MODEL_NAME = "qwen-vl-plus"只是一个占位名称,你需要替换成实际使用的多模态模型名称,并通过API_BASE指向对应的服务地址。版本与接口参数请以你使用的模型服务文档为准,本文重点演示工程思路。

4.3 项目结构

spatial_planner/ ├── config.py # 全局配置 ├── rules.py # 规则定义 ├── scene_parser.py # 场景信息提取 ├── prompt_builder.py # 提示词构建 ├── llm_client.py # 模型客户端 ├── validator.py # 规则校验器 ├── planner.py # 反馈规划器 ├── visualize.py # 结果可视化 └── main.py # 主流程入口

每个文件职责单一,方便后续替换模型服务和增加规则。

4.4 规则定义

# 文件路径:spatial_planner/rules.py from typing import List, Dict def build_rule_text() -> str: return """ 1. inside_boundary:物体中心点必须位于桌面区域内,且与桌面边缘的距离 >= 20 像素。 2. min_distance:任意两个物体中心点之间的欧氏距离 >= 60 像素。 3. max_overlap:任意两个物体边界框的重叠面积比例不得超过 0.2。 4. valid_json:输出必须是合法 JSON,且只能包含规定字段。 """

这里用函数把规则文本集中管理,后续修改规则时只需要改一个文件。

4.5 场景信息提取

真实项目中,我们需要从照片里获取“已有物体的位置”。这可以通过目标检测模型(如 YOLO、DETR、GroundingDINO)或云厂商的视觉 API 完成。为了把核心逻辑讲清楚,这里的parse_scene使用模拟数据,返回结构保持与实际检测结果一致。

# 文件路径:spatial_planner/scene_parser.py from typing import List, Dict def parse_scene(image_path: str) -> List[Dict]: """ 从图像中提取已有物体的位置和类别。 实际项目可替换为检测模型或在线视觉 API。 返回格式: [ { "id": "obj_1", "category": "mug", "center": [150, 250], "bbox": [110, 210, 190, 290] } ] """ # 演示数据:真实项目中这里应调用检测模型 demo_objects = [ { "id": "obj_1", "category": "mug", "center": [150, 250], "bbox": [110, 210, 190, 290], }, { "id": "obj_2", "category": "notebook", "center": [500, 400], "bbox": [440, 340, 560, 460], }, ] return demo_objects

bbox采用[x1, y1, x2, y2]格式,即左上角和右下角坐标。center用于计算物体之间的距离,bbox用于计算重叠面积。如果你的检测模型输出的是其他格式,需要在解析阶段完成格式统一。

4.6 提示词构建

# 文件路径:spatial_planner/prompt_builder.py from typing import List, Dict def build_system_prompt(rule_text: str) -> str: return f"""你是一个视觉空间规划助手。用户会提供桌面场景和待放置物体。 你必须遵循以下规则生成放置方案: {rule_text} 输出要求: 1. 只输出 JSON,不要输出任何解释或 Markdown 标记。 2. JSON 格式: {{ "reasoning": "简述规划依据", "placements": [ {{"object_id": "new_1", "category": "keyboard", "center": [400, 300]}} ] }} 3. center 是图像像素坐标,误差控制在 20 像素以内。 4. 如果找不到任何满足规则的放置点,输出: {{ "reasoning": "无法找到合规位置", "placements": [] }} """ def build_user_prompt( existing_objects: List[Dict], new_objects: List[Dict], board_width: int, board_height: int, ) -> str: existing_text = "\n".join( f"{obj['id']}: {obj['category']}, center={obj['center']}, bbox={obj['bbox']}" for obj in existing_objects ) new_text = ", ".join(f"{obj['id']} ({obj['category']})" for obj in new_objects) return f"""桌面尺寸:{board_width} x {board_height} 已有物体: {existing_text} 请为以下新物体规划位置: {new_text} 请直接输出 JSON。"""

提示词中显式给出手边可计算的桌面尺寸和已有物体信息,避免模型“凭空猜测”。把“无法找到合规位置”作为一种合法输出,能在危机场景下防止模型硬编一个错误坐标。

4.7 模型客户端

# 文件路径:spatial_planner/llm_client.py import base64 from typing import Optional class BaseMLLMClient: """所有模型客户端的抽象基类,便于替换不同服务商。""" def chat(self, system_prompt: str, user_prompt: str, image_path: Optional[str] = None) -> str: raise NotImplementedError class OpenAICompatibleClient(BaseMLLMClient): """兼容 OpenAI Chat Completions 协议的多模态模型客户端。""" def __init__(self, api_key: str, model_name: str, base_url: Optional[str] = None): try: from openai import OpenAI except ImportError as exc: raise RuntimeError("请先安装 openai:pip install openai") from exc self._client = OpenAI(api_key=api_key, base_url=base_url) self._model_name = model_name def chat(self, system_prompt: str, user_prompt: str, image_path: Optional[str] = None) -> str: content = [{"type": "text", "text": user_prompt}] if image_path: if image_path.startswith("http://") or image_path.startswith("https://"): image_url = image_path else: with open(image_path, "rb") as f: encoded = base64.b64encode(f.read()).decode("utf-8") image_url = f"data:image/png;base64,{encoded}" content.append({"type": "image_url", "image_url": {"url": image_url}}) resp = self._client.chat.completions.create( model=self._model_name, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": content}, ], temperature=0.2, ) return resp.choices[0].message.content

在项目中建议始终使用BaseMLLMClient定义调用接口,之后无论换哪个服务商,只需要新增一个子类即可。例如,本地部署的多模态模型可以使用 vLLM 或 Ollama 的 OpenAI 兼容接口,直接复用这个类。

4.8 规则校验器

# 文件路径:spatial_planner/validator.py import math from typing import List, Dict class RuleValidator: def __init__( self, board_width: int, board_height: int, min_distance: int, safety_margin: int, max_overlap_ratio: float = 0.2, ): self.board_width = board_width self.board_height = board_height self.min_distance = min_distance self.safety_margin = safety_margin self.max_overlap_ratio = max_overlap_ratio def _is_inside_board(self, obj: Dict) -> bool: cx, cy = obj["center"] return ( self.safety_margin <= cx <= self.board_width - self.safety_margin and self.safety_margin <= cy <= self.board_height - self.safety_margin ) def _distance(self, a: Dict, b: Dict) -> float: ax, ay = a["center"] bx, by = b["center"] return math.hypot(ax - bx, ay - by) def _overlap_ratio(self, a: Dict, b: Dict) -> float: ax1, ay1, ax2, ay2 = a["bbox"] bx1, by1, bx2, by2 = b["bbox"] ox1, oy1 = max(ax1, bx1), max(ay1, by1) ox2, oy2 = min(ax2, bx2), min(ay2, by2) if ox2 <= ox1 or oy2 <= oy1: return 0.0 inter_area = (ox2 - ox1) * (oy2 - oy1) a_area = (ax2 - ax1) * (ay2 - ay1) if a_area <= 0: return 1.0 return inter_area / a_area def validate(self, existing_objects: List[Dict], placements: List[Dict]) -> List[str]: """ 校验新增物体是否满足规则。 返回违规信息列表;空列表表示全部通过。 """ violations = [] all_objects = existing_objects + placements # 规则 1:边界约束 for p in placements: if not self._is_inside_board(p): violations.append(f"物体 {p['id']} 中心点超出边界或距桌边过近") # 规则 2、3:间距与重叠约束 for i in range(len(all_objects)): for j in range(i + 1, len(all_objects)): a, b = all_objects[i], all_objects[j] if self._distance(a, b) < self.min_distance: violations.append( f"物体 {a['id']} 与 {b['id']} 距离小于 {self.min_distance} 像素" ) if self._overlap_ratio(a, b) > self.max_overlap_ratio: violations.append( f"物体 {a['id']} 与 {b['id']} 重叠面积超过 {self.max_overlap_ratio:.0%}" ) return violations

这里需要注意:上面的实现会把“已有物体之间”的距离也纳入检查。如果检测器给出的原始数据本身就存在物体过近,全量比较会把历史问题也报出来。实际项目中建议只校验“新增物体与所有物体”之间的关系,避免历史数据干扰新规划。

4.9 决策闭环:反馈规划器

# 文件路径:spatial_planner/planner.py import json import re from typing import List, Dict, Optional def extract_json(text: str) -> str: """从模型输出中提取 JSON 片段,兼容 Markdown 代码块。""" text = text.strip() if text.startswith("```"): text = re.sub(r"^```(?:json)?|```$", "", text, flags=re.MULTILINE).strip() start = text.find("{") end = text.rfind("}") if start == -1 or end == -1 or end <= start: raise ValueError("模型输出中未找到合法 JSON") return text[start:end + 1] class FeedbackPlanner: """ 生成 -> 解析 -> 校验 -> 反馈重试 的闭环控制器。 max_retries 控制最大尝试次数,防止无限循环。 """ def __init__(self, llm: BaseMLLMClient, validator: RuleValidator, max_retries: int = 3): self.llm = llm self.validator = validator self.max_retries = max_retries def run( self, system_prompt: str, user_prompt: str, image_path: Optional[str], existing_objects: List[Dict], ) -> Dict: for attempt in range(1, self.max_retries + 1): raw_output = self.llm.chat(system_prompt, user_prompt, image_path) try: data = json.loads(extract_json(raw_output)) except Exception as exc: user_prompt += f"\n\n注意:上一轮输出不是合法 JSON({exc}),请只输出 JSON。" continue placements = data.get("placements", []) violations = self.validator.validate(existing_objects, placements) if not violations: return data user_prompt += "\n\n上一轮方案违反以下规则,请重新规划并只输出 JSON:\n" user_prompt += "\n".join(violations) raise RuntimeError( f"经过 {self.max_retries} 次尝试仍未得到合规方案,最后输出:{raw_output}" )

extract_json的作用是容错。模型偶尔会把 JSON 放在 ```json 代码块里,或者夹杂少量解释文字,这个函数可以把核心 JSON 片段安全提取出来。重试次数建议设置为 3 到 5 次,太少则成功率低,太多则成本和延迟过高。

4.10 结果可视化

# 文件路径:spatial_planner/visualize.py from typing import List, Dict from PIL import Image, ImageDraw def draw_plan( image_path: str, output_path: str, existing_objects: List[Dict], placements: List[Dict], ) -> None: img = Image.open(image_path).convert("RGB") draw = ImageDraw.Draw(img) for obj in existing_objects: x1, y1, x2, y2 = obj["bbox"] draw.rectangle([x1, y1, x2, y2], outline="blue", width=3) draw.text((x1, max(0, y1 - 14)), obj["category"], fill="blue") for p in placements: cx, cy = p["center"] radius = 25 draw.rectangle( [cx - radius, cy - radius, cx + radius, cy + radius], outline="red", width=3, ) draw.text((cx, cy), p.get("category", ""), fill="red") img.save(output_path)

可视化是排查空间规划问题最直接的手段。边界问题、间距问题、重叠问题,在图上几乎一眼就能看出来。实际开发中可以把这张图保存到日志目录,方便回看模型的历史输出。

4.11 主流程

# 文件路径:spatial_planner/main.py from config import API_BASE, API_KEY, BOARD_HEIGHT, BOARD_WIDTH, MIN_DISTANCE, MODEL_NAME, SAFETY_MARGIN from llm_client import OpenAICompatibleClient from validator import RuleValidator from planner import FeedbackPlanner from prompt_builder import build_system_prompt, build_user_prompt from rules import build_rule_text from scene_parser import parse_scene from visualize import draw_plan def main(): image_path = "data/table.png" output_path = "output/plan_result.png" existing_objects = parse_scene(image_path) new_objects = [ {"id": "new_1", "category": "keyboard"}, ] llm = OpenAICompatibleClient(api_key=API_KEY, model_name=MODEL_NAME, base_url=API_BASE) validator = RuleValidator( board_width=BOARD_WIDTH, board_height=BOARD_HEIGHT, min_distance=MIN_DISTANCE, safety_margin=SAFETY_MARGIN, ) planner = FeedbackPlanner(llm, validator, max_retries=3) system_prompt = build_system_prompt(build_rule_text()) user_prompt = build_user_prompt( existing_objects=existing_objects, new_objects=new_objects, board_width=BOARD_WIDTH, board_height=BOARD_HEIGHT, ) result = planner.run( system_prompt=system_prompt, user_prompt=user_prompt, image_path=image_path, existing_objects=existing_objects, ) print("规划结果:", result) placements = result.get("placements", []) draw_plan(image_path, output_path, existing_objects, placements) print("可视化结果已保存到:", output_path) if __name__ == "__main__": main()

把配置集中在 config.py 里,方便不同项目复用。API_KEY建议从环境变量读取,不要提交到代码仓库。如果某个阶段调用失败,可以打开output/plan_result.png直接查看模型给出的坐标是否合理。

4.12 运行与预期结果

在完成模型服务配置后,执行:

cd spatial_planner python main.py

首次运行时,模型输出可能会触发一次或多次反馈重试。这是正常现象。如果一切顺利,程序会打印类似结果:

{ "reasoning": "键盘放在桌面右侧空白区域,与水杯距离约 180 像素,与笔记本距离约 120 像素,满足所有规则", "placements": [ { "object_id": "new_1", "category": "keyboard", "center": [620, 230] } ] }

如果模型在max_retries次内一直无法生成合规位置,程序会抛出RuntimeError,并保留最后一条模型输出,方便排查原因是提示词不足还是场景本身无解。

5. 常见问题与排查思路

问题现象常见原因解决思路
模型返回的不是 JSON提示词约束不足或模型指令遵循能力较弱在提示词中增加 JSON 示例;利用extract_json容错;检查是否启用了 JSON Mode
坐标超出图片范围模型对绝对坐标感知不准确校验器兜底;重试反馈;增加后处理钳位逻辑
距离规则始终不满足模型不知道已有物体的精确坐标在 user_prompt 中列出所有已有物体的 center 和 bbox
重试多次仍然失败当前场景确实没有合规位置,或规则过严放宽阈值;允许模型返回空 placements;人工介入审核
检测到的已有物体有偏差目标检测模型精度不足更换更强检测模型;加入人工校准步骤;对 bbox 做平滑后处理
API 调用报鉴权或限流错误密钥错误、配额不足、服务不可用检查环境变量;查看服务状态;增加指数退避重试

排查时建议严格按照顺序来:先确认图片和检测结果没问题,再确认提示词是否覆盖全部规则,最后再看校验器逻辑是否与规则描述一致。很多时候问题不是模型“不会”,而是上游输入数据和提示词本身有歧义。

一个容易被忽略的坑是坐标系不一致。检测模型输出的坐标可能是 0~1 的归一化坐标,而规划结果要求像素坐标,两者混用会导致空间规划完全错乱。所以工程上必须统一坐标系统,并在进入校验器之前完成转换。

6. 最佳实践与工程建议

6.1 规则设计要独立于模型

模型是可以换的,规则体系最好在设计之初就独立出来。把规则文本、校验逻辑、反馈文本分开维护,能够让你在切换模型服务时只改llm_client.py,其余代码不受影响。

每一条规则都应该有对应的单元测试。比如inside_boundary可以准备三类测试用例:完全合法、边界外、贴近边界。这样后续修改阈值时,能快速发现回归问题。规则是系统工程的一部分,不应该只是代码里的一段 if 判断。

6.2 提示词要版本化和可观测

空间规划任务里,提示词是影响成功率的最大变量。建议把系统提示词按照版本号管理,并在输出日志中记录每次请求使用的提示词版本和模型输出原始内容。当线上效果变差时,可以快速回滚到上一个稳定版本。

一个更稳妥的做法是把提示词模板和规则配置一起放到配置中心,方便灰度调整,避免每次修改都要重新发布代码。尤其在机器人、仓储等生产环境中,这种可观测性非常关键。

6.3 校验器是安全底线

无论模型多强大,校验器都不能取消。它应该是独立的、确定性的、无副作用的。也就是说,校验器不应该依赖网络请求,也不应该调用模型。它只用数学和逻辑判断输出是否合规。

校验失败后不要直接返回失败,优先尝试反馈重生成。重生成仍然失败时,要给用户一个明确的兜底动作,例如返回默认安全位置、转人工审核、或者彻底取消本次规划。安全相关的动作优先级要提前约定好。

6.4 生产环境的性能与成本控制

多模态大模型调用成本高、延迟高,工程方案必须考虑效率和成本。常见手段有:

  • 先做后处理修正,再进入反馈重试,减少无意义的模型调用;
  • 给规则校验设置短路逻辑,某条规则已经判定失败,就不必继续检查后续规则;
  • 对高频场景做结果缓存,相同布局和历史结果可以复用;
  • 在夜间或低峰期批量验证新提示词版本,而不是在生产时段临时改。

如果反馈重试超过两次,成功率会逐步下降。高并发场景下建议设置更低的max_retries,优先保证响应时间。

6.5 数据安全与最小权限

在调用云端多模态模型时,要注意图像数据可能包含敏感信息,比如监控画面、工单截图、用户个人物品等。上线前需要确认平台的数据协议,并进行必要的脱敏处理。对模型服务的密钥,统一使用密钥管理系统或环境变量注入,避免以明文形式出现在代码配置中。

给模型服务的权限也要遵循最小权限原则。如果整个系统只需要图片输入和文本输出,就不要让模型服务具备写文件、访问数据库等额外权限。这不是一个加分项,而是安全底线。

7. 总结与下一步方向

到这里,规则合规的视觉空间规划已经拆成了一条完整的工程链路:先定义机器可校验的规则,再用提示词把规则传给多模态大模型,强制模型输出结构化 JSON,最后用独立校验器拦截违规结果,并通过反馈重生成让模型自我修正。这套方案在桌面物品摆放、仓库分拣、机器人抓取规划、智能座舱交互等场景中都可以复用。

如果你要开始实践,建议从一个非常受限的小任务入手,例如只规划一个物体、两条规则、固定桌面大小。先把闭环跑通,再逐步增加物体数量和规则类型。你会发现,当模型连续三轮输出合规结果时,说明你的规则建模和提示词已经达到了一个相对稳定的状态。

下一步值得探索的方向有三个:一是把目标检测模型接入scene_parser,让整个系统从一张真实图片开始工作;二是引入约束解码或强化学习,进一步降低反馈重试次数;三是把单条规划扩展为“多物体顺序规划”,考虑后放置物体是否挤压先放置物体。视觉空间规划的下一个难点不是“模型能不能理解空间”,而是“模型生成的每个决策是否都能被规则系统证明为安全”。希望这篇文章能帮你把这条合规链路真正落到自己的项目中。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询