把一个通用智能体直接丢给业务方用,大概率会翻车。这东西我们团队试过不止一次,要么答非所问,要么权限混乱碰到什么信什么,要么上下文拖到几千行之后生成质量肉眼可见地崩坏。痛定思痛,我们才把单体智能体彻底拆掉,重构为多代理分工的结构,也才有了现在这套 Sub-Agents 角色分工、Skills 能力封装、再加上 V1 知识库跑通的完整方案。
这篇文章不聊空泛理论,主要记录我们在实际项目里怎么拆角色、设权限、封装技能,以及第一版知识库从零到能用的整个落地过程。如果你正在做智能体应用,或者准备把一个演示级别的 Agent 推向真实场景,这篇文章应该能帮你少踩不少坑。
1. 项目整体设计:从单体 Agent 到分工协作的 Sub-Agents
1.1 单体 Agent 的瓶颈在哪里
先说我们为什么一定要拆。单体 Agent 最典型的问题有三个,几乎绕不开。
第一是上下文污染。业务方希望一个 Agent 同时做意图理解、信息检索、内容生成、格式规范检查,所有工具和知识都堆在同一套上下文里。用户问题进来之后,系统要把几十个工具描述、几百条知识片段、历史对话全部塞给模型,上下文迅速逼近窗口上限,生成质量自然下降,Token 消耗也居高不下。
第二是权限不可控。所有工具对一个 Agent 开放,意味着只要有条件触发,模型就可以调任何接口。这在内部 Demo 里无所谓,一旦接真实业务,轻则拿错数据,重则出现越权操作。我们那时候出过一次事故:客服场景里一个简单问题,模型自己调了订单修改接口,给用户改错了地址,还好及时发现回滚了。从那以后,权限拆分优先级直接拉满。
第三是排错像大海捞针。一个单体 Agent 出了问题,你不知道是 Prompt 写得不对,还是工具返回的格式有问题,还是模型本身理解错了。整条链路耦合在一起,日志糊成一团,复现一次问题要半天。
Sub-Agents 的思路其实很简单,把一个人干十件事的模式,改成十个人各干一件事、由一个组长统一调度的模式。每个子代理只负责一个职责域,只持有完成该职责所需的工具和知识权限,上下文干净、职责清晰、权限边界可控。组长负责路由、汇总和决策。这样一来,复杂任务被拆碎,单点出问题也能快速定位到具体某个子代理。
1.2 子代理怎么拆:职责最小化
拆子代理的时候,我们的原则是“职责最小化”。一个子代理不是“能干什么”,而是“只干什么”。定义职责时,先想清楚用户与系统交互中到底有哪些不可再拆的动作单元。
以我们实际做的某企业内部知识问答与任务处理系统为例(下称内部系统),最终拆出了四类子代理:
- 意图识别代理:判断用户问题属于“知识查询”“任务处理”还是“闲聊闲聊”,同时抽取关键实体。
- 检索代理:只负责从知识库检索相关信息,不做任何生成和回答。
- 生成代理:负责组织语言、写作、格式转换,但没有直接访问知识库的权限,只能拿到检索结果。
- 质检代理:对输出做合规检查、格式校验、敏感信息过滤,并决定是否直接放行还是退回重写。
为什么这么拆?因为这几个环节的失效模式完全不一样。意图识别错,改 Prompt 和分类逻辑;检索不准,调切片和检索参数;生成不流畅,换模型版本或修改生成 Prompt;质检漏判,大概率是规则覆盖不足。每个子代理独立测试、独立评估、独立迭代。如果还是单体,改一个意图边界就得重新回归十几轮生成效果,成本太高。
1.3 编排模式选择:网关式串行流水线
多代理系统常见的协作模式有两种,一种是让所有子代理自由对话、动态协商,另一种是用中央调度器做任务编排。前者看起来灵活,实际项目里很容易失控,代理之间来回绕圈,Token 消耗爆炸,最后谁也不知道输出是怎么出来的。
我们最终选的是网关式串行流水线:主控代理扮演“网关”角色,接收用户请求,根据意图将任务派发给对应子代理,子代理之间不直接通信,统一通过主控代理传递数据。这个模式有点像一个项目组长:他分活、收活、汇总,成员之间不需要互相打招呼。优点是链路清晰、每步都有迹可循、权限控制容易落地。缺点是主控可能成为瓶颈,但只要请求量没到每秒几千级,完全够用。
V1 阶段我们用这个模式跑通了全链路,后面再考虑更复杂的并行调度。
2. 角色定义与权限控制:不是加个名字就算分工
2.1 角色定义落地:配置化优于硬编码
角色这东西,如果只在系统里写死几个类名,后期改动非常痛苦。我们一开始就定了配置化路线,角色的定义全部放在独立配置文件中,代码只负责解析和执行。每个角色由六个字段描述:
- role_id:角色唯一标识,对应子代理实例。
- name:角色名称。
- description:角色的职责描述,给调度器做路由依据。
- allowed_tools:该角色可以调用的技能包列表。
- allowed_kb:该角色可以访问的知识库范围。
- guardrails:针对该角色的输出约束与红线规则。
配置文件示例大致这样:
roles: - role_id: intent_agent name: "意图识别代理" description: "负责判断用户意图,抽取关键实体,不执行任何工具调用" allowed_tools: [] allowed_kb: [] guardrails: - "只输出意图标签与实体,不输出其他解释" - "无法判断时输出 unknown,不得猜测" - role_id: retrieval_agent name: "知识检索代理" description: "负责从指定知识库检索相关信息,不做内容生成" allowed_tools: - "kb_search_v1" allowed_kb: - "product_docs_v1" - "faq_v1" guardrails: - "不基于检索结果做推理,只返回原始命中片段" - "检索为空时明确返回 no_result"这个配置最终会被加载成角色对象,注入到对应的子代理运行时。好处是权限在配置层面就清晰可见,做安全评审的时候不用翻代码,直接看这张表即可。
2.2 权限控制的关键:工具级加知识级双重隔离
权限控制在项目里是两个维度同时推进的,缺一个都会出问题。
第一维度是工具权限隔离。每个技能包在注册进系统的时候,必须声明自己需要的权限级别,我们分为 read-only、write、admin 三个级别。子代理的 allowed_tools 只能在技能包声明的权限范围内被授予。这是一个很笨但很有效的办法:即使调度器因为模型幻觉错误地把某个子代理派给了不该用的工具,配置层也会拒绝执行,而不会把错误传导到业务层。
实际出现过这种情况:模型在路由时不知道为什么认为“用户想改订单状态”,于是把写操作工具传给了检索代理,检索代理本身没有 write 权限,调用被拦截,直接返回权限不足。这个设计帮我们挡下了至少三次因为模型幻觉导致的越权操作。
第二维度是知识库范围隔离。我们的内部系统有产品知识库、故障排查知识库、内部流程知识库三类,其中流程库只对特定角色开放。实现方式是每个检索技能包在创建时绑定数据源标识,运行时如果子代理的角色不在该数据源的白名单内,直接拒绝启动检索任务。这样即使子代理被恶意或者错误地派发检索任务,也拿不到超范围的知识。
2.3 角色权限自我进化:观察与收敛
权限设置不是一劳永逸,实际跑起来之后几乎每周都要微调。V1 阶段我们的收敛策略是:记录每个子代理的调用成功率、越权尝试次数和返工次数。如果一个子代理频繁因为缺少某个技能而无法完成任务,说明技能分配不足;如果一个子代理频繁尝试调用权限外的工具,说明职责边界定义已经和实际需求背离了。
这些数据汇总成一张权限复盘表,每周拿到会上讨论一次。前两周调整最频繁,后面逐渐稳定。没有这一步,权限设计很容易从一个极端走到另一个极端,要么给多了失控,要么给少了流程走不通。
3. Skills 能力封装:把工具变成可复用的技能包
3.1 技能包的结构设计
Skills 是子代理对外能力的封装单元。我们不直接让子代理调函数,而是把所有能力包装成结构化的“技能包”,统一暴露给调度层。一个技能包包含四层:
- 技能描述:一句话说明这个技能做什么、什么时候用、什么时候不用。
- 输入模式:定义调用该技能必须提供的参数及其格式。
- 执行逻辑:真实的代码实现,比如检索逻辑、计算逻辑、数据转换逻辑。
- 输出模式:定义返回值结构,以及失败时的错误码约定。
这样做和直接调函数的差别很大。函数是一堆散装能力,模型需要在 Prompt 中理解所有函数签名;技能包则是一个高内聚的单元,描述、参数、执行逻辑封装在一起,模型只需要理解“这个技能包解决什么问题”,而不需要关心内部怎么实现。对多代理系统来说,技能包也是给子代理分配权限的基本单元,没有这个抽象层级,权限就只能控制到函数级别,细碎且难管理。
3.2 一个技能包的完整实现示例:知识库检索
以我们系统里的 kb_search_v1 技能包为例,它承载了“从指定知识库检索相关内容”的能力。输入参数包括:
- query:查询问题文本
- kb_id:目标知识库标识
- top_k:返回命中条数
- threshold:相似度阈值
这段伪代码可以清晰表达整个技能包的执行逻辑:
class KbSearchSkill: name = "kb_search_v1" description = "从指定知识库中检索与查询最相关的片段,返回原始文本,不进行任何推理或加工" input_schema = { "query": {"type": "string", "required": True, "description": "用户原始问题"}, "kb_id": {"type": "string", "required": True, "description": "目标知识库ID"}, "top_k": {"type": "int", "required": False, "default": 5}, "threshold": {"type": "float", "required": False, "default": 0.35} } def execute(self, params): # 1. 权限校验:检查当前调用方的角色是否允许访问该kb_id if not permission_check(params["kb_id"]): return {"error_code": "403", "message": "当前角色无权访问该知识库"} # 2. 向量化查询 query_vector = embed(params["query"]) # 3. 相似度检索 hits = vector_db.search( kb_id=params["kb_id"], vector=query_vector, top_k=params["top_k"] ) # 4. 阈值过滤 filtered = [h for h in hits if h.score >= params["threshold"]] if not filtered: return {"error_code": "200", "data": [], "message": "no_result"} return {"error_code": "200", "data": filtered}执行逻辑里,“不进行推理或加工”这句话特别关键。很多 Agent 出 bug 是因为检索代理拿到检索结果后顺手开始“发挥”,把片段改写、补全,导致生成代理拿到的已经不是原始知识。技能包的原则就是:技能只做它声称的事,绝不越界。
3.3 技能包的注册与统一路由
所有技能包在系统启动时通过注册中心加载,注册中心保存技能包元数据,并向调度器暴露“技能目录”。调度器根据子代理角色的 allowed_tools 为该角色生成“可见技能包列表”,模型只能在这个列表内选择技能调用。
这里有一个细节值得提:技能包描述的质量直接影响模型路由准确率。我们一开始把描述写得很学术,“提供基于向量空间模型的语义检索功能”,结果时不时路由错。后来改成大白话,“在内部知识库里搜索相关内容,输入自然语言问题,输出相关片段列表”,路由准确率立刻上来了。经验是技能描述越接近用户的自然表达,模型越容易理解,不需要端着。
技能包封装到这一步,子代理不再是一堆 Prompt 拼出来的“虚拟身份”,而是有真实能力边界的执行单元。能力从哪来、能做什么、不能做什么,都写在技能包的元数据里。整个系统的基础也就夯实了。
4. V1 知识库从零到跑通:数据、切片、向量化与检索
4.1 数据准备:先解决“知识从哪来”
V1 知识库听上去很高级,实质上第一步是脏活累活:把散落各处的数据汇总、清洗、格式化。我们的数据来源包括产品说明文档、历史故障排查记录、内部 FAQ、工单回复模板。这些内容格式千差万别,有 PDF、Word、Markdown、甚至扫描件。
清洗阶段做了三件事:统一转成 Markdown 或纯文本,去除页眉页脚和重复段落,把表格转成结构化的 Markdown 表格。这一步自动化脚本可以做,但必须人工抽检。我们的经验是至少每 50 个文档抽 1 个做质量检查,重点看有没有乱码、有没有错位、有没有因为 OCR 识别产生的奇怪符号。数据质量不过关,后面检索效果怎么调都不可能好,因为问题出在源头。
在明确背景下,我们做了个简单分类,把清洗后的数据按业务域拆成三个知识库。每个库的文档总量不太多,V1 阶段控制在相对小规模,够验证链路即可。
4.2 切片策略:不是越短越好
知识库检索效果最核心的参数就是切片大小与重叠长度。切太短,语义不完整,检索到的片段缺乏上下文;切太长,混入无关内容,相似度计算被稀释,命中精度下降。
V1 阶段我们采用的策略是:按语义段落切分,设定 max_chunk_size 为 800 字符,overlap 为 120 字符。800 这个数字不是拍脑袋,是综合测试得到的。我们选了几十条真实问答做评测,分别用 400、800、1200、1500 试了一轮,800 在“命中准确率”和“片段可用度”两个指标上综合表现最好。overlap 的作用是防止关键信息刚好落在切片边界被截断,120 字符大约是一到两句话的长度,实测能把边界损失率降低一半以上。
切片之后还要做一步清洗:过滤掉纯标题、纯导航、无意义的时间戳等噪音片段。这些片段如果不滤掉,会持续命中并被作为“知识”提交给生成代理,直接影响输出可信度。
4.3 向量化与存储选型
V1 没有自研向量索引,我们用的是开源向量数据库,配合现成的 Embedding 模型完成向量化。Embedding 模型选择了中文场景下效果稳定的通用型模型,维度不算高,检索速度对 V1 数据量来说完全够用。
入库流程其实很简单:读取切片 -> 清洗过滤 -> 调用 Embedding 接口生成向量 -> 将向量和原文、文档 ID、业务标签一起写入向量库。每个切片在库里既存向量,也存原文,检索时返回原文。为什么一定要存原文?因为向量本身不可读,生成代理需要的是原始文本,不是一串数字。很多新手做 RAG 时只存向量不存原文,检索出来一堆 NaN,完全没法用。
此外,我们给每个知识库建了独立的集合(collection),方便做权限隔离与独立更新。升级某一个知识库的数据不会影响其他库,排查问题的时候也能精确定位到是哪个库的数据有问题。
4.4 检索链路与阈值调优
V1 的检索链路是:向量检索召回到 top_k=20,然后做相似度阈值过滤,阈值设为 0.35。低于阈值的片段直接丢弃。这里 top_k 和最终给到生成代理的片段数不是同一个概念。top_k=20 是召回量,过滤后我们只取前 5 条给生成代理。多召回、精过滤,这是一个比较稳的组合。
阈值 0.35 是怎么定的?我们跑了一批标注数据,统计了“相关问题的相似度分布”和“不相关问题的相似度分布”,两条分布曲线有明显分离带,带的中线大约在 0.35 附近。于是以 0.35 为基准,再根据线上反馈微调。这个值不是万能的,不同领域、不同 Embedding 模型差异很大,一定要基于自己的数据去标定。
检索链路还有一个大坑:query 的写法。用户问题直接拿去向量化,效果往往不够好,因为口语问题里包含太多语气词和指代。V1 阶段我们做了一个很轻量的 query 改写:先由意图识别代理抽取核心实体和问题主干,拼成一个更紧凑的检索语句,再调用检索技能。跑通后实测检索命中率提升了大约十几个百分点,代价很小,收益很明显。
5. 全链路实操过程:从配置到跑通
5.1 项目结构搭建
我们用一个标准的多代理项目结构来组织工程目录,核心目录大致如下:
project_root/ ├── agents/ │ ├── intent_agent.py │ ├── retrieval_agent.py │ ├── generation_agent.py │ └── quality_agent.py ├── skills/ │ ├── kb_search_v1.py │ ├── intent_classify_v1.py │ └── format_check_v1.py ├── knowledge/ │ ├── raw_docs/ │ ├── processed_docs/ │ └── vector_store/ ├── configs/ │ ├── roles.yaml │ ├── skills.yaml │ └── knowledge_bases.yaml ├── orchestrator/ │ └── main_router.py └── logs/这个结构的好处是职责一目了然。子代理代码只关注自己的运行逻辑,技能包独立在 skills 目录下,知识库数据与代码完全分离。后续扩展新的子代理、新增技能包、更新知识库,都互不干扰。
5.2 子代理与主控路由的配置联动
主控路由的核心逻辑是这样的:接收用户问题后,先走意图识别代理,拿到意图标签和实体;意图是“知识查询”就派发给检索代理,检索代理调用知识库技能包,随后将检索到的原始片段交给生成代理;生成代理基于片段组织回答,最后质检代理做合规检查。
我们用一段伪代码描述这条链路:
def main_pipeline(user_input): # 第一步:意图识别子代理 intent_result = agents["intent_agent"].run(user_input) if intent_result.intent == "knowledge_query": # 第二步:检索子代理调用知识库技能包 search_params = { "query": intent_result.rewritten_query, "kb_id": intent_result.target_kb, "top_k": 5, "threshold": 0.35 } search_result = agents["retrieval_agent"].run(search_params) # 第三步:生成子代理基于检索结果回答 generation_input = { "question": user_input, "context": search_result.data } answer = agents["generation_agent"].run(generation_input) # 第四步:质检子代理检查输出 final_answer = agents["quality_agent"].run(answer) return final_answer # 其他意图走对应分支这个流程最关键的一点是,每一步子代理的输入输出结构都是明确的。子代理之间不共享完整对话历史,只传递结构化结果。这样编排层完全可控,任何一步出错都能立刻定位到具体环节。调试的时候,只需要看传递的数据结构是否符合预期就可以了。
5.3 数据准备与入库实操
入库的实操流程我们写成了一套脚本,大致分三步:加载、切片、向量化入库。
数据加载阶段,首先扫描 raw_docs 目录,根据文件扩展名选择解析器。Markdown 和纯文本直接读取,PDF 调解析库提取文本,扫描件走 OCR 工具识别。提取后的文本统一转成标准 Markdown 格式,再做去重和清洗。
切片阶段,我们实现了一个简单但足够用的语义段落切分器:先按标题层级把文档拆成章节,再按空行拆成段落,最后把短段落合并到相邻段落,直到达到目标 chunk_size。示例代码如下:
def split_document(text, max_chunk=800, overlap=120): sections = split_by_heading(text) chunks = [] for section in sections: paragraphs = split_by_blank_line(section) current_chunk = "" for para in paragraphs: if len(current_chunk) + len(para) <= max_chunk: current_chunk += para + "\n" else: if current_chunk: chunks.append(current_chunk) # 保留尾部overlap长度的内容作为下一个chunk的开头 tail = current_chunk[-overlap:] if len(current_chunk) > overlap else "" current_chunk = tail + "\n" + para if current_chunk: chunks.append(current_chunk) return chunks向量化入库用现成 Embedding 接口批量处理,每批 64 条,入库后自动建立索引。整个 V1 知识库的处理时间在可接受范围内,跑完会输出一份入库统计,包括库内切片总数、向量维度、平均长度。这些元数据对后续调参很有用,建议入库时保留下来。
5.4 首次全链路联调
所有组件接好后,首次联调阶段我们准备了一批测试用例,覆盖五大类:正常知识查询、模糊意图、越权访问尝试、知识库无答案、闲聊问题。每类测试用例十条左右,通过一条条跑并记录结果。
首跑遇到的问题比想象中多得多。第一个坑是意图识别代理把“怎么查库存”判断成了“任务处理”而不是“知识查询”,导致走了错误的流程分支。原因是我们自己的意图分类 Prompt 对“查询类”动词覆盖不够,加了几十条示例后收敛。第二个坑是质检代理的合规规则太严格,把包含数字的答案全部标记为“疑似敏感信息”,导致大量正常回答被退回。调整规则定义,明确增加“只检查真实敏感内容”的限定词后解决。
这个阶段最需要耐心。多代理系统的联调不是一次成功的事,而是不断暴露问题、不断局部修正的过程。我们从首跑到全链路稳定,大约花了一周时间。每天迭代的是某一两个环节的配置和 Prompt,而不是大面积重构代码——这也是配置化带来的最大红利。
5.5 日志与可观测性建设
V1 阶段我们把“过程可回放”作为可观测性的底线。每条请求从进入系统到返回,都会生成一条会话日志,包含:用户原始输入、每个子代理的输入和输出、技能包调用记录、知识库命中的片段 ID 及相似度分数。日志统一以 JSON 行格式落盘,方便后续分析。
这个日志体系帮我们解决了很多问题。比如生成代理输出质量差,打开日志一看,检索代理传过去的片段是一条完全无关的内容,问题根本不在生成,而在检索。再比如有次用户反馈回答“过于自信”,日志里发现是生成代理在没有检索结果的情况下,仍然编造了一段貌似合理的答案。后来我们在生成代理的 Prompt 里强约束:“没有检索结果时必须如实回答不知道”,这个问题才被堵住。
多代理系统的调试难点在于问题可能发生在任何一环,没有日志回放能力,基本只能靠猜。所以日志平台建议在 V1 就跑起来,哪怕粗糙一些,也比事后补要省力得多。
6. 常见问题与排查技巧实录
6.1 高频问题速查表
我们把 V1 阶段遇到的高频问题整理成了表格,方便对照排查:
| 问题现象 | 大概率原因 | 排查方法与解决方向 |
|---|---|---|
| 子代理答非所问 | 路由错误:意图识别代理把用户意图判错 | 查看会话日志中意图输出标签,补充分类示例,或调整描述 |
| 检索结果明显无关 | 切片过长/过短、query 改写不充分 | 检查命中的切片文本与相似度分数,调整切片策略与查询写法 |
| 回答引用了不存在的知识 | 检索为空但生成代理强行作答 | 为检索代理增加“无结果时返回 no_result”约束,生成代理绑定此项约束 |
| 子代理频繁调用权限外工具 | 角色 allowed_tools 配置过宽或职责边界模糊 | 缩减工具白名单,或拆分更细粒度的角色 |
| 越权访问行为仍然发生 | 配置层权限校验有遗漏 | 检查技能包执行入口是否有独立权限校验,不能只靠调度层 |
| 多次返工、时长超过预期 | 主控调度策略僵化,子代理间数据往返过多 | 精简流程,将可并行步骤改为并行,或将简单任务直通处理 |
这张表不是万能的,但基本覆盖了多代理系统最常见的六类问题形态。如果你也是第一次搭这类系统,建议把这个表打印出来贴在工位旁边,排查的时候可以先对着过一遍。
6.2 独门排查心法:从数据流反推异常
多代理系统的调试,我们用下来最有效的方法,是“数据流反推法”。也就是说,出了问题绝不先猜模型或者猜 Prompt,而是先把整条链路上的数据结构打开,一步步往回看:质检代理收到的输入是什么?生成代理收到的上下文是什么?检索代理返回的片段是什么?意图识别代理输出的标签是什么?
哪种场景下返工率最高?是意图识别代理输出一个“模糊意图”仍硬走知识查询分支的时候。后来我们在路由逻辑里加了一道判断:意图置信度低于 0.5 时,不走完整链路,而是直接向用户追问澄清。这个改动看似简单,却把整个系统的返工率降低了一半。多代理系统里,最先解决的一定是最前面的环节,入口不准,后面所有环节都在白费力气,返工率居高不下。
另一个容易被忽略的排查点是时间维度。我们曾经遇到一个问题:早上系统表现正常,下午回答质量明显下降。排查日志发现,问题并不是某个环节坏了,而是知识库在下午有过一次数据更新,新切片的 embedding 分布和原来不一致,导致检索相似度整体偏移。从那以后,我们定了一个规矩:知识库更新后必须跑一遍回归测试集,测试通过才能上线。这个规矩一直沿用到 V1 结束,帮我们挡了几次数据更新引入的回退。
6.3 如何判断 V1 跑通:验收标准
什么时候算“跑通”?我们的验收标准有四条:
- 第一,核心测试集通过率不低于 90%。这包括知识查询的正确率、意图识别的准确率、越权访问拦截率。
- 第二,全链路单次平均时延在可接受范围内,这里的瓶颈主要在向量检索与生成阶段,通过缓存热点问题可以进一步压缩。
- 第三,日志回放能完整还原任意一条请求的处理过程,这一步是可观测性的底线。
- 第四,连续一周线上试运行没有出现越权访问和严重错误回答。
我们在内部系统上按这四个标准验收,通过后 V1 才正式定版。这里有一个很现实的心态建议:第一版不用追求极致准确率,而是要把链路走通、指标监控建好、问题定位机制跑顺。只要能做到这三点,V1 就已经完成了它的历史使命。
最后再分享两个小技巧
第一个是给每个技能包加独立的调参开关。比如 kb_search_v1 的 threshold、top_k,我们全部参数化,放在配置里,不需要改代码就能调整。这个开关在初期调参时太重要了,能让你十分钟内测完一组参数,而不是每次都要改代码重新部署。建议所有技能包都朝这个方向设计。
第二个是定期做“角色权限复盘”。我们每周看一次各子代理的工具调用记录和越权尝试记录。哪怕系统表现稳定,也坚持做这个复盘。有一次复盘发现生成代理开始频繁试探调用知识库更新工具,虽然没有权限,但这说明生成代理的职责边界可能已经模糊了。及时调整它的 Prompt 和技能可见范围,避免了一个潜在隐患。权限这种东西,摆在那里可以不用,但边界不能不清楚。
这套 Sub-Agents 角色分工加 Skills 能力封装的骨架,我们一直沿用了很久,后面无论加新业务、新场景,都是往这个骨架上添技能包、加角色、扩展知识库。V2 的方向我已经想好了:把主控路由从串行升级成支持条件分支和并行调用,同时把技能包升级成可远程注册的插件式架构。但那是后话了,先把 V1 踩扎实,多代理系统这条路才算真正走通了。