ChatModelAgentMiddleware 是 Agent 的中间件接口:
type ChatModelAgentMiddleware interface {
// BeforeAgent:在每次Agent任务开始运行前触发,
// 可修改Agent的系统指令(instruction)和工具配置。
BeforeAgent(ctx context.Context, runCtx *ChatModelAgentContext) (context.Context, *ChatModelAgentContext, error)
// BeforeModelRewriteState:**每一轮调用大模型之前**触发。
// 返回的状态会保存到Agent内部状态,并传给大模型。
BeforeModelRewriteState(ctx context.Context, state *ChatModelAgentState, mc *ModelContext) (context.Context, *ChatModelAgentState, error)
// AfterModelRewriteState:**每一轮大模型调用完成之后**触发。
// 入参state里面已经包含模型本次返回的消息作为最后一条message。
AfterModelRewriteState(ctx context.Context, state *ChatModelAgentState, mc *ModelContext) (context.Context, *ChatModelAgentState, error)
// WrapInvokableToolCall:包装【普通同步工具】的执行逻辑,用于自定义行为。
// 仅对实现了 InvokableTool 接口的工具生效。
WrapInvokableToolCall(ctx context.Context, endpoint InvokableToolCallEndpoint, tCtx *ToolContext) (InvokableToolCallEndpoint, error)
// WrapStreamableToolCall:包装【流式工具】的执行逻辑,用于自定义行为。
// 仅对实现了 StreamableTool 接口的工具生效。
WrapStreamableToolCall(ctx context.Context, endpoint StreamableToolCallEndpoint, tCtx *ToolContext) (StreamableToolCallEndpoint, error)
// WrapEnhancedInvokableToolCall:包装【增强型同步工具】的执行逻辑。
// 仅对实现了 EnhancedInvokableTool 接口的工具生效(支持Interrupt中断/人机审批)。
WrapEnhancedInvokableToolCall(ctx context.Context, endpoint EnhancedInvokableToolCallEndpoint, tCtx *ToolContext) (EnhancedInvokableToolCallEndpoint, error)
// WrapEnhancedStreamableToolCall:包装【增强型流式工具】的执行逻辑。
// 仅对实现了 EnhancedStreamableTool 接口的工具生效。
WrapEnhancedStreamableToolCall(ctx context.Context, endpoint EnhancedStreamableToolCallEndpoint, tCtx *ToolContext) (EnhancedStreamableToolCallEndpoint, error)
// WrapModel:包装大模型实例,自定义模型调用行为。
// 在准备发起模型请求时触发。
WrapModel(ctx context.Context, m model.BaseChatModel, mc *ModelContext) (model.BaseChatModel, error)
}
按照 Go 原生语法的要求,如果一个接口包含多个方法,那么结构体必须完整实现接口的全部方法,才能够赋值给该接口类型的变量ChatModelAgentMiddleware 。而在 Eino 框架中提供了便捷方案:在自定义中间件结构体里嵌入*adk.BaseChatModelAgentMiddleware,该基类已经预先完成了接口全部方法的默认空实现。开发时只需覆写业务需要的少量方法,其余方法直接复用基类自带的默认逻辑,无需手动编写全部接口方法,减少样板代码。
type safeToolMiddleware struct {
*adk.BaseChatModelAgentMiddleware
}
func (m *safeToolMiddleware) WrapInvokableToolCall(
_ context.Context,
endpoint adk.InvokableToolCallEndpoint,
_ *adk.ToolContext,
) (adk.InvokableToolCallEndpoint, error) {
return func(ctx context.Context, args string, opts ...tool.Option) (string, error) {
result, err := endpoint(ctx, args, opts...)
if err != nil {
if _, ok := compose.IsInterruptRerunError(err); ok {
return "", err
}
// 将错误转换为字符串,而不是返回错误
return fmt.Sprintf("[tool error] %v", err), nil
}
return result, nil
}, nil
}
如上代码,只重写你想要自定义的这一个方法,其余7个里剩下的复用基类。
在 Agent 初始化阶段,框架只会调用一次WrapInvokableToolCall,入参endpoint代表原始的工具执行函数。我们在这个方法内部定义并返回一个新的闭包函数,随后WrapInvokableToolCall执行结束,不会再次运行;框架会保存返回的闭包,替换掉原始的endpoint。在后续对话流程里,当大模型选择调用该工具时,框架执行的是包装后的闭包,不再直接调用原始endpoint。闭包内部主动调用原始endpoint来执行业务逻辑,同时可以在闭包中实现错误捕获、返回值包装、调用日志记录等通用横切能力。
其中:
// 将错误转换为字符串,而不是返回错误
return fmt.Sprintf("[tool error] %v", err), nil
相对于普通业务错误相比,这里采用了特殊处理逻辑,不再向外返回 Go 的 error,而是将错误信息包装为普通字符串返回给 LLM。Go 常规写法是出错时直接返回result, err,但此处工具执行遇到业务报错(例如用户不存在、参数非法)时,不会把 error 抛给 Agent 框架,而是返回形如[tool error] xxx的文本,同时将 err 置为 nil。这样大模型可以读取到这段错误文本,识别出工具调用失败,并自主组织自然语言回复用户。倘若直接向框架返回 error,TurnLoop 会判定为系统异常,直接终止整个 Agent 任务。
二、配置 Agent 使用 Middleware
cfg := &deep.TypedConfig[M]{
Name: "Ch05MiddlewareAgent",
Description: "ChatWithDoc agent with safe tool middleware and retry.",
ChatModel: cm,
Instruction: agentInstruction,
Backend: backend,
StreamingShell: backend,
MaxIteration: 50,
Handlers: []adk.TypedChatModelAgentMiddleware[M]{
helpers.NewSafeToolMiddleware[M](),
},
}
helpers.ApplyMessageModelRetry(cfg)
agent, err := deep.NewTyped[M](ctx, cfg)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
上面代码:构造deep.TypedConfig[M]配置结构体,依次设置 Agent 名称、描述、大模型、系统提示词、Backend、最大迭代次数以及中间件列表,在 Handlers 中注册 SafeToolMiddleware,也就是前面介绍的工具安全错误处理中间件;接着调用helpers.ApplyMessageModelRetry(cfg),为该 Agent 配置追加模型调用重试能力;最后通过deep.NewTyped[M]依据配置创建 Agent 实例,创建失败时打印错误信息并退出程序。
其中,Handlers 是中间件切片(slice),可以放多个中间件实例,多个 helpers.NewXXXMiddleware() 放进去。
Handlers: []adk.TypedChatModelAgentMiddleware[M]{
helpers.NewSafeToolMiddleware[M](),
middleware.NewAuthMiddleware[M](), // 你自己写的鉴权中间件
middleware.NewTraceMiddleware[M](), // 链路追踪中间件
},
执行链路: M1 → M2 → M3 → 原始Agent逻辑 → M3返回 → M2返回 → M1返回