PatchToolCalls
adk/middlewares/patchtoolcalls
💡 PatchToolCalls 中间件用于修复消息历史中「悬空的工具调用」(dangling tool calls)问题。在 v0.8.0 版本引入。同时支持
*schema.Message和*schema.AgenticMessage两种消息类型。
概述
在多轮对话场景中,可能出现 Assistant 消息包含工具调用(ToolCalls),但对话历史中缺少对应 Tool 响应的情况。这种「悬空的工具调用」会导致某些模型 API 报错或产生异常行为。常见场景:
- 用户在工具执行完成前发送了新消息,导致工具调用被中断
- 会话恢复时,部分工具调用结果丢失
- Human-in-the-loop 场景下,用户取消了工具执行 PatchToolCalls 中间件会在每次模型调用前(
BeforeModelRewriteState钩子)扫描消息历史,为缺少响应的工具调用自动插入占位符消息。
快速开始
import (
"context"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/adk/middlewares/patchtoolcalls"
)
// 使用默认配置(cfg 可传 nil)
mw, err := patchtoolcalls.New(ctx, nil)
if err != nil {
// 处理错误
}
agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: yourChatModel,
Middlewares: []adk.ChatModelAgentMiddleware{mw},
})
API 参考
Config
type Config struct {
PatchedContentGenerator func(ctx context.Context, toolName, toolCallID string) (string, error)
}
| 字段 | 类型 | 必填 | 说明 |
| PatchedContentGenerator | func(ctx context.Context, toolName, toolCallID string) (string, error) | 否 | 自定义生成占位符消息内容的函数。未设置时使用内置默认消息模板 |
New
func New(ctx context.Context, cfg *Config) (adk.ChatModelAgentMiddleware, error)
创建 PatchToolCalls 中间件。cfg 可为 nil,此时使用默认配置。内部调用 NewTyped[*schema.Message]。
NewTyped
func NewTyped[M adk.MessageType](_ context.Context, cfg *Config) (adk.TypedChatModelAgentMiddleware[M], error)
泛型版本构造函数,支持 *schema.Message 和 *schema.AgenticMessage。cfg 可为 nil。
- 当
M = *schema.Message时,通过ToolCallID字段匹配 Tool 消息 - 当
M = *schema.AgenticMessage时,通过ContentBlock.FunctionToolResult.CallID匹配
默认占位符消息
如果不设置 PatchedContentGenerator,中间件使用内置模板(通过 fmt.Sprintf 格式化,%s 依次对应 toolName 和 toolCallID):英文(默认):
Tool call %s with id %s was canceled - another message came in before it could be completed.
中文:
工具调用 %s(ID 为 %s)已被取消——在其完成之前收到了另一条消息。
可通过 adk.SetLanguage() 切换语言。
使用示例
自定义占位符消息
mw, err := patchtoolcalls.New(ctx, &patchtoolcalls.Config{
PatchedContentGenerator: func(ctx context.Context, toolName, toolCallID string) (string, error) {
return fmt.Sprintf("[系统提示] 工具 %s 的执行被跳过(调用ID: %s)", toolName, toolCallID), nil
},
})
泛型用法(AgenticMessage)
mw, err := patchtoolcalls.NewTyped[*schema.AgenticMessage](ctx, nil)
if err != nil {
// 处理错误
}
agent, err := adk.NewTypedChatModelAgent[*schema.AgenticMessage](ctx, &adk.TypedChatModelAgentConfig[*schema.AgenticMessage]{
Model: yourChatModel,
Middlewares: []adk.TypedChatModelAgentMiddleware[*schema.AgenticMessage]{mw},
})
结合其他中间件
// PatchToolCalls 通常放在中间件链的前面
// 确保在其他中间件处理消息之前修复悬空的工具调用
agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: yourChatModel,
Middlewares: []adk.ChatModelAgentMiddleware{
patchToolCallsMiddleware, // 先修复消息
summarizationMiddleware, // 再进行摘要
reductionMiddleware, // 最后进行裁剪
},
})
工作原理
💡 对于
*schema.Message,通过msg.Role == schema.Tool && msg.ToolCallID匹配;对于*schema.AgenticMessage,通过ContentBlock.FunctionToolResult.CallID匹配。
示例场景
修复前:
[User] "帮我查询天气"
[Assistant] ToolCalls: [{id: "call_1", name: "get_weather"}, {id: "call_2", name: "get_location"}]
[Tool] "call_1: 晴天,25°C"
[User] "不用查位置了,直接告诉我北京的天气" <- 用户中断
修复后:
[User] "帮我查询天气"
[Assistant] ToolCalls: [{id: "call_1", name: "get_weather"}, {id: "call_2", name: "get_location"}]
[Tool] "call_1: 晴天,25°C"
[Tool] "call_2: 工具调用 get_location(ID 为 call_2)已被取消..." <- 自动插入
[User] "不用查位置了,直接告诉我北京的天气"
多语言支持
占位符消息支持中英文,通过 adk.SetLanguage() 切换:
import "github.com/cloudwego/eino/adk"
adk.SetLanguage(adk.LanguageChinese) // 中文
adk.SetLanguage(adk.LanguageEnglish) // 英文(默认)
注意事项
💡
BeforeModelRewriteState返回的 state 会被框架持久化到 agent 内部状态(参见wrappers.go中的ProcessState调用)。因此 PatchToolCalls 插入的占位符消息会保留在后续迭代中,不需要每轮重复修补。
- 建议将此中间件放在中间件链的前面,确保其他中间件处理的是完整的消息历史
cfg参数可传nil,等价于&Config{}- 如果消息列表为空(
len(state.Messages) == 0),中间件直接返回,不做任何处理