前言
多步任务的计划不能只写在提示词里。提示词下一轮就会变成普通历史,并行工具调用还会把同一份列表写乱,简单问答又会被规划流程拖慢。更稳妥的做法是:把待办放进图状态,用一个只做整表覆盖的工具去改它,在每次请求模型前注入规划约束,在模型返回后拦截违规的并行写入。读写文件这类领域工具只做领域的事,不碰进度。
计划要活在循环里
智能体是反复循环:组装请求、调用模型、执行工具、把结果写回消息,再请求模型,直到不再调用工具。计划若只存在于某一次系统提示或某条助手消息里,下一轮就没有结构,运行时也无法检查。
因此规划要同时满足三点。计划对后续每一轮可见,而且是结构化数据。模型可以改计划,但改法必须可判定,即整表覆盖,而不是在多条消息里打补丁。运行时要能拒绝破坏一致性的调用,而不是只靠模型自觉。这三件事分别落在状态、工具和中间件钩子上。
待办是运行时状态
在智能体状态上增加一份待办列表。每条只有任务描述和状态,状态只允许排队中、处理中、已完成。不再需要的步骤直接从列表删除,不再另设取消或阻塞。
这份列表要从用户输入里省略。用户只提交目标,不提交内部进度,避免调用方伪造一份"已完成"的列表。列表只能由规划工具写入,并随检查点保留;线程恢复时进度还在,不必从历史消息里重新解析。消息里仍要有一条工具回执,让模型看见刚才更新了什么;程序真正读取和覆盖的是状态字段。
整表覆盖
规划工具每次收到的都是完整列表,写入方式是整表替换,不提供"只改某一条"或"追加一条"。增量写法省 token,但分不清漏传和有意删除;并行时两次增量也无法确定合并顺序。整表覆盖的语义只有一句:这次调用之后,列表就等于这次提交的内容。还要保留的已完成项,必须原样出现在新列表里。
返回值应同时替换状态中的待办,并追加一条带调用编号的工具消息。只回文本,程序没有结构可查;只改状态,工具调用就对不上结果。调用编号由运行时注入,不放进给模型的参数。
同一轮只写一次
无共享写点的工具可以并行,例如同时读两个文件。规划工具的写点是同一份列表,语义又是整表替换。同一轮写两次,后一次会丢掉前一次,而两次参数通常都只是各自心目中的全量,并不能合并。
所以要在模型返回之后、工具执行之前检查最近一条助手消息。其中规划工具超过一次,就全部不覆盖,并为每一次调用各回一条错误消息。少回一条,循环会停在有调用无结果;笼统回一条,模型对不上是哪次被拒。次数不超过一次时不干预。领域工具不受这条规则限制,可以和一次规划写入出现在同一轮。
这个判断必须放在模型之后的钩子里。单次工具执行看不到同一条回复上的全部调用,钩子看到的是完整状态。
约束加在模型边界
智能体原有的系统提示负责角色和领域能力。规划协议是另一层:何时建列表、如何标状态、何时不该规划。每次请求模型之前,把规划说明接在当前系统消息后面;原来没有系统消息时,规划说明单独成为系统消息。同步和异步两条路径做同一件事。这样换规划策略不必改智能体本体。
协议本身保持很短。多步、多目标、计划可能随中间结果增减时才规划;三步以内或纯问答直接做完。开始某步前标为处理中,做完立刻标为已完成。未全部完成时,列表里至少留一条处理中。全部完成后,给用户的最终回复放到下一条消息,不和最后一次规划写入挤在同一轮。
提示词管使用习惯,钩子管会破坏状态的调用形态。模型是否真的做完了某一步,运行时无法完全判定;同一轮多次覆盖则可以判定。
和领域工具分开
领域工具只表达领域副作用,不读、不改待办。规划工具只替换待办并留下回执,不碰外部资源。模型先把步骤写入状态,再调用领域工具完成当前那条处理中的步骤,然后回到规划工具把它标成已完成。领域工具失败后,模型再修订列表:增加步骤,或删掉还没做且不再需要的条目。
模型看不到中间件实现,只能看到工具名、参数和描述。描述里要写明整表覆盖、合法状态、禁止并行多次调用,以及不该使用的场景。系统提示每轮提醒,工具描述紧挨着决策点,两处重复是有意的。
一次循环里的顺序
请求模型前,把规划说明并入系统消息。模型返回后,若规划工具在同一条回复里出现多次,写入错误消息并跳过覆盖;若只有一次,则替换待办并追加成功回执。其他工具按各自语义执行。然后带着新的消息和状态再次请求模型,直到不再出现工具调用。
构造时把中间件放进中间件列表,把领域工具放进工具列表。中间件注册规划工具,状态模式指向带待办字段的扩展状态。规划工具的同步和异步入口共用同一套覆盖逻辑。
取舍
这套机制不保证计划最优,也不验证"已完成"在外部世界里确实完成。它保证进度有一块独立于聊天文本的状态,修改方式只有整表覆盖,同一次回复里的多次覆盖会在执行前被挡住,规划约束在每一轮模型请求前都在,领域副作用和进度副作用分开。简单请求不进入规划,避免多一次工具往返。计划会错、会改;中间件管的是改的时候状态仍然单一、完整、可恢复。