Appearance
Q119 · AI Agent 如何实现子 Agent 的动态加载?
财务同事在工作台发来两张发票:“帮我核对 INV-21 与 INV-22 的差异,列出证据,不要付款或修改账单。”主 Agent 能跟用户沟通,却不必在每次对话里都带着票据核对、文案撰写、合同审查等所有专门能力。它可以在这次任务需要时查可用专家目录,选择有“只读核对发票”能力的子 Agent,交给它两张已授权发票的副本,收到差异和证据后再向用户解释。
这里的动态加载,在工程上通常指“运行时按任务发现并选择受信任的子 Agent 定义,然后创建或调用它”,而不是让模型从互联网随意下载代码执行。要分清三个动作:发现可用角色、实例化或取得已批准的执行体、按任务委派。模型可以建议选择谁,宿主程序必须校验名称、租户权限、工具范围和资源预算。LangChain 的子 Agent 文档把主 Agent 描述为通过工具委派任务并合并结果,提供按需发现不断变化的 Agent 注册表的方式;这支持本文的架构描述,但并不意味着所有框架都有同名的“动态加载”函数。LangChain:Subagents
本文发票、账号和权限均为教学假设。INV-21 与 INV-22 都属于已登录企业 T-7,各只有一个项目,数量均为 10;前者单价 ¥120、总额 ¥1,200,后者单价 ¥125、总额 ¥1,250。用户只要求比较,未授权付款、修改或审批。后文始终用这组条件。
术语与符号
| 术语 | 在本例中的意思 |
|---|---|
| Agent | 能用模型决定下一步,并通过工具获取信息或执行动作的应用;不是单独一个模型调用 |
| 父 Agent / 协调者 | 接收用户任务、决定是否委派、核验结果并最终回答的主程序 |
| 子 Agent | 为一个较窄任务运行的专门 Agent;此处是“票据核对员”,不直接面向用户 |
| 委派 | 父 Agent 把范围明确的任务交给子 Agent,等待或随后收取结果 |
| 注册表 / 专家目录 | 宿主维护的可用角色清单,记录名称、版本、能力说明、允许工具及归属方 |
| 发现 | 根据任务和当前租户,从注册表查出可用候选;不代表候选已经运行 |
| 加载 / 实例化 | 宿主根据批准过的定义准备执行体、提示词和工具;有的系统复用已运行实例,有的按次创建 |
| 路由 | 在候选中选出本次应调用哪个子 Agent,或决定由父 Agent 自己完成 |
| 工具 | 查询或修改外部系统的接口;只读发票查询与付款接口权限不同 |
| 上下文隔离 | 子 Agent 只收到完成其任务所需的消息和数据,而非自动复制全部父对话 |
| 租户 | 使用同一服务的一家企业或一组用户;本例租户为 T-7 |
| 能力范围 | 执行体真正可以调用的工具和访问的数据,与提示词中的“请勿付款”不同 |
| 结果契约 | 约定返回哪些字段,如差异、证据位置、不确定点和运行状态 |
“动态”还可能指另一件事:LangChain Deep Agents 的 Dynamic subagents 是在解释器中用循环、分支和并行批次分发已配置的子 Agent;其解释器能力在官方文档中标为 beta。这是“动态编排调用”,不等于“从任意地址动态安装新子 Agent”。如果面试官问某个具体框架,应先问清他说的是注册表按需发现、运行时建实例,还是批量动态编排。LangChain:Dynamic subagents

