ChatModelAgent(ReAct Agent)
ChatModelAgent = ChatModel + Tools + ReAct Loop + Middleware
它是 Eino ADK 最核心的 Agent 实现,以 LLM 为决策中枢,通过 ReAct(Reason + Act)循环自主推进问题求解。配置 Tools 后自动进入 ReAct 模式;未配置 Tools 则退化为单次 ChatModel 调用。
ReAct Loop 工作流
用户输入 → [Reason: 调用模型] → 模型判断
├── 无需调用工具 → 输出最终回复 ✓
└── 需要调用工具 → [Act: 执行 Tool]
→ [Observe: 结果注入上下文]
→ 回到 Reason ↻每个循环检查 MaxIterations(默认 20),超出则报错退出。
快速创建
agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "intelligent_assistant",
Description: "An intelligent assistant capable of using tools",
Instruction: "You are a professional assistant.",
Model: openaiModel,
ToolsConfig: adk.ToolsConfig{
Tools: []tool.BaseTool{searchTool, calculatorTool, weatherTool},
},
MaxIterations: 20,
})
runner := adk.NewRunner(ctx, adk.RunnerConfig{Agent: agent})
iter := runner.Query(ctx, "What's the weather in Beijing?")配置全集 — ChatModelAgentConfig
type ChatModelAgentConfig struct {
Name string // Agent 名称(用作 AgentTool 时必填)
Description string // 能力描述(用作 AgentTool 时必填)
Instruction string // System Prompt,支持 {key} 占位符
Model model.BaseChatModel // 必填。需支持 model.WithTools(用于 Tools)
ToolsConfig ToolsConfig // 工具配置
GenModelInput func(...) []*schema.Message // 默认:Instruction 作为 System Msg
Exit tool.BaseTool // NOT RECOMMENDED
OutputKey string // NOT RECOMMENDED
MaxIterations int // 默认 20
// Middleware(推荐用 Handlers)
Handlers []ChatModelAgentMiddleware // interface-based(推荐)
Middlewares []AgentMiddleware // struct-based(Deprecated)
// 模型容错
ModelRetryConfig *ModelRetryConfig
ModelFailoverConfig *ModelFailoverConfig
}ToolsConfig
type ToolsConfig struct {
compose.ToolsNodeConfig
ReturnDirectly map[string]bool // 命中后不回调模型,直接返回
EmitInternalEvents bool // 透传子 Agent 事件到父 Agent 事件流
}Middleware — AOP 拦截器
Middleware 是 ChatModelAgent 的拦截器,在 ReAct Loop 的生命周期点位上注入行为。不改变循环结构,只需在特定点拦截和增强。
解决的问题
| 需求 | Middleware |
|---|---|
| Agent 需要读写文件、执行命令? | FileSystem |
| Agent 需要复用预定义指令和知识? | Skill |
| 上下文超长,超出模型窗口? | Reduction / Summarization |
| 工具太多,塞进 prompt 稀释注意力? | ToolSearch |
| 模型调用不稳定? | ModelRetry / ModelFailover |
Middleware 接口
type ChatModelAgentMiddleware interface {
// —— 生命周期 Hook ——
BeforeAgent(ctx, runCtx *ChatModelAgentContext) (context.Context, *ChatModelAgentContext, error)
AfterAgent(ctx, state *ChatModelAgentState) (context.Context, error)
BeforeModelRewriteState(ctx, state *ChatModelAgentState, mc *ModelContext) (context.Context, *ChatModelAgentState, error)
AfterModelRewriteState(ctx, state *ChatModelAgentState, mc *ModelContext) (context.Context, *ChatModelAgentState, error)
// —— Wrapper ——
WrapModel(ctx, m model.BaseChatModel, mc *ModelContext) (model.BaseChatModel, error)
WrapInvokableToolCall(ctx, endpoint InvokableToolCallEndpoint, tCtx *ToolContext) (InvokableToolCallEndpoint, error)
WrapStreamableToolCall(ctx, endpoint StreamableToolCallEndpoint, tCtx *ToolContext) (StreamableToolCallEndpoint, error)
// + EnhancedToolCall 变体...
}💡 嵌入
*BaseChatModelAgentMiddleware可只覆盖需要的方法,无需实现全部。
钩子点位全景
| 钩子 | 时机 | 可修改 | 典型用途 |
|---|---|---|---|
BeforeAgent |
Agent 运行前(仅一次) | Instruction、Tools、ReturnDirectly | 注入工具 / 增强指令 |
AfterAgent |
Agent 成功结束后 | 只读最终 state | 后处理、状态清理 |
BeforeModelRewriteState |
每次模型调用前 | Messages、ToolInfos、DeferredToolInfos | 压缩历史 / 过滤工具 |
AfterModelRewriteState |
每次模型调用后 | Messages(含模型响应) | 修改响应 / 修补状态 |
WrapModel |
单次模型调用包装 | 重试、failover(不修改 Messages) | 重试 / 故障切换 |
WrapToolCall |
单次工具调用包装 | 权限检查、日志、输出改写 | 权限 / 安全 |
🔬
BeforeModelRewriteState返回的 state 被框架持久化到 Agent 内部状态,修改会跨所有后续迭代保持。
执行顺序
模型调用链(外到内 → 内到外):
BeforeModelRewriteState →
failoverModelWrapper →
retryModelWrapper →
eventSenderModelWrapper →
WrapModel(先注册先执行)→
callbackInjection →
Model.Generate / Stream
← callbackInjection
← WrapModel(先注册后执行)
← eventSenderModelWrapper
← retryModelWrapper
← failoverModelWrapper
← AfterModelRewriteState工具调用链(外到内):
eventSenderToolHandler →
ToolsConfig.ToolCallMiddlewares →
cancelMonitoredToolHandler →
WrapToolCall(先注册最外层)→
callbackInjected →
Tool.InvokableRun / StreamableRun内置 Middleware 总览
| Middleware | 功能 |
|---|---|
| Reduction | 超长工具输出截断 / 写文件系统,防 token 超限 |
| Summarization | 历史消息摘要压缩 |
| Skill | 可复用指令 / 知识以 Tool 形式暴露,Agent 按需加载 |
| FileSystem | ls / read / write / edit / glob / grep / execute 文件工具集 |
| ToolSearch | tool_search 元工具,按需搜索加载工具 |
| PatchToolCall | 修补消息历史中悬空的工具调用(缺失工具结果) |
| SafeTool | 拦截工具执行错误,转为可读文本返回模型,Agent 可自行修正 |
| ModelRetry | 模型调用失败按策略重试 |
| ModelFailover | 模型调用失败切换备用模型 |
| AgentsMD | 将 Agents.md 知识文件注入模型上下文 |
| PlanTask | 持久化任务管理工具集(create/get/update/list) |
| WriteTodos | 轻量级 TODO 列表工具 [DeepAgent 内置] |
| TaskTool | 子 Agent 委派工具 [DeepAgent 内置] |
| Permission | 工具调用权限控制 [WIP] |
上下文类型速查
ChatModelAgentContext(BeforeAgent 参数)
type ChatModelAgentContext struct {
Instruction string // 当前指令
Tools []tool.BaseTool // 工具列表
ReturnDirectly map[string]bool // 直接返回的工具名集合
ToolSearchTool *schema.ToolInfo // 模型原生工具搜索能力
}ChatModelAgentState(BeforeModel / AfterModel 参数)
type ChatModelAgentState struct {
Messages []*schema.Message // 所有消息
ToolInfos []*schema.ToolInfo // 传给模型的工具定义
DeferredToolInfos []*schema.ToolInfo // 延迟检索工具
}ModelContext(WrapModel / 模型前后参数)
type ModelContext struct {
Tools []*schema.ToolInfo // Deprecated:用 State.ToolInfos
ModelRetryConfig *ModelRetryConfig
ModelFailoverConfig *ModelFailoverConfig
}ToolContext(WrapToolCall 元数据)
type ToolContext struct {
Name string // 工具名称
CallID string // 本次调用唯一标识
}自定义 Middleware 示例
type MyMiddleware struct {
*adk.BaseChatModelAgentMiddleware // 嵌入,只覆盖需要的方法
}
// 运行前注入额外指令
func (m *MyMiddleware) BeforeAgent(ctx context.Context, runCtx *adk.ChatModelAgentContext) (context.Context, *adk.ChatModelAgentContext, error) {
runCtx.Instruction += "\n必须在回答结束注明"以上信息仅供参考""
return ctx, runCtx, nil
}
// 每次模型调用前裁剪历史(防 token 超限)
func (m *MyMiddleware) BeforeModelRewriteState(ctx context.Context, state *adk.ChatModelAgentState, mc *adk.ModelContext) (context.Context, *adk.ChatModelAgentState, error) {
if len(state.Messages) > 30 {
// 保留 system + 最近 20 条
state.Messages = append(state.Messages[:1], state.Messages[len(state.Messages)-20:]...)
}
return ctx, state, nil
}
// 注册
agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: model,
Handlers: []adk.ChatModelAgentMiddleware{&MyMiddleware{}},
})ModelRetry — 模型调用重试
agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: model,
ModelRetryConfig: &adk.ModelRetryConfig{
MaxRetries: 2,
IsRetryAble: func(ctx context.Context, err error) bool {
return isTransientError(err)
},
},
})流式场景:错误发生时,当前流仍通过 AgentEvent 返回,消费 MessageStream 时收到 *WillRetryError。
stream := event.Output.MessageOutput.MessageStream
for {
msg, err := stream.Recv()
if err == io.EOF { break }
if err != nil {
var willRetry *adk.WillRetryError
if errors.As(err, &willRetry) {
log.Printf("Retry attempt %d...", willRetry.RetryAttempt)
break // 等待下一个事件
}
break
}
}ModelFailover — 模型故障切换
agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: primaryModel,
ModelFailoverConfig: &adk.ModelFailoverConfig{
MaxRetries: 1,
ShouldFailover: func(ctx context.Context, msg *schema.Message, err error) bool {
return !errors.Is(err, context.Canceled)
},
GetFailoverModel: func(ctx context.Context, fc *adk.FailoverContext) (model.BaseChatModel, []*schema.Message, error) {
return fallbackModel, nil, nil // nil → 沿用原始输入
},
},
})与 Retry 组合:每个模型先按 Retry 策略重试,重试耗尽后触发 Failover 切换。执行链路:failoverModelWrapper → retryModelWrapper → 实际模型调用。
流式 Failover 行为
| 场景 | 行为 |
|---|---|
Stream() 初始化失败 |
直接触发 failover 判定 |
| 流中途出错 | 已接收 chunk 拼入 LastOutputMessage,决定 failover 后关闭当前流,用新模型重启 |
| 客户端主动放弃 | ErrStreamCanceled 不触发 failover |
| 失败尝试中已发送的事件 | 不会被撤回,客户端应按元数据去重或重置部分结果 |
Cancel — 运行时取消(v0.9+)
cancelOpt, cancelFn := adk.WithCancel()
iter := runner.Run(ctx, messages, cancelOpt)
handle := cancelFn(adk.CancelAfterChatModel | adk.CancelAfterToolCalls)
handle.Wait() // 等待取消完成CancelMode 支持位掩码组合,控制在哪个节点后取消。
RunLocal — 运行时本地存储
在当前 Run() 期间存取键值对。值与中断 / 恢复兼容,随 checkpoint 持久化。
adk.SetRunLocalValue(ctx, "key", value)
val, ok, _ := adk.GetRunLocalValue(ctx, "key")
adk.DeleteRunLocalValue(ctx, "key")🚨 自定义类型须在
init()中通过schema.RegisterName[T]()注册,确保 gob 序列化正确。
func init() {
schema.RegisterName[*ToolStats]("mypackage.ToolStats")
}SendEvent — 发送自定义事件
adk.SendEvent(ctx, &adk.AgentEvent{
Output: &adk.AgentOutput{CustomizedOutput: myData},
})仅限 Middleware 回调内调用,调用方遍历事件流时可收到。
常见陷阱
| 陷阱 | 说明 |
|---|---|
| 🚨 WrapModel 中修改 Messages | 修改不会被持久化,应使用 BeforeModelRewriteState |
| 🚨 MaxIterations 过小 | ReAct 循环提前终止,复杂任务执行不完 |
| 🚨 ReturnDirectly 误配置 | 工具执行后直接返回,不再回调模型修正结果 |
| 🚨 流式 Failover 重复事件 | 失败尝试中已发送的事件不撤回,客户端需自行去重 |
| 🚨 Tools 过多且无 ToolSearch | 全部塞入 prompt 稀释模型注意力,推荐配合 ToolSearch Middleware |