Eino ADK 中的 ChatModelAgent:不只是模型调用的封装
ChatModelAgent 不是简单的模型封装
很多人第一次接触 Eino ADK 的 ChatModelAgent,容易把它理解成“给大模型加个工具调用功能”。这种看法不算错,但远远不够。

实际上,ChatModelAgent 是 ADK 里预构建的默认思考型 Agent。它的核心价值不是“能调工具”,而是把“模型推理 + 工具执行 + 协作跳转 + 事件输出”这一整套复杂逻辑,封装成一个可运行、可扩展、可监控的标准单元。

换句话说,它解决的是:当一个任务不能靠一次模型调用完成时,系统该怎么组织这个多轮决策过程。

内部是一个 ReAct 循环
ChatModelAgent 的执行逻辑基于经典的 ReAct(Reason + Act)模式:
- 调用大模型,让它判断下一步该做什么;
- 如果模型直接给出答案,流程结束;
- 如果模型决定调用工具,系统就执行该工具;
- 把工具返回的结果作为新观察(Observation)喂回模型;
- 模型基于新信息再次决策,循环往复;
- 直到模型不再需要工具,或达到最大迭代次数。
这个循环意味着,ChatModelAgent 天然支持将复杂任务拆解为多步推理。而如果你没配任何工具,它就会退化为一次普通的聊天模型调用——这说明循环与否,取决于你是否赋予它“行动”的能力。
为了避免无限循环,框架设定了 MaxIterations(默认 20 次)。超过这个阈值还没结束,Agent 会主动报错退出。这在生产环境中至关重要,能防止因 prompt 设计不当或模型犹豫不决导致的资源浪费。
关键配置项解析
Name 与 Description
这两个字段常被忽视,但在多 Agent 协作中极其关键。Name 是 Agent 的唯一标识,Description 则是其他 Agent 判断“是否该把任务转交给你”的依据。写得越具体、越聚焦,协作时被选中的准确性就越高。
Instruction 与 Model
Instruction 定义行为边界(比如“用中文回答”“先结论后依据”),Model 决定能力底座(比如 qwen-plus 或 gpt-4o)。前者管“怎么做”,后者管“能不能做”。
ToolsConfig 及其扩展字段
这是拉开 ChatModelAgent 与普通模型调用差距的核心。
- ReturnDirectly:某些工具一旦成功调用,结果就直接作为最终输出,不再送回模型润色。适合那些结果本身就是终态的场景,比如生成工单、触发审批或转人工。
- EmitInternalEvents:当你把另一个 Agent 包装成 Tool 使用时,开启此选项可以让内层 Agent 的事件流透传出来,方便前端实时展示内部进展。
OutputKey
把 Agent 最终输出的消息存入 SessionValues,并指定一个 key。后续的 Workflow 或其他 Agent 可以直接通过这个 key 获取结果,避免手动传递字符串。
Exit
这是一个特殊的内置工具。模型调用它并成功执行后,Agent 会立即退出,并将传入的内容作为最终结果。语义上比 ReturnDirectly 更明确——这是模型主动宣布“任务完成”。
ModelRetryConfig
处理模型调用失败的重试策略。尤其在流式响应场景下,如果中途出错,调用方会收到 WillRetryError,提示即将重试而非彻底失败。这对构建健壮的流式交互体验很关键。
Tool、Transfer、AgentAsTool 怎么选?
这三者都涉及“把事情交给别人”,但语义完全不同。
- 普通 Tool:像函数调用,输入输出明确,边界清晰。例如查错误码、算时间、调 HTTP 接口。
- Transfer:把任务控制权完全移交。当前 Agent 判断另一个 Agent 更适合处理,于是触发跳转。Runner 收到 Transfer 事件后,会切换到目标 Agent 继续执行。适合职责分明的多专家协作场景。
- AgentAsTool:把另一个 Agent 当成“高级工具”来用。它不需要完整上下文,只要一个请求参数就能独立工作。调用方仍保留主控权,只是借用了子 Agent 的能力。
简单记:Tool 是调函数,Transfer 是交控制权,AgentAsTool 是把 Agent 当函数用。
Handler:工程化的真正分水岭
如果说 Tool 决定了 Agent “能做什么”,那 Handler 决定了它“在真实系统里怎么被管理”。
ADK 提供了多个扩展钩子:
- BeforeAgent:运行前修改本次执行的配置,比如动态追加 Instruction、按租户加载工具、临时标记某个工具为
ReturnDirectly。 - Before/AfterModelRewriteState:拦截发给模型的消息历史,适合做裁剪、脱敏或格式校验。
- WrapModel:包装模型调用本身,用于统一日志、指标采集、审计等。
- WrapInvokableToolCall / WrapStreamableToolCall:拦截工具调用,记录入参、耗时,或对结果做二次处理。
值得注意的是,新代码更推荐使用 ChatModelAgentMiddleware 接口风格的 Handler,而非旧的 struct 风格。前者更灵活,支持动态行为和上下文改写。
实战:搭建一个故障分诊助手
我们用 ChatModelAgent 实现一个简单的运维助手,它能:
- 根据服务名和错误码查询预置的故障预案(runbook);
- 在信息不足或风险较高时,直接生成交接单转人工;
- 通过自定义 Handler 统一添加运行约束和工具日志。
核心设计
search_runbook是普通 Tool,模型先查事实再组织答案;handoff_to_human配置了ReturnDirectly: true,一旦调用就立即结束流程;- 自定义
OpsGuardHandler在BeforeAgent中追加指令:“始终中文回复,先结论后依据,风险高时优先转人工”;同时在WrapInvokableToolCall中打印工具调用日志。
运行效果
当输入 payment 服务出现 DB_TIMEOUT,连接池已满 时,模型会先调用 search_runbook 获取预案,再综合信息给出排查建议。
但如果输入 payment 服务持续报错,但我只有一句日志:DB_TIMEOUT,模型可能判断信息不足,直接调用 handoff_to_human,生成工单并退出——整个过程由 Agent 自主决策,无需外部干预。

这个例子展示了 ChatModelAgent 的真正能力:它不只是说话,而是会决定下一步该做什么。
小结
ChatModelAgent 的本质,是 Eino ADK 对“思考型 Agent”的标准化实现。它通过 ReAct 循环支持多步推理,通过 Transfer 和 AgentAsTool 支持协作,通过 Handler 支持工程扩展,最终以 AgentEvent 的形式输出全过程。
理解它,关键不是记住 API,而是建立一种认知:在复杂任务中,Agent 应该是一个能自主决策、动态调整、并与系统深度集成的智能单元,而不是一个只会生成文本的黑盒。