1. 深入理解 ChatModelAgent 的核心架构
在 Eino ADK 体系中,ChatModelAgent 扮演着"思考型 Agent"的关键角色。与普通的大模型调用不同,它实现了完整的决策-执行循环机制,让 LLM 能够动态决定何时回答问题、何时调用工具、何时转交任务。
1.1 ReAct 循环的执行机制
ChatModelAgent 的核心是基于 ReAct(Reasoning and Acting)模式的循环执行引擎。这个循环包含四个关键阶段:
- 推理阶段(Reason):模型分析当前上下文,决定下一步行动
- 决策阶段(Action):模型选择具体行动(回答、调用工具或转交)
- 执行阶段(Act):系统执行选定的动作
- 观察阶段(Observation):将执行结果反馈给模型进行下一轮判断
这种机制使得 ChatModelAgent 能够处理复杂的多步骤任务,而不仅仅是生成单次响应。例如,在处理故障排查请求时,Agent 可能会:
- 先调用 runbook 查询工具获取技术文档
- 然后根据文档内容组织回答
- 如果问题复杂则转交人工处理
1.2 关键配置参数解析
ChatModelAgent 的行为由一组精细的配置参数控制,这些参数可以分为几个重要类别:
身份标识类
Name:Agent 的唯一标识符Description:用于任务转交时的匹配依据
模型控制类
Instruction:定义 Agent 的行为准则和响应风格Model:底层使用的 LLM 实例ModelRetryConfig:模型调用失败时的重试策略
工具管理类
ToolsConfig:工具调用相关配置ReturnDirectly:指定哪些工具的结果直接作为最终输出EmitInternalEvents:控制是否透传嵌套 Agent 的事件
流程控制类
MaxIterations:限制最大循环次数(默认20)OutputKey:指定结果存储的会话键名Exit:定义特殊退出工具
2. ChatModelAgent 的三种协作模式
2.1 普通工具调用(Tool)
这是最基本的协作方式,适用于边界清晰的功能单元。工具需要明确定义:
- 输入参数结构
- 输出结果格式
- 功能描述(供模型判断是否调用)
典型实现示例:
go复制type CalculatorInput struct {
A float64 `json:"a"`
B float64 `json:"b"`
Op string `json:"op" jsonschema:"enum=+,enum=-,enum=*,enum=/"`
}
calculatorTool := utils.InferTool("calculator", "执行基础数学运算",
func(ctx context.Context, input *CalculatorInput) (float64, error) {
switch input.Op {
case "+": return input.A + input.B, nil
case "-": return input.A - input.B, nil
case "*": return input.A * input.B, nil
case "/":
if input.B == 0 {
return 0, errors.New("division by zero")
}
return input.A / input.B, nil
default: return 0, errors.New("unknown operator")
}
})
2.2 任务转交(Transfer)
Transfer 机制允许 Agent 将整个任务控制权移交给更适合处理的 Agent。这种模式下:
- 主 Agent 评估任务性质
- 根据子 Agent 的 Description 选择最佳接手者
- 通过 Transfer Tool 发起转交
- Runner 切换执行上下文到目标 Agent
配置示例:
go复制dispatcher := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "dispatcher",
Description: "请求分发中心",
Model: qwenModel,
})
dbExpert := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "db_expert",
Description: "数据库问题专家",
Model: qwenModel,
})
// 设置协作关系
adk.SetSubAgents(ctx, dispatcher, []adk.Agent{dbExpert})
2.3 Agent 作为工具(AgentAsTool)
这种模式将整个 Agent 包装成一个工具,适用于需要保持执行上下文的场景。与 Transfer 的关键区别在于:
- 调用方保留控制权
- 被调用 Agent 作为子过程执行
- 可以通过 EmitInternalEvents 透传内部事件
实现方式:
go复制reportAgent := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "report_generator",
Description: "生成详细分析报告",
Model: qwenModel,
})
reportTool := adk.NewAgentTool(ctx, reportAgent)
mainAgent := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{reportTool},
},
EmitInternalEvents: true,
},
})
3. 工程化实践:Middleware 与 Handler
3.1 核心扩展点架构
ChatModelAgent 提供了多个层次的扩展点,形成完整的拦截器链:
-
BeforeAgent:整个运行开始前
- 可修改 Instruction、Tools 等配置
- 典型用途:动态追加约束条件
-
BeforeModelRewriteState:模型调用前
- 可修改消息历史
- 典型用途:敏感信息过滤
-
AfterModelRewriteState:模型调用后
- 可修改模型输出
- 典型用途:结果标准化
-
WrapModel:包装模型调用
- 典型用途:统一日志、监控
-
WrapToolCall:包装工具调用
- 典型用途:参数校验、性能统计
3.2 实战:运维安全 Handler
以下是一个增强运维安全的 Handler 实现示例:
go复制type SecurityHandler struct {
*adk.BaseChatModelAgentMiddleware
}
func NewSecurityHandler() *SecurityHandler {
return &SecurityHandler{
BaseChatModelAgentMiddleware: &adk.BaseChatModelAgentMiddleware{},
}
}
func (h *SecurityHandler) BeforeAgent(
ctx context.Context,
runCtx *adk.ChatModelAgentContext,
) (context.Context, *adk.ChatModelAgentContext, error) {
// 拷贝上下文避免污染
newCtx := *runCtx
// 添加安全约束
newCtx.Instruction += `
安全约束:
1. 禁止透露任何内部系统架构细节
2. 涉及敏感操作必须调用审批工具
3. 用户数据必须脱敏处理`
return ctx, &newCtx, nil
}
func (h *SecurityHandler) BeforeModelRewriteState(
ctx context.Context,
state *adk.ChatModelAgentState,
) (context.Context, *adk.ChatModelAgentState, error) {
newState := *state
// 脱敏处理
for i, msg := range newState.Messages {
newState.Messages[i].Content = sanitize(msg.Content)
}
return ctx, &newState, nil
}
func (h *SecurityHandler) WrapInvokableToolCall(
ctx context.Context,
endpoint adk.InvokableToolCallEndpoint,
tCtx *adk.ToolContext,
) (adk.InvokableToolCallEndpoint, error) {
return func(ctx context.Context, args string, opts ...tool.Option) (string, error) {
// 记录审计日志
auditLog := fmt.Sprintf("工具调用审计 - 工具:%s 参数:%s 操作者:%s",
tCtx.Name,
maskSensitive(args),
getUserFromContext(ctx))
if err := saveAuditLog(ctx, auditLog); err != nil {
return "", fmt.Errorf("审计失败: %w", err)
}
// 高危工具检查
if isHighRiskTool(tCtx.Name) && !hasPermission(ctx, "HIGH_RISK") {
return "", errors.New("权限不足: 需要HIGH_RISK权限")
}
return endpoint(ctx, args, opts...)
}, nil
}
4. 典型问题排查指南
4.1 循环无法终止
症状:Agent 持续循环调用工具,不返回最终结果
排查步骤:
- 检查 MaxIterations 是否设置合理(建议5-20)
- 验证工具是否返回了足够的信息供模型决策
- 检查 Instruction 是否明确要求给出最终答案
- 确认是否应该为某些工具配置 ReturnDirectly
示例修复:
go复制agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
MaxIterations: 10,
ToolsConfig: adk.ToolsConfig{
ReturnDirectly: map[string]bool{
"final_answer_tool": true,
},
},
Instruction: "在收集足够信息后必须给出明确的最终答案",
})
4.2 工具选择不当
症状:模型频繁调用错误工具或拒绝调用合适工具
排查步骤:
- 检查工具描述的准确性
- 验证工具输入输出的 JSON Schema
- 在 Instruction 中添加工具使用指南
- 使用 BeforeAgent 动态调整工具集
工具描述优化示例:
go复制// 优化前
utils.InferTool("query", "查询信息", queryFunc)
// 优化后
utils.InferTool("customer_data_query",
"根据客户ID查询基本信息,输入格式:{\"customer_id\":string},输出包含姓名、等级和最近订单日期",
queryFunc)
4.3 性能优化技巧
- 消息历史裁剪:
go复制func (h *PerfHandler) BeforeModelRewriteState(
ctx context.Context,
state *adk.ChatModelAgentState,
) (context.Context, *adk.ChatModelAgentState, error) {
newState := *state
if len(newState.Messages) > 10 {
// 保留最近5条和系统消息
newState.Messages = compactMessages(newState.Messages)
}
return ctx, &newState, nil
}
- 工具缓存:
go复制func cachedToolCall(realTool tool.BaseTool) tool.BaseTool {
cache := make(map[string]cacheEntry)
return utils.InferTool(realTool.Name(), realTool.Description(),
func(ctx context.Context, input json.RawMessage) (json.RawMessage, error) {
cacheKey := string(input)
if entry, ok := cache[cacheKey]; ok && time.Since(entry.time) < 5*time.Minute {
return entry.result, nil
}
result, err := realTool.Invoke(ctx, input)
if err == nil {
cache[cacheKey] = cacheEntry{
result: result,
time: time.Now(),
}
}
return result, err
})
}
- 并发工具调用:
go复制func parallelToolInvoke(ctx context.Context, tools []tool.BaseTool, input json.RawMessage) ([]json.RawMessage, error) {
var wg sync.WaitGroup
results := make([]json.RawMessage, len(tools))
errs := make([]error, len(tools))
for i, t := range tools {
wg.Add(1)
go func(idx int, tl tool.BaseTool) {
defer wg.Done()
results[idx], errs[idx] = tl.Invoke(ctx, input)
}(i, t)
}
wg.Wait()
for _, err := range errs {
if err != nil {
return nil, err
}
}
return results, nil
}
5. 进阶应用场景
5.1 复杂审批工作流
结合 ChatModelAgent 和 Workflow Agent 实现智能审批:
go复制func createApprovalWorkflow(ctx context.Context) adk.Agent {
// 创建决策Agent
decisionAgent := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "approval_decider",
Description: "根据公司政策决定审批路径",
Model: qwenModel,
Tools: []tool.BaseTool{
createPolicyQueryTool(),
createRiskAssessmentTool(),
},
})
// 创建各级审批Agent
departmentApprover := createApproverAgent("department")
financeApprover := createApproverAgent("finance")
legalApprover := createApproverAgent("legal")
// 构建工作流
workflow := adk.NewWorkflowAgent(ctx, &adk.WorkflowConfig{
Name: "complex_approval",
Steps: []adk.WorkflowStep{
{
Name: "initial_assessment",
Agent: decisionAgent,
},
{
Name: "parallel_approvals",
Mode: adk.Parallel,
Steps: []adk.WorkflowStep{
{Name: "dept", Agent: departmentApprover},
{Name: "finance", Agent: financeApprover, Condition: "needs_finance_review"},
{Name: "legal", Agent: legalApprover, Condition: "needs_legal_review"},
},
},
{
Name: "final_decision",
Agent: createFinalizerAgent(),
},
},
})
return workflow
}
5.2 实时监控与干预
通过事件流实现实时监控:
go复制func monitorAgent(ctx context.Context, iter *adk.AsyncIterator[*adk.AgentEvent]) {
for {
select {
case <-ctx.Done():
return
default:
event, ok := iter.Next()
if !ok {
return
}
if event.Err != nil {
alert(ctx, "Agent运行错误", event.Err)
continue
}
if event.Output != nil {
switch output := event.Output.(type) {
case *adk.MessageOutput:
logMessage(output)
case *adk.ToolCallOutput:
monitorToolCall(output)
if isHighRiskTool(output.ToolName) {
if shouldIntervene(output) {
iter.Intervene(createIntervention())
}
}
}
}
}
}
}
在实际工程实践中,ChatModelAgent 的威力在于它既保持了 LLM 的灵活性,又通过规范的执行框架确保了可控性。这种平衡使得它特别适合需要智能决策但又要求可靠性的企业级应用场景。