Eino ADK 中的 ChatModelAgent:不只是模型调用的封装

0 阅读

ChatModelAgent 不是简单的模型封装

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

文章配图

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

在这里插入图片描述

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

在这里插入图片描述

内部是一个 ReAct 循环

ChatModelAgent 的执行逻辑基于经典的 ReAct(Reason + Act)模式

  1. 调用大模型,让它判断下一步该做什么;
  2. 如果模型直接给出答案,流程结束;
  3. 如果模型决定调用工具,系统就执行该工具;
  4. 把工具返回的结果作为新观察(Observation)喂回模型;
  5. 模型基于新信息再次决策,循环往复;
  6. 直到模型不再需要工具,或达到最大迭代次数。

这个循环意味着,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 实现一个简单的运维助手,它能:

  1. 根据服务名和错误码查询预置的故障预案(runbook);
  2. 在信息不足或风险较高时,直接生成交接单转人工;
  3. 通过自定义 Handler 统一添加运行约束和工具日志。

核心设计

  • search_runbook 是普通 Tool,模型先查事实再组织答案;
  • handoff_to_human 配置了 ReturnDirectly: true,一旦调用就立即结束流程;
  • 自定义 OpsGuardHandlerBeforeAgent 中追加指令:“始终中文回复,先结论后依据,风险高时优先转人工”;同时在 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 应该是一个能自主决策、动态调整、并与系统深度集成的智能单元,而不是一个只会生成文本的黑盒。