第 5 章(下):产品中的自定义 Agent 与 Tool
我的产品需要解决什么问题
前两节讲了 Tool 怎么与 LLM 交互、Agent 是什么。这一节讲我的产品里,自定义 Agent 和自定义 Tool 是怎么设计的。
我的产品(Growth AIOS)有 100+ 个工作流节点。用户可以用自然语言描述需求,Agent 自动生成工作流。
这个"自动生成"不是一步完成的,而是分成了多个步骤——需求解析、控制骨架设计、节点选型、参数填充、Canvas 构建、注册。
每个步骤需要的知识、工具、判断逻辑完全不同。一个 Agent 做不完。
自定义 SubAgent:不同类型的 Agent 做不同的事
不同场景需要不同的 Agent。但怎么管理这些 Agent?
csharp
// SubAgent 提供者接口
public interface IAgentSubAgentProvider
{
// 获取所有已注册的 SubAgent 配置
IReadOnlyList<SubAgentConfig> GetRegisteredAgents();
// 执行指定的 SubAgent
Task<SubAgentResult> ExecuteAsync(
SubAgentConfig config, string prompt, CancellationToken cancellationToken = default);
}每个 SubAgent 有自己的配置:
- AgentType:类型标识(如 "WorkflowPlanner"、"CodeRunner")
- SystemPrompt:这个 Agent 的系统提示词
- ModelId:使用哪个模型(不同任务可以用不同模型)
- Tools:这个 Agent 可用的工具集
同样是 Provider 可覆盖模式——核心层注册默认空实现,业务层按需注册。

