ChatModelAgentMiddleware
ChatModelAgentMiddleware 是自定义 ChatModelAgent(及基于它的 DeepAgent)行为的核心接口。自 v0.8.0 引入,在后续版本持续演进。
类型约定
本文使用默认 M = *schema.Message 的别名。泛型原始类型以 Typed 前缀命名:
type ChatModelAgentMiddleware = TypedChatModelAgentMiddleware[*schema.Message]
type BaseChatModelAgentMiddleware = TypedBaseChatModelAgentMiddleware[*schema.Message]
type ChatModelAgentState = TypedChatModelAgentState[*schema.Message]
type ModelContext = TypedModelContext[*schema.Message]
当需使用 *schema.AgenticMessage 时,直接使用 Typed 泛型版本即可。
接口定义
type ChatModelAgentMiddleware interface {
// ── 生命周期 Hook ──
// BeforeAgent:agent 运行前调用一次,可修改 instruction、tools 配置
BeforeAgent(ctx context.Context, runCtx *ChatModelAgentContext) (context.Context, *ChatModelAgentContext, error)
// AfterAgent:agent 成功终止后调用(最终回答或 return-directly 工具结果)
// 错误终止(超迭代、context 取消、model 错误)时不调用
AfterAgent(ctx context.Context, state *ChatModelAgentState) (context.Context, error)
// BeforeModelRewriteState:每次模型调用前调用
// 返回的 state 被持久化,可修改 Messages、ToolInfos、DeferredToolInfos
BeforeModelRewriteState(ctx context.Context, state *ChatModelAgentState, mc *ModelContext) (context.Context, *ChatModelAgentState, error)
// AfterModelRewriteState:每次模型调用后调用
// 输入 state 包含模型响应作为最后一条消息
AfterModelRewriteState(ctx context.Context, state *ChatModelAgentState, mc *ModelContext) (context.Context, *ChatModelAgentState, error)
// ── Wrapper ──
WrapInvokableToolCall(ctx context.Context, endpoint InvokableToolCallEndpoint, tCtx *ToolContext) (InvokableToolCallEndpoint, error)
WrapStreamableToolCall(ctx context.Context, endpoint StreamableToolCallEndpoint, tCtx *ToolContext) (StreamableToolCallEndpoint, error)
WrapEnhancedInvokableToolCall(ctx context.Context, endpoint EnhancedInvokableToolCallEndpoint, tCtx *ToolContext) (EnhancedInvokableToolCallEndpoint, error)
WrapEnhancedStreamableToolCall(ctx context.Context, endpoint EnhancedStreamableToolCallEndpoint, tCtx *ToolContext) (EnhancedStreamableToolCallEndpoint, error)
// WrapModel:包装 ChatModel,参数类型为 model.BaseModel[M](非 ToolCallingChatModel)
// 框架单独处理 WithTools 绑定,不经过用户 wrapper
WrapModel(ctx context.Context, m model.BaseModel[M], mc *ModelContext) (model.BaseModel[M], error)
}
💡 嵌入
*BaseChatModelAgentMiddleware可获得所有方法的空操作默认实现,只需覆盖关心的方法。
为什么用接口而非 AgentMiddleware 结构体?
AgentMiddleware 是结构体,有固有局限——用户无法扩展方法,回调仅返回 error 无法传播 context。ChatModelAgentMiddleware 是接口:
- Hook 方法返回
(context.Context, ..., error),支持 context 传播 - Wrapper 方法通过 endpoint 链传播修改后的 context
- 自定义 handler 可携带任意内部状态
选择原则:简单静态修改(追加 instruction/tools)用 AgentMiddleware;需动态行为、context 修改或调用包装时用 ChatModelAgentMiddleware。两者可同时使用。
上下文类型
ChatModelAgentContext
BeforeAgent 的输入,每次 Run 前调用一次:
type ChatModelAgentContext struct {
// 当前 instruction(含 agent 配置 + 框架追加 + 前序 handler 修改)
Instruction string
// 原始工具列表(含框架隐式工具如 transfer/exit)
Tools []tool.BaseTool
// 配置为"直接返回"的工具名集合
ReturnDirectly map[string]bool
// 模型原生工具搜索能力的 ToolInfo
// 由 handler 设置后,框架通过 model.WithToolSearchTool 传递给模型
ToolSearchTool *schema.ToolInfo
}
ChatModelAgentState
每次模型调用前后传递的持久化状态(跨 iteration 保持):
type ChatModelAgentState struct {
// 当前会话的所有消息
Messages []*schema.Message
// 传递给模型的工具定义(via model.WithTools),可在 BeforeModelRewriteState 中修改
ToolInfos []*schema.ToolInfo
// 延迟检索工具定义(via model.WithDeferredTools),用于模型原生搜索能力
// 未使用时为 nil
DeferredToolInfos []*schema.ToolInfo
}
💡 修改
ToolInfos/DeferredToolInfos的推荐位置是BeforeModelRewriteState——这是工具配置的 source of truth。不要在WrapModel中修改工具列表。
ModelContext
WrapModel 和 Before/AfterModelRewriteState 的上下文:
type ModelContext struct {
// Deprecated: 使用 ChatModelAgentState.ToolInfos 替代
Tools []*schema.ToolInfo
// 模型重试配置
ModelRetryConfig *ModelRetryConfig
// 模型容灾切换配置
ModelFailoverConfig *ModelFailoverConfig[*schema.Message]
}
ToolContext
工具包装的元数据:
type ToolContext struct {
Name string // 工具名称
CallID string // 本次调用唯一标识
}
工具调用端点类型
工具包装使用函数类型而非接口。根据工具实现的接口,框架调用对应的 Wrap 方法:
// 标准工具
type InvokableToolCallEndpoint func(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error)
type StreamableToolCallEndpoint func(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (*schema.StreamReader[string], error)
// 增强型工具(使用 ToolArgument/ToolResult)
type EnhancedInvokableToolCallEndpoint func(ctx context.Context, toolArgument *schema.ToolArgument, opts ...tool.Option) (*schema.ToolResult, error)
type EnhancedStreamableToolCallEndpoint func(ctx context.Context, toolArgument *schema.ToolArgument, opts ...tool.Option) (*schema.StreamReader[*schema.ToolResult], error)
💡 每个 Wrap 方法仅在工具实现了对应接口时才被调用。例如,工具只实现了
InvokableTool,则只会调用WrapInvokableToolCall,不会调用WrapStreamableToolCall。
执行顺序
Model 调用生命周期(由外到内)
AgentMiddleware.BeforeChatModel- ChatModelAgentMiddleware.BeforeModelRewriteState
retryModelWrapper(内部 — 失败重试)eventSenderModelWrapper预处理(内部 — 准备事件发送)- ChatModelAgentMiddleware.WrapModel 预处理(先注册 → 先执行)
callbackInjectionModelWrapper(内部)- Model.Generate / Stream
callbackInjectionModelWrapper后处理- ChatModelAgentMiddleware.WrapModel 后处理(先注册 → 后执行)
eventSenderModelWrapper后处理retryModelWrapper后处理- ChatModelAgentMiddleware.AfterModelRewriteState
AgentMiddleware.AfterChatModel
Tool 调用生命周期(由外到内)
eventSenderToolHandler(内部 — 发送工具结果事件)ToolsConfig.ToolCallMiddlewaresAgentMiddleware.WrapToolCall- ChatModelAgentMiddleware.WrapXxxToolCall(先注册 → 最外层)
- Tool.InvokableRun / StreamableRun
WrapModel 使用建议
| ✅ 推荐用途 | ❌ 不推荐用途 |
| 模型调用重试逻辑 | 修改输入消息(不持久化,破坏 prompt cache) |
| 模型容灾切换(备用模型) | 修改工具列表(应在 BeforeModelRewriteState中修改 state.ToolInfos) |
| 发送自定义事件(如流式进度) | |
| 处理/变换响应流、修改调用参数 |
运行时本地存储 API
在当前 agent Run() 期间存取键值对。值与中断/恢复兼容——序列化后随 checkpoint 持久化。
func SetRunLocalValue(ctx context.Context, key string, value any) error
func GetRunLocalValue(ctx context.Context, key string) (any, bool, error)
func DeleteRunLocalValue(ctx context.Context, key string) error
💡 自定义类型必须在
init()中通过schema.RegisterName[T]()注册,以确保 gob 序列化正确。这些函数只能在ChatModelAgentMiddleware回调内调用。
示例:跨回调共享状态
func init() {
schema.RegisterName[*ToolStats]("mypackage.ToolStats")
}
type ToolStats struct {
Count int
Name string
}
type MyMiddleware struct {
*adk.BaseChatModelAgentMiddleware
}
// 在工具调用后记录统计
func (m *MyMiddleware) 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) {
result, err := endpoint(ctx, args, opts...)
_ = adk.SetRunLocalValue(ctx, "last_tool", &ToolStats{Count: 1, Name: tCtx.Name})
return result, err
}, nil
}
// 在模型调用后读取统计
func (m *MyMiddleware) AfterModelRewriteState(ctx context.Context, state *adk.ChatModelAgentState, mc *adk.ModelContext) (context.Context, *adk.ChatModelAgentState, error) {
if val, found, _ := adk.GetRunLocalValue(ctx, "last_tool"); found {
if stats, ok := val.(*ToolStats); ok {
log.Printf("上一次工具: %s (count=%d)", stats.Name, stats.Count)
}
}
return ctx, state, nil
}
SendEvent API
在 agent 执行期间向事件流发送自定义 AgentEvent,调用方遍历事件流时可收到:
func SendEvent(ctx context.Context, event *AgentEvent) error
仅能在 ChatModelAgentMiddleware 回调内调用。
State 类型
💡
State仅为 checkpoint 向后兼容而保持导出。不要直接使用——请在ChatModelAgentMiddleware回调中使用ChatModelAgentState,用SetRunLocalValue/GetRunLocalValue替代原State.Extra。compose.ProcessState[*State]用法将在 v1.0.0 中停止工作。
迁移指南
从 compose.ProcessState[*State] 迁移
之前:
compose.ProcessState(ctx, func(_ context.Context, st *adk.State) error {
st.Extra["myKey"] = myValue
return nil
})
之后:
// 写入
if err := adk.SetRunLocalValue(ctx, "myKey", myValue); err != nil {
return ctx, state, err
}
// 读取
if val, found, err := adk.GetRunLocalValue(ctx, "myKey"); err == nil && found {
// use val
}
适配 AfterAgent(v0.9 新增)
AfterAgent 在 agent 成功终止后调用(最终回答或 return-directly 工具结果),可用于后处理:
func (m *MyMiddleware) AfterAgent(ctx context.Context, state *adk.ChatModelAgentState) (context.Context, error) {
log.Printf("Agent 完成,共 %d 条消息", len(state.Messages))
// 可在此做审计、统计、清理等
return ctx, nil
}
💡
AfterAgent按注册顺序调用(与BeforeAgent一致)。任一 handler 返回 error 后,后续 handler 不再调用(fail-fast),错误发送到事件流。
适配 ToolInfos / DeferredToolInfos(v0.9 新增)
ChatModelAgentState 新增了 ToolInfos 和 DeferredToolInfos 字段,取代 ModelContext.Tools 成为工具配置的 source of truth:
func (m *MyMiddleware) BeforeModelRewriteState(ctx context.Context, state *adk.ChatModelAgentState, mc *adk.ModelContext) (context.Context, *adk.ChatModelAgentState, error) {
// 动态过滤工具
filtered := make([]*schema.ToolInfo, 0, len(state.ToolInfos))
for _, t := range state.ToolInfos {
if shouldInclude(t.Name) {
filtered = append(filtered, t)
}
}
state.ToolInfos = filtered
return ctx, state, nil
}