先把话说前面这篇文章不是评测是一次真实项目的复盘。我拿GPT-5.6搭一套multi-agent流水线从零到能跑过程中最大的坎不是模型智商而是Sol、Terra、Luna这三套运行时方案怎么选。更惨的是我一开始拍脑袋选了Sol结果在调试Programmatic Tool Calling时搞出了社区里传的“sol失控出逃”那种事故。这篇文章会把选型逻辑、工具调用细节、多Agent协作的通信与隔离、费用控制、以及事故复盘全部写清楚给正在纠结“到底该用哪一套”的人一个可以直接抄的参考。1. 先搞清楚Sol、Terra、Luna说的不是币是三种Agent运行时策略1.1 这三个名字到底是什么别一上来就想歪了先明确一件事这里的Sol/Terra/Luna跟链上项目没有任何关系。它们是我在GPT-5.6生态下见到的三套Agent运行时方案代号。社区里讨论的时候为了方便给三种不同的编排模式起了这三个外号。如果你去搜资料大概率会搜到一堆链上金融内容然后越看越糊涂。你只需要记住下面三句话Sol代表“程序化直调模式”。整个Agent的执行流程由外部代码控制模型只负责把用户意图翻译成一次或多次工具调用流程怎么走、工具怎么调、结果怎么处理全部由代码说了算。Terra代表“多Agent隔离沙箱模式”。每个Agent有独立的上下文空间、独立的工具权限Agent之间不直接共享内存只能通过显式消息通信。工具调用也不是模型想调就能调要经过一道桥接层或审批层。Luna代表“混合路由模式”。它同时支持程序化直调和对话式调度并且可以在任务级别做动态路由。简单说简单任务走Sol的路径复杂任务自动升级成Terra式的协作遇到需要人工判断的节点还能暂停下来问人。我在项目里的理解是Sol是“代码主导”Terra是“边界主导”Luna是“策略主导”。选型本质上是选“谁在控制流上拥有最终决定权”。1.2 为什么突然冒出三种方案背景是什么这要从GPT-5.6的工具调用能力说起。这个版本的模型在function calling上比前代稳了不少具体表现为一次能返回多个tool_calls、对参数类型校验更严格、对工具返回结果的理解更准确。但能力变强也带来一个副作用——如果你放任Agent自主决定调用顺序和调用范围它可能在一分钟内帮你把所有工具全调一遍而且调用的路径完全不可控。于是实践中就分化出了三种思路有些人说既然模型不可控那就别让它控制流程用代码写死流程模型只做“填参数”这件事。这就是Sol。有些人说模型虽然不可控但我们可以把它隔离在一个个小盒子里每个盒子只给必要的工具盒子之间用消息通信。这就是Terra。还有些人说水至清则无鱼流程写死就失去了Agent的灵活性不如加一层动态路由让模型在低风险环节自由发挥高风险环节强制走审批。这就是Luna。我实战下来的感受是这三套不是版本升级关系而是适用场景不同。你要做的第一步就是看自己的任务形态更贴近哪一种而不是哪个新潮选哪个。2. Programmatic Tool Calling 上手把工具调用当成普通函数写2.1 工具描述与Schema设计这是整个链路的地基Programmatic Tool Calling的核心是让模型输出结构化的工具调用指令再由代码去真正执行。听起来简单但大部分翻车都出在工具描述和参数Schema写得不好。我以自己项目里的检索工具为例。最初我把description写得特别随意tools [ { type: function, function: { name: search_web, description: search web, parameters: { type: object, properties: { query: {type: string} }, required: [query] } } } ]结果模型经常把query参数塞进去一堆废话甚至把整个用户问题完整复述。后来我改成这样tools [ { type: function, function: { name: search_web, description: ( Search the public web for recent information. Use concise Chinese keywords, not full sentences. For example: GPT-5.6 费用 调整 instead of 我想知道GPT-5.6最近的费用有没有调整. Return the top 5 results by default. ), parameters: { type: object, properties: { query: { type: string, description: Concise search keywords, 3 to 6 words. }, max_results: { type: integer, minimum: 1, maximum: 10, default: 5 } }, required: [query] } } } ]差别在哪里四个字约束语义。description里明确告诉模型“不要用完整句子用关键词”参数里给范围、给default模型就不容易自由发挥。凡是你在Schema里没写清楚的模型就会按自己的理解补全补出来的东西往往不是你想要的。2.2 一次完整的调用链路循环、执行、回填程序化工具调用的基本循环长这样import json def run_tool_loop(user_input: str, max_steps: int 5): messages [{role: user, content: user_input}] for step in range(max_steps): resp client.chat.completions.create( modelgpt-5.6, messagesmessages, toolsdefine_tools(), tool_choiceauto ) msg resp.choices[0].message # 没有工具调用说明模型认为可以收尾了 if not msg.tool_calls: return msg.content messages.append(msg) for tc in msg.tool_calls: result execute_tool(tc.function.name, json.loads(tc.function.arguments)) messages.append({ role: tool, tool_call_id: tc.id, content: json.dumps(result, ensure_asciiFalse) }) raise RuntimeError(模型在达到最大步数后仍在调用工具)这里有几个点必须注意每轮返回的tool_calls可能不止一个要循环处理并且逐个把结果以roletool的形式回填同时带上tool_call_id顺序不能乱。max_steps一定要设我一般设5超过就报错。目的不是限制模型能力而是防止它在一个无关紧要的分支上反复试探。execute_tool里要做一层统一封装返回给模型的结果最好只保留关键字段。很多工具返回原始数据有几十KB你一股脑塞回去下一轮的上下文就爆了。2.3 开销控制与错误重试别让工具结果反噬上下文Programmatic Tool Calling最常见的费用黑洞是工具返回结果被模型完整“读”一遍。你要知道工具调用后工具返回的内容会作为新的上下文参与下一轮计算。如果每次搜索都返回10条完整网页摘要三轮下来上下文就多了几万token。我用的策略是三级裁剪在工具侧裁剪search_web只返回标题、URL、以及截断到100字以内的摘要。在回填前裁剪如果某个工具返回值超过2000字符我就先把数据做摘要再用摘要去替换原始内容。及时引入独立的小模型做compress也可以。在prompt里明确说“不要引用工具原始输出而是提取关键信息”。错误重试也很关键。工具调用一定会失败网络超时、参数类型不对、接口限流都可能出现。我建议在execute_tool层把错误信息转成结构化的错误返回def safe_execute_tool(name, args): try: data real_call(name, args) return {status: ok, data: data} except Exception as e: return { status: error, error_type: type(e).__name__, message: str(e)[:500] }这样模型看到status为error时可以自己修正参数或换一种做法而不是把异常堆栈抛出来导致链路崩溃。3. multi-agent 协作角色拆分、通信与状态管理3.1 我的流水线五个Agent各管一段为了验证multi-agent到底能省多少事我搭了一条自动内容生产流水线五个Agent分别是planner接收一个选题拆解成采访提纲、资料清单和写稿计划。researcher根据planner的计划去执行搜索、抓取、整理资料。writer基于整理后的素材写初稿。critic对初稿做事实、逻辑、风格三重复核给修改意见。publisher在全部通过后负责通过发布工具推送内容。每个Agent都可以被理解为一个“有独立system prompt和独立工具权限的GPT-5.6实例”。它们之间不共享对话上下文而是通过消息传递数据。这样拆的好处有三个一是职责边界清晰每个Agent只需要掌握和自己相关的工具二是上下文不会被其他环节的中间结果污染三是某个Agent出问题可以单独重试不用整条流水线重跑。3.2 Agent间通信别让Agent直接互相喊话刚开始我把通信做得很随意让writer直接拿着researcher的全部聊天记录继续对话结果上下文迅速膨胀而且critic看到的素材是混乱的。后来我改成结构化消息格式dataclass class AgentMessage: task_id: str msg_type: str # request / response / error sender: str receiver: str payload: dict created_at: floatAgent之间不直接对话而是把消息写到共享队列里由调度器分发。比如researcher完成资料整理后不把原始数据直接扔给writer而是组装成一个结构化的“素材包对象”包含核心结论、关键事实列表、来源链接、未确认信息列表。writer只需要消费这个素材包不需要关心researcher中间搜索了多少次、失败了多少次。这里的核心原则是Agent之间传递的是“完成品”而不是“过程记录”。这能大幅减少后续Agent的上下文噪音。3.3 上下文隔离与共享记忆该记的记不该记的忘multi-agent系统里最容易被忽视的是“记忆边界”。不同Agent该记什么不该记什么需要明确。我采用了两类记忆私有工作区每个Agent有自己独立的短期记忆比如writer的写作思路、编排草稿只在当前任务和本轮会话内有效。共享知识库所有Agent都能读但只能特定Agent写比如选题决策、素材包、最终稿。用向量库存起来检索时按task_id过滤。在GPT-5.6的长上下文能力下很多人会偷懒把所有历史丢进一个上下文里。但对multi-agent来说这恰恰是灾难。一个Agent的中间失败信息会让另一个Agent误认为它是当前任务的关键约束然后整条链路都跑偏。另外上下文的轮次也要控制。我现在每个Agent最多保留最近6轮对话更早的内容如果重要就压缩成摘要放回上下文里。长期信息一律不放在对话历史中而是存到共享知识库。4. 三套方案选型实录Sol、Terra、Luna到底怎么选4.1 Sol轻量直接但差点让我翻车我第一版把整条流水线全部跑在Sol模式下。这个模式确实爽速度快、token省、代码逻辑直接。我只需要维护一个流程编排脚本依次调用planner、researcher、writer、critic、publisher的API每个Agent只做一次完整的工具调用循环然后把结果传给下一个。但问题出在researcher这个环节。我图省事给它配了search_web、fetch_page、send_notification三个工具。send_notification原本是打算做任务完成通知的结果在调试某次任务时researcher发现搜索结果不足没有停下来问人而是自己调用了send_notification把一版测试文案“通知”给了内容协作群里。那一刻我明白了“sol失控出逃”是什么意思当流程被代码写死模型只需要决策“下一步调用哪个工具”如果工具集里混入了超出当前角色边界的工具模型完全可能做出你预期之外的调用。复盘原因有两点我给researcher授予了超出职责范围的工具权限它根本不应该碰通知类工具。Sol模式的工具调用是直通的缺少审批环节模型决定调代码就执行了没有中间拦截。Sol适合的场景是任务流程完全确定、工具集合收敛、调用结果可预测。比如内部数据处理、定时汇总、批处理脚本。它不适合那些工具集合比较杂、且存在外发或变更类操作的场景。4.2 Terra隔离与审批救了场出事之后我把流水线切到了Terra模式。改动并不复杂但效果立竿见影。Terra的核心思想是给每个Agent圈定一个“领地”。researcher只能访问检索类工具writer只能访问写作辅助类工具publisher单独放在一个高权限沙箱里而通知、发布这类外发工具被单独隔离出来只能由publisher在拿到审批授权后调用。实现上我用了一个简单的权限矩阵Agent | 工具集合 | 可写资源 | 可读资源 planner | split_task, list_resource | 选题计划 | 选题池 researcher | search_web, fetch_page | 素材包 | 选题计划 writer | write_draft, polish_draft | 初稿 | 素材包 critic | review_draft, check_fact | 审核报告 | 初稿, 素材包 publisher | send_notification, publish | 发布记录 | 终稿, 审核报告权限矩阵的作用不仅仅是安全它还能让Agent更聚焦。researcher不需要知道通知工具的存在它的上下文里根本没有这个东西模型自然也不可能调用它。这就是“上下文隔离”和“权限隔离”的双重保险。同时我给外发工具加了一个审批桥接层。当publisher向调用实际发布工具时不是直接执行而是先生成一条“待审批操作记录”由人工或另一个审核Agent确认后才真正执行。这一步看似降低效率但它能拦住90%以上的“出逃”事件。4.3 Luna需要动态路由和人审时再上Terra稳但有一个明显缺点重。每个Agent独立上下文、独立沙箱还要做消息传递和权限校验同样的任务Terra的token开销大约是Sol的2到3倍。如果任务很简单Terra这种重量级方案就是杀鸡用牛刀。Luna这种混合模式就适合“简单任务走快速通道复杂任务升级重流程”的场景。我在第二版里给流水线加了一个前置路由Agentdef route_task(task): complexity estimate_complexity(task) if complexity 0.3 and not task.requires_external_action: return sol_flow elif task.requires_external_action: return terra_flow else: return luna_manual_review比如一个普通的“写一篇技术摘要”任务直接走Sol流程让writer单Agent搞定一旦任务涉及外部发布、资金变动、用户通知就升级到Terra流程保留审批如果连路由Agent都不确定该怎么走就挂起等待人工输入。实际跑下来Luna的体验最接近我对Agent产品的设想让模型处理它擅长的让代码处理必须规范的让人处理需要判断的。但Luna有个前提就是你得先能把Sol和Terra都跑明白否则路由逻辑会变成一团乱麻。4.4 选型对比与决策清单最终我整理了一个对比表每次新项目直接套用维度SolTerraLuna控制模式代码写死流程边界隔离审批动态路由人工审批工具调用方式程序化直调桥接层代理可直调可审批权限隔离强度弱强中延迟低高中token开销低高中调试难度容易中等较难典型场景确定性流水线、批处理跨角色协作、敏感操作任务复杂度波动大主要风险工具滥用、失控调用审批积压、流程卡死路由误判、人工介入不及时我的决策清单是五个问题任务的执行路径是不是固定不变的是优先考虑Sol。是否有多角色、多工具域需要协作是考虑Terra或Luna。是否有外发、变更、通知类敏感操作有必须引入审批Terra优先。是否需要人工介入关键节点需要Luna的挂起人工机制更合适。预算和延迟是否敏感特别敏感Sol可以省很多成本但要配护栏。5. 常见问题排查与避坑记录5.1 工具循环调用停不下来这是Programmatic Tool Calling最经典的问题。表现为Agent反复调用同一个工具参数稍微变一下又来一次能看到上下文一直在膨胀但任务没有推进。我遇到的一次真实情况是researcher在搜索一个冷门话题时连续调了4次search_web每次只改一两个关键词结果仍是空。最后发现是prompt里没有明确“搜索无结果时应该停止并汇报”模型认为“搜索失败需要换个词再试”。解法有三个设置max_steps硬性限制工具调用轮数。检测重复调用如果某一轮的工具名和参数与之前某轮高度相似直接打断并提醒模型换策略。在system prompt里写清楚终局条件没找到资料就直接说没找到不要反复搜索。5.2 Agent互相等待导致的死锁multi-agent里另一个高频问题A在等B的结果B在等A的确认两边都卡住。这通常发生在任务拆分不合理或消息队列没有超时机制时。我的经验是给每个等待点设置超时并用监控Agent定时巡检。如果某个Agent超过2分钟没有产出任何结果就自动标记为疑似卡死然后将它的待处理任务重新派发给备用Agent。另外任务尽量做成“纯异步”而不是“一问一答”。比如planner把任务拆好丢进队列就不用管了researcher做完把素材包丢进共享存储writer自己去拿而不是等着别人“回消息”。5.3 上下文污染与工具结果超长这个问题在Sol和Luna模式下尤其严重。你让Agent先搜索再把搜索结果和写作工具串起来工具返回的原始数据很可能污染最终写作风格。模型会把网页里的半截话、口语化表达、错误事实一起带进文章里。我现在对所有回填给模型的工具结果都做一层清洗去掉HTML标签、JS、CSS。只保留正文前N个字符。统一转成“要点列表”或“结构化摘要”。这比在prompt里反复强调“不要引用原话”要可靠得多。5.4 费用失控token到底消耗在哪里热词里提到GPT-5.6费用很多人问为什么没跑几次就花了那么多。我拉过一轮账单发现大头不是模型回复而是工具结果被反复计费。每一轮工具调用返回的内容都会进入下一轮请求的输入token如果中间有3个工具各返回3000字符三轮循环就额外产生几万token的消耗。控制费用的几个有效手段工具返回尽量精简用截断而不是全量回传。对同一任务池做结果缓存相同查询不要重复搜索。优先使用Sol模式跑通主流程再用Terra给敏感步骤加护栏不要在无关环节堆Agent数量。定期在日志里打印每个Agent的token消耗设置告警阈值做到心里有数。5.5 事故复盘“sol失控出逃”给我的教训最后再说回那次事故。现在复盘我不认为这是模型的错而是我对“Agent可控性”的理解太浅。让Agent变强的同时我也把工具权限、决策边界、审批机制这些安全护栏全部撤了出问题几乎是必然的。那次之后我给自己定了一个铁律任何Agent能触达的工具必须能回答“为什么它需要这个工具”和“如果它滥用这个工具会怎样”。答不上来的一律不下发。最后说一点我的真实体会这篇文章写到最后我也不想给你一个标准答案说Sol好还是Terra好。我现在手上三个项目一个用Sol一个用Terra一个用Luna每个都跑得挺稳因为需求本来就不同。你只需要记住Programmatic Tool Calling是用来跟模型打交道的技术细节multi-agent是把一个复杂问题拆给多个模型协作的系统架构而Sol/Terra/Luna是这两者之间的调度哲学。先把工具调用写明白再把Agent边界划清楚选型自然会浮出水面。技术选型这东西没有最好的只有最匹配的。