自定义 Lead-Sub Agent 分层:让 Agent 能“编排”
有了多个 SubAgent,需要一个"指挥官"来编排它们。
这就是 LeadAgent 的角色:
用户消息
↓
LeadAgent(理解意图 + 拆分任务)
├── SubAgent A(执行子任务 1)
├── SubAgent B(执行子任务 2)
└── SubAgent C(执行子任务 3)
↓
LeadAgent 汇总结果 → 返回给用户LeadAgent 的核心工具是 TaskTool——它让 LeadAgent 可以把任务"派发"给 SubAgent。
csharp
// LeadAgent 的工具构建
private IList<AITool> BuildLeadAgentTools(string sessionId)
{
// 业务工具(由 Integration 层提供)
var domainTools = _toolProvider.GetToolsForLeadAgent()
.Select(t => WrapTool(t.ToAITool(), sessionId)).ToList();
// TaskTool 是唯一内置工具(用于派发 SubAgent)
domainTools.Add(WrapTool(_taskTool.ToAITool(), sessionId));
return domainTools;
}注意 TaskTool 是唯一内置的工具——其他工具都由业务层提供。这说明"派发任务给 SubAgent"是 LeadAgent 的核心能力,不是业务功能。
真实案例:用 Lead Agent + Sub Agent 创建工作流
这是产品设计思路最集中的体现。
用户说:"帮我创建一个工作流,读取图片并裁剪。"
这不是一步完成的。LeadAgent 把任务拆成 3 个阶段,每个阶段派发给不同的 SubAgent:
用户需求:"读取图片并裁剪"
↓
LeadAgent 理解意图 → 派发给 SubAgent 1
↓
SubAgent 1: 需求解析 Agent
├── System Prompt: "你是一个需求分析专家,从用户描述中提取操作对象、动作、流程特征"
├── Tool: 无(纯 LLM 理解)
├── 输出: 结构化需求(操作对象=图片, 动作=读取+裁剪, 流程=串行)
└── 用户确认: ✅ / 💬调整 / ⬆回退
↓
SubAgent 2: 控制骨架 Agent
├── System Prompt: "你是一个流程架构师,决定需要哪些控制节点(循环/条件/批处理)"
├── Tool: 无(控制节点是固定的)
├── 输出: 控制骨架(串行:读取→裁剪,无循环无条件)
└── 用户确认: ✅ / 💬调整 / ⬆回退
↓
SubAgent 3: 节点选型 Agent
├── System Prompt: "你是一个节点选型专家,从 100+ 节点中选出最合适的"
├── Tool: search_nodes(搜索节点), get_node_detail(查看节点详情)
├── 输出: 节点选型方案(读取=FileReadNode, 裁剪=ImageCropNode)
└── 用户确认: ✅ / 💬调整 / ⬆回退
↓
代码执行阶段(零 AI,纯代码自动执行)
├── Step 2: PlanEnricher → 查 form.json → 填默认值 → 建引用链
├── Step 3: CanvasBuilder → 生成 Canvas JSON → 结构校验
└── Step 4: Register → 注册到工作流引擎 → 返回 DefinitionId注意三个 SubAgent 的设计差异:
| SubAgent | 需要什么知识 | 需要什么工具 | 用什么模型 |
|---|---|---|---|
| 需求解析 | 理解自然语言 | 无(纯理解) | 强理解力模型 |
| 控制骨架 | 知道有哪些控制节点 | 无(控制节点固定) | 中等模型 |
| 节点选型 | 知道 100+ 节点的功能 | search_nodes + get_node_detail | 需要工具调用能力 |
每个 SubAgent 是一个独立的角色封装——自己的知识、自己的工具、自己的模型。LeadAgent 负责编排它们,但不做具体工作。
对话式迭代:不是线性流水线
这个流程不是"跑完就结束"的线性流水线。每一步都支持用户反馈:
SubAgent 输出结果 → 展示给用户
├── 用户说"合理" → 保存结果,前进到下一步
├── 用户说"调整:不是裁剪,是压缩" → SubAgent 吸收反馈,重新生成
└── 用户说"需求理解错了,回到上一步" → 回退,级联清除下游数据每步可以循环多轮,直到用户满意。这就是 Agentic Conversation Loop——AI 逐步生成、用户逐轮反馈、持续迭代直至满意。
关键设计:LLM 只做决策,代码做转换
注意 AI 阶段(3 个 SubAgent)和代码阶段(3 步)的分工:
- LLM 做:语义理解、拓扑设计、节点选型(需要判断力的决策)
- LLM 不做:拼 JSON、填默认值、建引用链、生成 Canvas(确定性转换)
- 代码做:查 form.json → 填充默认值 → 匹配参数 → 建立数据流引用 → 生成 Canvas JSON
这个分工很重要——LLM 擅长决策,不擅长精确转换。 让 LLM 拼 JSON 一定会出错,但让代码做确定性转换永远不会出错。
LeadAgent 的完整调用链
LeadAgent 的一次调用,涉及整个 Agent 系统的协作:
csharp
public async Task<ChatResponse> ChatAsync(ChatRequest request, CancellationToken cancellationToken)
{
// 1. 确保会话存在
var (sessionId, _) = await EnsureSessionAndTraceAsync(request);
// 2. 解析模型
var modelInstanceId = ResolveModelInstanceId(request);
// 3. 构建系统指令(含 SubAgent 描述)
var instructions = _leadAgentPrompt.BuildInstructions(_subAgentProvider);
// 4. 构建工具(业务工具 + TaskTool)
var tools = BuildLeadAgentTools(sessionId);
// 5. 通过工厂创建 Agent(内部自动挂载所有 Provider + 装饰器)
var agent = await _agentFactory.CreateLeadAgentAsync(
sessionId, modelInstanceId, instructions, tools, cancellationToken);
// 6. 创建 Session + 设置 StateBag
var agentSession = await agent.CreateSessionAsync(sessionId, cancellationToken);
agentSession.StateBag.SetValue(MemoryFactsContextProvider.SessionIdKey,
new SessionIdWrapper { Value = sessionId });
// 7. 执行(框架自动处理完整生命周期)
var agentResponse = await agent.RunAsync(
request.Message, session: agentSession, cancellationToken: cancellationToken);
// 8. 持久化 + 标题生成
StoreMessage(sessionId, "user", request.Message);
StoreMessage(sessionId, "assistant", agentResponse.Text ?? "");
EnsureTitle(sessionId, request.Message);
return new ChatResponse { SessionId = sessionId, Content = agentResponse.Text, IsSuccess = true };
}关键在第 5 步和第 7 步:
- 第 5 步:
AgentFactory内部自动挂载所有AIContextProvider(记忆、监控、标题、Skill)和装饰器管道(重试、日志、压缩) - 第 7 步:
agent.RunAsync()自动执行完整生命周期——加载上下文 → 调用 LLM → 工具调用循环 → 返回结果
LeadAgentManager 本身是一个薄编排层——它不做具体工作,只负责把各个组件串起来。
工具的可观测性包装
每个工具调用都被包装了一层"可观测性":
csharp
private AITool WrapTool(AITool tool, string sessionId)
{
if (_agentEventBus != null && _eventBuffer != null && tool is AIFunction aiFunc)
{
return new ObservingAIFunction(aiFunc, _agentEventBus, _eventBuffer, sessionId);
}
return tool;
}ObservingAIFunction 在工具调用前后发布事件:
- ToolCalledEvent:工具开始执行
- ToolCompletedEvent:工具执行完成(含耗时、结果)
这让监控系统能追踪每个工具的使用情况——哪个工具用得最多、哪个工具最慢、哪个工具经常失败。
SubAgent 的执行隔离
每个 SubAgent 有自己独立的上下文:
- 自己的系统提示词
- 自己的工具集
- 自己的模型选择
- 自己的执行环境
SubAgent 之间不共享状态——它们通过 LeadAgent 间接通信。
LeadAgent
├── SubAgent A(独立上下文)
├── SubAgent B(独立上下文)
└── SubAgent C(独立上下文)这种设计的好处是隔离性——一个 SubAgent 出问题不会影响其他 SubAgent。坏处是开销——每个 SubAgent 都要独立调用 LLM。
业界怎么做的
同样的"自定义 Agent + 多 Agent 编排"问题,其他框架是怎么解决的?
自定义 Tool 的注册方式
LangGraph 的做法是"工具绑定到节点"——每个图节点可以有自己的工具集,Agent 在某个节点上只能看到该节点绑定的工具。好处是工具权限控制精确,坏处是工具不能跨节点共享。
微软 Agent Framework (MAF) 的做法是通过 ChatOptions.Tools 传入工具——工具在每次调用时注入,不是在 Agent 构造时写死。这意味着同一个 Agent 在不同场景下可以看到不同的工具集。MAF 的 FunctionInvokingChatClient 自动处理工具调用循环。
Hermes 的做法是"工具自动发现"——新增一个工具,Hermes 自动让所有 Agent 知道它的存在。不需要手动注册。这比我的"Provider 注册"更自动化,但灵活性差一些——你无法控制"哪个 Agent 看到哪些工具"。
多 Agent 编排方式
LangGraph 的多 Agent 通过"子图"实现——一个节点可以是一个完整的子图,子图内部有自己的 Agent 和工具。好处是可视化、可控;坏处是灵活性受限于图结构,加一个新 Agent 就要改图。
AgentScope Java 的做法是"Pipeline + 消息传递"——Agent 之间通过消息管道通信。每个 Agent 是一个"处理器",接收消息、处理、输出。多 Agent 编排通过 Pipeline 组合实现(串行、并行、条件分支)。好处是解耦彻底;坏处是调试困难——消息在管道里流转,出错了不好定位。
我的设计是 Lead-Sub 分层——LeadAgent 通过 TaskTool 派发任务给 SubAgent,SubAgent 执行完返回结果。LeadAgent 是唯一有“派发权”的 Agent,SubAgent 之间不直接通信。
对比总结
| 框架 | Tool 注册 | 多 Agent 编排 | 特点 |
|---|---|---|---|
| LangGraph | 节点绑定工具 | 子图 | 可视化、可控,但灵活性差 |
| MAF | ChatOptions.Tools 动态注入 | Agent 组合 + 装饰器管道 | 最接近我的设计 |
| AgentScope | 处理器绑定 | Pipeline + 消息传递 | 解耦彻底,调试困难 |
| Hermes | 全局自动发现 | 无内置多 Agent | 自动化高,灵活性差 |
| 我的设计 | Provider 可覆盖 | Lead-Sub 分层 + TaskTool | 业务层按需注册,Agent 看不同工具集 |
关键认知
这个阶段做完,我有了第五个关键认知:
自定义 Agent 不是"重新写一个 Agent",是"在基座上组合能力"。
- Tool 是 Agent 的"手"——能操作外部世界
- SubAgent 是 Agent 的"分身"——每个分身有自己的专长
- LeadAgent 是 Agent 的"大脑"——理解意图、拆分任务、编排执行
三者组合在一起,就是 Agent 的"能力体系":
LeadAgent(大脑)
├── Tool(手)→ 操作文件、执行命令、调用 API
├── SubAgent(分身)→ 专业领域执行
└── TaskTool(调度)→ 把任务分配给分身但到这里,所有组件还是散的。记忆、检索、Skill、Rule、Tool、Agent——它们各自独立工作,缺少一个"全景图"把它们串起来。
这就是下一章要解决的问题:Harness 架构全景。