Skip to content
Go
ChatModelAgent(ReAct Agent)

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