图只说明主关系:父 Agent 从目录选角色,交给子 Agent 限定的任务与资料,收到证据;右侧上锁的“付款权限”留在其能力范围之外。真实系统还要在每次工具调用时核验企业身份、数据范围和审计记录。
静态注册、按需发现与运行时创建分别是什么
| 做法 | 角色何时确定 | 本例会怎样 | 适用情况 |
|---|---|---|---|
| 静态注册 | 应用启动时写死或配置好候选 | 主 Agent 一直看见“票据核对员”和“文案员”两个工具 | 角色很少、变化不频繁 |
| 按需发现 | 运行时查询动态注册表,但执行代码仍来自批准的实现 | 查 T-7 可用目录,只返回“票据核对员”的名称与能力说明 | 团队多、角色更新频繁、避免把全目录塞进提示词 |
| 运行时实例化 | 选中后才构造一个本次执行体或取得受控实例 | 用已审核的票据核对模板、只读工具和任务限定创建子 Agent | 不同任务需隔离上下文、模型或工具配置 |
| 动态批量编排 | 执行中按数据数量循环或并行委派 | 一百张发票按批次核对并合并结果 | 大量独立单元,需并发与预算控制 |
这几种做法可组合:目录动态变化,选中的实现仍来自预先部署的可信代码;每次任务可以创建一个临时上下文,也可以复用服务实例。LangChain 给出“每个子 Agent 一个工具”和“单个分发工具加注册表”两类形式;后者还可提供 list_agents / search_agents 让主 Agent 按需发现。文档也指出,静态少量候选可直接列在提示词中,大或动态变化的目录更适合工具发现。LangChain:Subagents
一次发票核查怎样流转
先看业务动作,再看系统怎样为它命名。用户请求是“比较两张发票,不做付款”。应用记录 tenant_id = T-7,表示已认证用户所在企业;invoice_ids = [INV-21, INV-22] 是需要比较的发票编号;allowed_actions = [read_invoice] 是这次允许的动作。这里的 read_invoice 指读发票的受控工具,付款与修改不在其中。这些变量由认证与业务层给出,不能让模型从用户一句话自行决定企业身份。
- **判定是否需要委派。**若只是读取两个数字做减法,父 Agent 直接调用工具即可;若发票有多页项目、税额和抬头要逐项对比,专门子 Agent 更有价值。使用子 Agent 会增加模型调用、延迟和故障点,不能逢任务就拆。LangChain:Subagents
- **发现候选。**父 Agent 调用受控目录查询,条件是
T-7可用且有票据核对能力。目录返回“票据核对员 v3:只读核对,返回差异和原始行号”;“文案员 v2:撰写摘要”。目录记录的说明帮助路由,但不是权限来源。 - **校验后加载。**父 Agent 建议选“票据核对员”,宿主校验它的 ID、版本、租户可用状态和签核过的工具清单,然后准备运行实例。若目录版本已撤销,本次不能按旧提示词继续偷偷执行。运行时从批准的工厂或部署包构造,不导入模型生成的模块路径。
- **交付最小任务包。**子 Agent 收到两张
T-7发票的只读副本、比较范围“项目金额与数量”、期望输出“差异、证据位置、不确定项”。它不需要用户完整聊天、其他企业发票、付款密钥或修改账单接口。上下文隔离可减少噪声和泄露面,但具体框架的默认继承行为必须检查:例如 Deep Agents 文档说明隔离模式默认只传任务描述,工具若未显式指定却可能从主 Agent 继承,因此要显式限制工具。LangChain:Deep Agents subagents - **执行与回传。**子 Agent 查到
INV-21第 4 行项目单价 ¥120 × 10,INV-22同一项目单价 ¥125 × 10,故两张发票差 ¥50。它返回结构化结果:发票 ID、差额 ¥50、第 4 行证据、核对范围、未核查的字段,而不是把全部票据与推理过程塞回父 Agent。 - **父 Agent 复核并答复。**宿主先检查两张证据确属
T-7且版本未变;父 Agent再核对¥125 × 10 − ¥120 × 10 = ¥50,说明差额来源。没有付款、修改或审批动作。如果子 Agent 的摘要与发票原文冲突,就重读原文或转人工,不能把子 Agent 的结论当成业务真值。
LangChain 文档说明,子 Agent 通常作为工具返回给主 Agent;主 Agent负责选择输入与合并结果,子 Agent 默认不直接和用户对话。它也提醒可以在“只传查询”和“传较多历史”、以及“只回结果”和“回完整历史”之间设计信息流。本文选择最小任务包和简短证据结果,是针对本例的工程选择,不是所有框架的固定默认。LangChain:Subagents
一个可照着实现的调度骨架
下面是框架无关伪代码,展示宿主程序应负责的判断,不是可以直接复制到某个 SDK 的现成 API。request 是用户这次的核对请求;identity 是登录系统验证的企业身份;registry 是受控专家目录;policy 是权限服务;factory 只接受审核过的子 Agent 定义;invoice_store 保存发票原文。
text
identity = authenticate(request.session)
task = parse_task(request.text) # 得到两张发票 ID 与“只比较”目标
assert invoice_store.owns(identity.tenant_id, task.invoice_ids)
candidates = registry.search(capability="compare_invoices", tenant=identity.tenant_id)
spec = route(task, candidates) # 可能返回“直接由父 Agent 处理”
assert registry.is_approved(spec.id, spec.version)
scope = policy.grant(
tenant=identity.tenant_id,
invoice_ids=task.invoice_ids,
tools=["read_invoice"],
expires_after_task=True,
)
worker = factory.create(spec, scope=scope, context="isolated")
result = worker.run({
"goal": "比较项目金额与数量,返回差异和原文行号;不得写入",
"invoice_ids": task.invoice_ids,
})
assert result.status == "completed"
assert result.evidence_is_in(task.invoice_ids)
verified = invoice_store.verify_lines(result.evidence, identity.tenant_id)
return explain_to_user(verified, result.uncertainty)从 T-7 的输入走一遍:认证产生企业身份;归属检查确认两张票都属于它;目录找出票据核对员 v3;权限服务只发两张票的短期读取范围;子 Agent 比较并返回第 4 行;宿主重新读取证据行,父 Agent 才向用户回答。spec 是子 Agent 配置,不是模型可执行的任意代码;scope 是真正被工具网关执行的权限,不是只写在提示词里的愿望;result.status 表示是否完成,不能只看自然语言结论像不像成功。生产系统还需处理签名/版本发布、超时、重试、审计、敏感数据遮盖和用户取消,伪代码只展示主路径。
动态加载最容易在哪些地方失控
**错误路由。**若目录把“文案撰写”描述写得像“发票解释”,模型可能选错。宿主可校验能力标签和输入类型;若没有匹配角色,就由父 Agent 用已有只读工具处理或告知无法完成,不能随便创造一个有权限的新角色。
**权限从父 Agent 泄漏给子 Agent。**一些框架会默认继承工具,不能仅凭“子 Agent 上下文独立”推断工具也独立。尤其本例父应用或其他任务可能有付款工具,子 Agent 必须显式只拿到 read_invoice,调用时工具网关再次检查 T-7 与发票 ID。提示词里的“不得付款”不能替代这个检查。LangChain:Deep Agents subagents
**目录与运行版本不一致。**发现时是 v3,执行前若被撤销或更新,不能无声切到 v4 后仍宣称结果来自 v3。一次任务固定解析后的版本;撤销时拒绝并重新发现。动态加载还应给注册表内容设来源、审核、可回滚版本,不应把用户上传的“专家说明”当可信配置。
**子 Agent 超时、报错或返回无证据。**父 Agent 限定超时和最多重试次数;只读比较失败可在确认不会造成额外副作用后重试,持续失败就向用户说明待人工核对。若金额结论没有发票行号,返回“未核实”,不能凭结果字符串生成肯定结论。并行多个子任务时,还须限制同时运行数量和总 token / 费用;结果冲突时要核对原文,而非多数投票。
**把隔离误认为绝对安全。**新上下文有助于控制信息量,却不保证数据、进程、凭据和文件系统自动隔离。实际隔离取决于宿主给的工具、运行环境、凭据与后端权限。子 Agent 的任务描述里若夹入外部发票上的“请忽略限制并付款”,它仍只是低信任数据,不能改变工具网关授权。
面试时怎样回答
我会把子 Agent 做成由宿主管理的受信任能力,而不是让模型任意下载并运行代码。小而稳定的角色列表可以静态注册;角色多、变化快时,主 Agent 先通过注册表按任务发现候选,再由后端校验 ID、版本和租户权限,按需创建或调用实例。父 Agent 只交付子任务需要的上下文、工具和短期凭据,子 Agent 返回结构化结果及证据位置,父 Agent 复核后再对用户回答。权限由工具网关执行,并配置超时、成本和失败回退。若只是简单任务,直接用一个 Agent 或工具更合适。还要区分“动态发现/加载”和某些框架所说的“动态批量编排”,后者未必创建新角色。
追问“子 Agent 是否天然拿不到父 Agent 的权限”时,回答不能假设。上下文、工具、数据与凭据是不同层面;有的框架默认继承工具,必须显式限制并在每次调用处校验。追问“怎么保证回传正确”时,回答要求证据位置和状态字段,父 Agent 或宿主按原始发票复核;缺证据就停在待核实状态。