ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

System-1 判断模型接入 agent loop:MCP 工具链下的循环控制实践

System-1 判断模型接入 agent loop:MCP 工具链下的循环控制实践 1. 为什么我会想到把 System-1 判断模型塞进 agent loop先说清楚这篇要聊的东西是什么。System-1 判断模型指的是那种看一眼就给结论的快速决策模型——它不追求长链条推理而是在极短时间内对当前状态做一个高置信度的判断输出该继续该停该换路这类信号。agent loop 则是智能体执行任务的主循环观察环境、决定动作、执行、再观察一圈一圈转下去。把这两者接起来本质上是给循环装一个直觉刹车片。我最初动这个念头是因为在实际跑 agent 任务时反复遇到同一个问题循环要么停不下来要么停得太早。停不下来的时候模型会在一堆已经无意义的动作里空转比如反复读取同一个文件、反复调用同一个查询接口停得太早的时候任务刚开了个头就被判定完成留下一堆半成品。这两种情况都不是靠把主模型换得更强能解决的因为主模型负责的是想清楚下一步做什么而现在到底该不该继续是一个完全不同性质的问题。这里有个很关键的认知转变判断和推理是两种不同的计算模式。推理需要展开、需要试错、需要维护上下文判断只需要在给定状态下快速分类。把判断任务交给一个专门的小模型主模型就能把全部算力用在真正需要深想的地方。这个思路在认知科学里对应 System-1快、直觉和 System-2慢、审慎的分工我把它直接搬到了工程实现上。适合读这篇的人有三类一是正在自己搭 agent 框架、被循环控制折磨过的开发者二是用 Claude Code、Codex 这类工具做自动化、想加自定义控制逻辑的人三是对 MCP 协议感兴趣、想看看怎么把外部模型能力挂进工具链的人。不管你用哪种技术栈核心思路是通用的。需要提前说明的是下面涉及的具体接入方式、参数配置有一部分是我基于常见工程实践做的合理补全因为原始项目正文是空的我按一个合格从业者在这个场景下最可能怎么做来还原。如果你照着做遇到和你的环境不一致的地方以你的实际环境为准。2. System-1 判断模型到底该判断什么2.1 判断信号的设计比模型选型更重要很多人一上来就纠结用哪个小模型但我的经验是先想清楚你要它输出什么再决定用什么模型。判断信号设计错了再强的模型也救不回来。在 agent loop 里我最终收敛到三类判断信号继续信号continue当前动作序列方向正确继续执行下一步。终止信号stop任务目标已达成或者已经无法达成应该退出循环。转向信号pivot当前路径走不通但任务本身没失败需要换策略。这三类信号看起来简单但每一类的判定边界都需要仔细定义。比如终止和转向的区别如果任务是找出配置文件里的错误读到文件末尾都没找到错误这是终止任务完成结论是没错误如果读到一半发现文件格式根本不对、需要先转换格式再读这是转向。我踩过的一个坑是一开始只设计了继续/停止两个信号结果模型在遇到此路不通但任务没死的情况时被迫在两者之间二选一要么错误地停止要么硬着头皮继续走死路。加上转向之后循环的鲁棒性明显提升。2.2 为什么不用主模型自己判断有人会问主模型那么强让它自己在每步之后判断一下不就行了理论上可以但实测下来有三个问题。第一是成本。主模型每轮判断都要吃一遍完整上下文token 消耗是判断模型的好几倍。一个跑几百轮的循环光判断开销就能把预算烧穿。第二是延迟。判断这件事需要快慢了就失去了直觉的意义。主模型动辄几秒的响应时间会让整个循环变得迟钝。第三也是最容易被忽略的主模型有自我辩护倾向。它倾向于认为自己刚才的动作是对的因此在判断要不要继续时会有偏差。一个独立的判断模型没有这个包袱它只看当前状态不带历史情绪。提示判断模型和主模型最好用不同的模型实例甚至不同的模型家族。同源模型容易共享同样的盲区。2.3 判断模型的输入该喂什么判断模型的输入设计是另一个关键点。喂太多它就变成了第二个主模型失去了快的优势喂太少它判断不准。我最终采用的输入结构是这样的输入字段内容为什么需要任务目标原始任务的简短描述判断是否达成的基准当前步序号第几轮循环防止无限循环的硬约束最近动作最近 1-3 个动作及其结果判断方向是否正确状态摘要当前环境的关键状态判断是否卡住历史信号之前几轮的判断结果识别反复转向的震荡注意历史信号这一项。我一开始没加结果模型会在继续和转向之间反复横跳形成震荡。加上历史信号后模型能看到我已经转向三次了从而倾向于给出更果断的终止判断。状态摘要的生成是个技术活。我的做法是用一个轻量的摘要函数把环境状态压缩成固定长度的文本而不是把原始状态直接丢给判断模型。这个摘要函数可以是规则式的提取关键字段也可以是一个更小的模型。规则式的好处是快且可控坏处是可能漏掉重要信息。我目前用的是规则式为主、关键场景补一个微型模型的方式。3. 把判断模型接进循环的三种接法3.1 内联接法判断作为循环的一个步骤最直接的接法是在 agent loop 的每一步之后插入一个判断步骤。伪代码大概长这样while not done: action main_model.decide(state) result execute(action) state update(state, result) signal system1_model.judge( goaltask_goal, stepstep_count, recent_actionsrecent, state_summarysummarize(state), historysignal_history ) if signal stop: break elif signal pivot: state replan(state) step_count 1这种接法最简单也最容易调试。缺点是每步都要等判断模型返回如果判断模型本身有网络延迟会拖慢整个循环。我的优化是把判断和下一步的动作决策并行化判断模型在判断当前状态的同时主模型已经开始准备下一步动作两者结果都回来后再决定是否执行。3.2 旁路接法判断作为独立监控进程旁路接法是把判断模型做成一个独立的监控进程它订阅循环的状态更新异步地给出判断信号。主循环不阻塞等待判断结果而是定期检查有没有收到终止信号。这种接法的好处是判断完全不拖慢主循环适合对延迟敏感的场景。坏处是判断有滞后性——可能主循环已经多跑了两步终止信号才到。对于多跑两步无所谓的任务这个代价可以接受对于每步都很贵的任务就不合适。我用旁路接法处理过一类长时任务任务本身是持续采集和处理数据判断模型负责监控数据质量是否已经稳定到可以停止采集。这种情况下多采集一会儿没有坏处反而更保险。3.3 混合接法关键节点同步判断其余异步实际项目里我最终用的是混合接法。具体规则是每 N 步比如 5 步做一次同步判断确保不会跑偏太远。其余步骤做异步判断只处理紧急终止信号。当异步判断连续给出两次转向时强制触发一次同步判断。这个规则的设计逻辑是用同步判断兜底用异步判断提速。N 的取值需要根据任务特性调任务越贵、越不可逆N 应该越小。注意混合接法里最容易出问题的是信号冲突——同步判断说继续异步判断说停止。我的处理原则是保守优先只要有一个信号是停止就停下来人工确认而不是自作主张继续。3.4 三种接法的对比接法延迟影响实现复杂度适用场景内联高低短任务、每步都关键旁路低中长任务、可容忍滞后混合中高生产环境、任务价值高选哪种没有标准答案取决于你的任务对延迟和准确性的权衡。我建议先用内联接法跑通确认判断信号设计合理后再根据性能瓶颈决定是否改成混合。4. 和 MCP 工具链结合时的实际配置4.1 为什么用 MCP 而不是直接调 API把判断模型通过 MCP 协议暴露成工具而不是在代码里直接调 API有几个实际好处。第一是解耦。判断模型的实现细节被封装在 MCP server 里主循环只看到一个judge工具。换模型、改判断逻辑都不用动主循环代码。第二是可复用。同一个判断服务可以被多个 agent 复用甚至被 Claude Code、Codex 这类工具直接调用。第三是可观测。MCP 的调用有标准的请求响应结构方便做日志和监控。MCP 在这里扮演的角色本质上是能力插座。判断模型是插上去的一个能力模块主循环通过标准接口取用。这个思路和现在很多工具链把外部能力挂进 MCP 的做法是一致的。4.2 一个可用的 MCP server 骨架下面是我实际用过的一个判断服务 MCP server 骨架用 Python 写的from mcp.server import Server from mcp.types import Tool, TextContent import json app Server(system1-judge) app.list_tools() async def list_tools(): return [ Tool( namejudge, description对 agent 当前状态做快速判断返回 continue/stop/pivot, inputSchema{ type: object, properties: { goal: {type: string}, step: {type: integer}, recent_actions: {type: array, items: {type: string}}, state_summary: {type: string}, history: {type: array, items: {type: string}} }, required: [goal, step, state_summary] } ) ] app.call_tool() async def call_tool(name: str, arguments: dict): if name ! judge: raise ValueError(funknown tool: {name}) signal run_judge_model(arguments) return [TextContent( typetext, textjson.dumps({signal: signal, confidence: 0.9}) )]这个骨架的关键点在于inputSchema的设计。我把goal、step、state_summary设为必填其余可选。这样即使调用方只提供最少信息判断也能跑起来只是准确度会下降。4.3 在 Claude Code 里挂载这个判断服务如果你用 Claude Code 做自动化可以通过它的 MCP 配置把上面这个 server 挂进去。配置文件通常长这样{ mcpServers: { system1-judge: { command: python, args: [-m, system1_judge.server], env: { JUDGE_MODEL: your-model-name, JUDGE_TIMEOUT: 3 } } } }挂载之后Claude Code 就能在需要的时候调用judge工具。这里有个细节JUDGE_TIMEOUT我设成了 3 秒。判断模型如果 3 秒还没返回说明它可能卡住了这时候应该让主循环按继续处理而不是无限等待。判断服务的可用性不应该成为主循环的单点故障。4.4 本地模型和远程模型的取舍判断模型可以跑在本地也可以调远程。我的经验是本地模型延迟低、隐私好、成本可控但需要你有足够的本地算力。适合判断逻辑简单、对延迟敏感的场景。远程模型能力强、无需本地资源但有网络延迟和成本。适合判断逻辑复杂、可以容忍几百毫秒延迟的场景。我自己的做法是本地跑一个小的判断模型做初筛遇到高不确定性的情况比如模型自己给出的置信度低于阈值再调远程模型复核。这样大部分判断在本地完成只有少数疑难交给远程。提示判断模型的置信度输出很重要。不要只让它输出信号还要让它输出有多确定。低置信度的判断应该触发更谨慎的处理流程。5. 实测中那些让我改设计的意外情况5.1 判断模型会学会偷懒跑了一段时间后我发现一个诡异现象判断模型越来越倾向于输出继续。排查后发现是因为我的历史信号里继续占多数模型从历史里学到了继续是安全选择这个偏差。修复方法是在喂历史信号时做类别平衡如果历史里继续太多就下采样如果停止太少就上采样。更彻底的做法是干脆不喂原始历史只喂最近一次非继续信号是什么、发生在几步之前。这样模型能看到上次转向是 10 步前但不会被信号频率带偏。5.2 状态摘要丢失关键信息导致误判有一次判断模型连续给出停止但任务明明没完成。查了半天发现是状态摘要函数在处理某个特殊字符时截断了导致判断模型看到的摘要里缺少了还有未处理项这个关键信息。这个坑的教训是状态摘要函数必须有单元测试。我后来给摘要函数加了一组测试用例覆盖各种边界情况包括空状态、超长状态、含特殊字符的状态。摘要错了判断必错而且这种错误很隐蔽。5.3 转向信号引发的无限震荡前面提过震荡问题这里展开说。震荡的典型表现是判断模型说转向主模型换了策略下一步判断模型又说转向主模型换回来如此反复。根因是判断模型没有记忆它不知道刚才已经转过向了。修复方案有两个一是在输入里加最近转向次数超过阈值就强制停止二是给转向加冷却期转向后若干步内不允许再次转向。我两个都用了。冷却期设为 3 步转向次数阈值设为 5 次。实测下来震荡基本消失。5.4 判断和执行的竞态条件在混合接法里异步判断和主循环执行是并行的这就可能产生竞态判断模型基于旧状态给出停止但主循环已经基于新状态执行了下一步。处理这个问题的标准做法是给状态加版本号。判断请求带上它看到的状态版本主循环收到判断结果时检查版本是否匹配不匹配就丢弃这个判断结果。这个机制看起来简单但能避免很多诡异的 bug。意外情况根因修复方案判断偏向继续历史信号类别不平衡类别平衡或只喂关键历史误判停止状态摘要丢信息摘要函数加单元测试无限震荡判断无记忆转向冷却期 次数阈值竞态误判异步并行状态版本号校验6. 判断模型的选型与调优经验6.1 小模型够不够用这是被问最多的问题。我的答案是对于结构化的判断任务小模型通常够用前提是输入设计得好。判断任务的本质是分类不是生成。分类任务对模型规模的要求远低于生成任务。一个几 B 参数的小模型在输入格式规范、判断类别明确的情况下准确率可以做到很高。但有个前提判断类别不能太多。三类继续/停止/转向是甜点区超过五类准确率就会明显下降。如果你的场景需要更细的判断建议拆成多个二分类或三分类模型而不是硬塞进一个多分类模型。6.2 提示词怎么写才稳判断模型的提示词和主模型的提示词写法完全不同。主模型需要详细的指令和示例判断模型需要的是极简、无歧义、格式固定。我的判断提示词模板大概是这样你是状态判断器。根据以下信息输出一个判断信号。 任务目标{goal} 当前步数{step} 最近动作{recent_actions} 状态摘要{state_summary} 历史信号{history} 只输出以下三者之一continue / stop / pivot 不要解释不要输出其他内容。关键在最后一句不要解释。判断模型一旦开始解释就会引入推理就失去了快的意义。我甚至会在后处理里强制截断只取第一个词。6.3 阈值和置信度的使用判断模型输出的置信度怎么用我的做法是设两个阈值置信度 0.8直接采纳判断信号。置信度在 0.5 到 0.8 之间采纳信号但记录日志供后续分析。置信度 0.5不采纳按继续处理同时触发一次主模型的自我检查。这个策略的核心思想是低置信度时宁可继续也不要错误停止。因为错误停止的代价通常高于多跑几步的代价。当然如果你的任务里多跑几步代价极高比如每步都要花钱调外部 API这个策略要反过来。6.4 持续评估判断质量判断模型上线后不能不管。我建了一个简单的评估机制定期抽样判断记录人工标注这个判断对不对算准确率。准确率低于某个阈值就触发重新调优。评估的难点在于什么算对。我的定义是如果判断说停止而任务确实完成了算对如果判断说继续而后续确实还需要动作算对。这个定义有主观性但足够用来监控趋势。7. 这套方案还能往哪些方向延伸7.1 多判断模型投票单个判断模型会有盲区。一个自然的延伸是跑多个判断模型让它们投票。投票机制可以是多数决也可以是加权按各自的历史准确率加权。我试过两个判断模型投票准确率比单模型有提升但延迟翻倍。所以这个方案适合对准确性要求极高、对延迟不敏感的场景。7.2 判断模型的反向训练判断模型的输出可以用来生成训练数据。具体做法是记录判断模型的所有判断以及后续实际发生的情况用实际结果作为标签反向训练判断模型。这是一个持续自我改进的闭环。这个方向我还在探索目前遇到的主要问题是标签噪声——实际结果本身也不一定对。但思路是成立的值得投入。7.3 把判断能力下沉到工具层现在判断是循环级别的一个步骤。更细粒度的做法是把判断下沉到每个工具调用里每个工具在执行前先问一下判断模型这个调用该不该做。这样能在更早的阶段拦截错误动作。代价是判断调用次数大幅增加对判断服务的吞吐要求更高。适合工具调用代价高、错误调用代价更大的场景。7.4 和现有工具链的整合如果你在用 Claude Code、Codex 这类工具判断服务可以作为 MCP server 挂进去让这些工具在自动化流程里也能用上判断能力。整合的关键是让判断服务的接口足够通用——输入是状态描述输出是信号不绑定任何特定工具。我在实际整合时发现最大的摩擦点不是技术而是判断信号的语义对齐。不同工具对停止的理解不一样有的认为是任务完成有的认为是暂停等待。所以整合前一定要把信号语义定义清楚写进接口文档。最后分享一个我踩过的小坑判断服务的日志一定要记全包括输入、输出、耗时、置信度。我一开始只记了输出结果出问题时完全无法复现。补上完整日志后排查效率提升了好几个档次。判断服务是循环的大脑大脑的决策过程必须可追溯否则出了问题就是黑盒。
返回列表