
1. 从一个全能助手到一支协作小队为什么需要 Sub-Agents刚开始接触智能体编排的时候我走的也是那条最直觉的路——把所有能力塞进一个 Agent 里。系统提示词越写越长工具列表越挂越多从查资料、写代码、做总结到发通知全让一个角色扛。头两周感觉挺爽一个入口解决所有问题。但项目稍微复杂一点问题就全冒出来了它开始人格分裂写代码的时候带着客服腔做总结的时候又漏掉关键约束工具调用经常选错明明该查知识库却去调了计算器最要命的是你没法给它做权限隔离一个能删数据的工具和一个只读查询的工具挂在同一个角色身上风险敞口大得吓人。Sub-Agents子智能体这套思路本质上就是把一个全能助手拆成一支分工明确的协作小队。每个子智能体只干一件事有自己的角色设定、自己的工具权限、自己的技能包彼此通过一个主控Orchestrator来调度。这跟现实里的团队管理是一个道理你不会让一个人同时当财务、法务和运维因为职责不清必然出事。角色分工带来的第一个好处是提示词可以极简——一个只负责从知识库检索并回答的子智能体它的系统提示可能就三五行比那种上千行的万能提示稳定得多。第二个好处是权限可以收窄。这是我在实际项目里踩过坑之后才真正重视起来的。早期我把文件写入、网络请求、数据库查询全挂在一个 Agent 上结果它在一次帮我整理资料的任务里顺手把临时文件写到了不该写的位置。拆成子智能体之后检索角色只有只读权限写入角色才拿写权限每个角色的能力边界清清楚楚。哪怕某个角色被诱导它能造成的破坏也被限制在它自己的权限范围内。第三个好处是技能可以复用和封装。Skills技能包这个概念说白了就是把一类任务的标准做法打包成一个可插拔的模块。比如检索增强问答这个技能包含固定的检索策略、固定的引用格式、固定的兜底话术任何需要这个能力的子智能体直接挂载即可不用每个角色重写一遍。这跟写代码时抽公共函数是一个思路——重复的逻辑抽出来改一处全局生效。我这次要跑通的目标很具体搭一套最小的 Sub-Agents 协作框架包含角色定义、权限隔离、技能包封装三个核心模块并且用一个 V1 版本的知识库把整条链路真正跑起来——不是画架构图是让一个真实的问题从主控分发、到子智能体检索、到技能包处理、最后返回带引用的答案全程可复现。下面我把这套东西从设计到落地的完整过程拆开讲包括我踩过的坑和最后稳定下来的做法。2. 角色、权限、技能包三个模块的职责边界到底怎么划在动手写代码之前我花了整整一个下午只做一件事把这三个概念的关系理清楚。因为如果边界划错了后面全是返工。我见过不少项目把权限直接写进角色的提示词里比如在系统提示里写你只能读取文件不能修改然后指望模型自觉遵守。这种做法在演示阶段看着没问题一旦遇到稍微复杂的诱导就会破防。权限必须是代码层面的硬约束不能是提示词层面的软约束这是我用血泪换来的第一条原则。2.1 角色Role定义我是谁、我负责什么角色回答的是身份和职责问题。一个角色定义通常包含四样东西角色名称、职责描述、可用的技能包列表、以及它被允许调用的工具白名单。注意这里我特意把工具白名单和权限分开——工具白名单是你能碰哪些工具权限是这些工具在底层能做什么。一个检索角色可以挂载知识库查询工具但这个工具在底层实现时只连只读接口这就是两层防护。角色的职责描述要写得窄而具体。我一开始写的是你是一个帮助用户解决问题的助手这种描述等于没写。后来改成你是一个知识库检索专员你的唯一任务是根据用户问题从指定知识库中检索最相关的三段内容并原样返回不做任何总结和改写效果立刻不一样。职责越窄行为越可预测。2.2 权限Permission代码层面的硬边界权限是这套架构里最容易被忽视、也最不能妥协的部分。我的做法是给每个工具在注册时就打上权限标签比如read_only、write、network、execute然后在子智能体初始化时只把符合它权限等级的工具注入进去。一个只有read_only权限的子智能体它的运行环境里根本不存在写入工具这个对象模型想调也调不到。这比在提示词里写一百遍不要写入都管用。这里有个细节值得说权限校验要放在工具执行层而不是调度层。也就是说即使主控因为某种 bug 把写入工具传给了只读角色工具在执行前也会再校验一次调用方的权限标签不匹配直接拒绝。双层校验听起来冗余但在实际运行中确实救过我一次——那次是配置热更新时角色和工具的映射表错位了靠底层校验兜住了。2.3 技能包Skills可插拔的能力封装技能包是我最喜欢的一个抽象。它把完成某类任务的标准流程封装成一个独立模块包含三部分技能描述给模型看的说明这个技能干什么、什么时候用、执行逻辑实际的代码或提示链、以及输入输出契约明确的参数格式和返回格式。一个子智能体可以挂载零个或多个技能包。举个具体的例子检索增强问答这个技能包它的执行逻辑是固定的先对用户问题做一次改写扩展然后并行检索多个片段再按相关度重排最后组装成带引用的上下文。这套流程如果让每个角色自己实现质量参差不齐封装成技能包之后所有需要它的角色行为一致而且优化一次全局受益。三者关系可以这样理解角色是容器技能包是装在容器里的能力模块权限是容器和模块共同遵守的边界规则。角色决定谁来干技能包决定怎么干权限决定能干到什么程度。理清这层关系之后代码结构就顺了。模块回答的问题实现位置是否可被提示词绕过角色 Role我是谁、负责什么配置 系统提示部分职责描述可被诱导权限 Permission我能碰什么代码层工具注入 执行校验否硬约束技能包 Skills这类任务怎么做独立模块 契约否逻辑固定3. 把 V1 知识库跑通一条完整链路的搭建实录架构想清楚之后我决定用一个最小但真实的知识库来验证整条链路。所谓 V1 知识库就是不追求花哨的向量检索和重排模型先用最朴素的方式把问题进、答案出、引用可追溯这条链路跑通。我的原则是先让链路通再让链路好。很多人一上来就堆最先进的检索方案结果链路本身没跑通调优无从下手。3.1 知识库的最小可用形态V1 知识库我用了最朴素的方案一批 Markdown 文档按段落切分每段生成一个向量存进本地向量库检索时用余弦相似度取 Top-K。切分策略上我做了个取舍——按语义段落切而不是按固定字数切。固定字数切分实现简单但经常把一句话拦腰截断检索出来的片段读起来莫名其妙。按段落切虽然片段长度不均但语义完整对后续的引用展示友好得多。文档入库的流程是这样的先遍历目录读取所有 Markdown 文件用空行和标题层级做切分每个片段保留来源文件名和段落序号作为元数据然后批量生成向量写入。这里有个坑批量生成向量时要注意速率限制我一开始一次性提交几百个片段直接被接口限流后来改成每批 20 个、批间加 200 毫秒延迟稳定跑完。元数据里的来源信息非常关键它是后面引用可追溯的基础千万别省。3.2 主控如何分发任务主控Orchestrator的职责是理解用户意图决定交给哪个子智能体。V1 阶段我没上复杂的路由模型用的是最直接的方式主控本身也是一个轻量智能体它的工具列表里只有调用某个子智能体这一种工具每个子智能体的描述就是它的路由依据。用户问题进来主控根据各子智能体的职责描述选择最合适的一个把任务转交过去。这种设计的好处是路由逻辑和业务逻辑彻底分离。主控不需要知道知识库怎么查、代码怎么写它只需要知道这个问题该找谁。我实测下来只要子智能体的职责描述写得够窄够清楚主控的路由准确率相当高。反过来如果子智能体职责描述含糊主控就会频繁选错这时候要改的是子智能体的描述而不是给主控加更多规则。3.3 检索子智能体与技能包的配合检索子智能体是这条链路的核心执行者。它挂载了检索增强问答技能包权限是read_only。当它收到主控转来的问题时技能包接管流程先改写问题把口语化的提问转成更适合检索的关键词组合然后调用知识库查询工具拿回 Top-K 片段再按相关度重排最后组装成带引用的上下文交给模型生成答案。这里有个我反复调试才稳定的细节引用格式必须在技能包里固定死。我要求模型在答案里用[来源:文件名#段落号]的格式标注每一处引用并且技能包在返回前会做一次校验如果答案里出现了没有对应来源的断言就打回重生成。这个校验机制让答案的可信度上了一个台阶用户能清楚看到每句话的出处。3.4 一次完整调用的时序还原我把一次真实调用拆开记录方便你对照复现。用户问这套框架里权限是怎么保证不被绕过的主控收到后判断这属于知识库问答范畴转交给检索子智能体。子智能体启动技能包问题被改写为权限 硬约束 绕过 工具注入 执行校验这组关键词。知识库查询工具返回三段最相关片段分别来自架构设计文档和权限模块说明。技能包重排后组装上下文模型生成答案每处断言都带上了来源标注。技能包校验通过答案返回主控主控原样透传给用户。整个过程从收到问题到返回答案本地环境实测在两秒左右。4. 权限隔离的实战细节那些提示词管不住的地方权限这块我想单独拎出来讲因为它是最容易做表面功夫、也最容易出事的地方。我见过太多项目在提示词里写满你不能做 X然后就没有然后了。下面是我在实际搭建中总结的几个关键做法每一条都对应一个真实的坑。4.1 工具注入而非工具声明最核心的一条子智能体不该知道自己没有权限的工具存在。早期我的做法是所有工具都注册在全局然后在每个角色的提示词里声明你只能用其中某几个。结果模型经常幻觉出它不该用的工具或者在被诱导时尝试调用。后来改成按角色动态注入——只读角色的运行环境里写入工具这个对象压根不存在模型连尝试的机会都没有。这个改动之后越权调用的问题直接归零。4.2 执行层的二次校验前面提过工具执行前会再校验一次调用方权限。这个二次校验的实现很简单每个工具执行函数的第一行检查传入的调用上下文里的权限标签是否匹配。不匹配就抛异常并记录日志。这层校验平时几乎不会触发但它是最后一道防线。我建议所有涉及写操作、网络请求、代码执行的工具都必须加这层校验成本极低收益极高。4.3 权限标签的设计粒度权限标签的粒度需要拿捏。太粗比如只有读和写不够用太细每个工具一个独立标签维护成本高。我最后定的是四个基础标签read_only、write、network、execute然后允许组合。比如一个需要联网查询的角色标签是read_only network。这个粒度覆盖了我遇到的所有场景既够用又不至于爆炸。标签定义要写进配置不要硬编码在代码里方便后续调整。注意权限标签的组合要遵循最小权限原则。一个角色如果只需要读本地文件就绝对不要给它network标签哪怕它可能以后会用到。以后用到的时候再加比一开始就给宽权限安全得多。4.4 日志与审计权限相关的操作必须留痕。我的做法是每次工具调用都记录调用方角色、工具名、权限标签、调用参数摘要、执行结果状态。这些日志在排查问题时价值巨大。有一次一个子智能体行为异常我翻日志发现它在短时间内高频调用了同一个查询工具定位到是技能包里的重试逻辑写错了导致无限重试。没有日志的话这种问题很难发现。5. 技能包封装的经验怎么让能力真正可复用技能包封装得好不好直接决定了这套架构能不能规模化。我一开始把技能包写成了一堆提示词的集合后来发现复用性很差因为不同角色对同一个技能的需求细节不一样。经过几轮重构我总结出一套让技能包真正可复用的做法。5.1 输入输出契约要严格技能包最容易被忽视的是契约。我要求每个技能包必须明确定义输入参数有哪些、每个参数的类型和含义、输出的结构是什么。比如检索增强问答技能包的输入是{question: string, top_k: int, require_citation: bool}输出是{answer: string, citations: list, confidence: float}。契约严格之后任何角色挂载这个技能包拿到的结果格式都一致下游处理逻辑不用为每个角色写适配。5.2 技能包内部要能容错技能包执行过程中会遇到各种意外检索返回空、模型生成超时、引用校验不通过。这些情况技能包内部要能处理而不是把异常抛给调用方。我的做法是给每个技能包定义明确的降级策略。比如检索返回空时技能包不直接报错而是返回一个未找到相关内容的标准响应并附带置信度 0。这样调用方拿到的是一个结构完整的响应处理逻辑统一。5.3 技能包的版本管理技能包一旦被多个角色依赖修改就要谨慎。我吃过一次亏优化了一个技能包的检索策略结果依赖它的三个角色行为全变了其中一个角色的输出格式因此不兼容下游。后来我给技能包加了版本号角色挂载时指定版本升级技能包时新版本并行存在角色按需切换。这个做法跟软件依赖管理是一个思路虽然增加了一点复杂度但避免了改一处崩一片。5.4 技能包与角色的解耦最后一条经验技能包不要假设自己运行在哪个角色里。我早期写的技能包里有if role retriever这种判断后来发现这完全破坏了复用性。正确的做法是技能包只关心自己的输入输出角色相关的差异通过参数传入。解耦之后同一个技能包可以被检索角色、总结角色、甚至代码角色复用只是传入的参数不同。6. 跑通之后回头看几个值得记住的判断整套东西跑通之后我回头复盘有几个判断是我觉得最值得分享的。第一个判断是先窄后宽。角色职责、权限范围、技能包功能一开始都要往窄了做遇到不够用再放宽。反过来先宽后窄你会发现到处都是历史包袱收不回来。我见过一个项目一开始给所有角色开了全权限后来想收紧发现几十处代码都依赖了宽权限改造成本极高。第二个判断是硬约束优先于软约束。凡是能用代码保证的绝不用提示词保证。权限、格式、流程这些都应该在代码层固化提示词只负责那些真正需要模型判断的部分。这个原则让我这套框架的稳定性比早期版本高了一个数量级。第三个判断是链路先通再优化。V1 知识库用最朴素的方案就是为了先让整条链路跑起来。链路通了之后你想换更好的检索方案、加更复杂的路由都是在稳固的地基上做增量。反过来如果链路本身没通就去堆高级特性最后往往是一堆零件拼不起来。我在实际使用中发现这套 Sub-Agents 架构最舒服的地方是它让加一个新能力变得非常简单。以前加功能要改那个万能 Agent 的提示词改完还要担心影响其他功能现在只需要定义一个新角色、挂载合适的技能包、配好权限主控自动就能路由过去。这种可扩展性是单 Agent 架构怎么优化都换不来的。如果你也在做类似的编排系统我的建议是从最小的三角色开始——一个主控、一个检索、一个处理先把链路跑通再逐步加角色和技能包别一上来就设计一个庞大的角色体系那样很容易在细节里迷路。