ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

anthropic-sdk-go Tool Runner 详解:nhost 仓库中工具定义、自动对话循环与流式执行全解析

anthropic-sdk-go Tool Runner 详解:nhost 仓库中工具定义、自动对话循环与流式执行全解析 anthropic-sdk-go Tool Runner 详解nhost 仓库中工具定义、自动对话循环与流式执行全解析【免费下载链接】nhostThe Open Source Firebase Alternative with GraphQL.项目地址: https://gitcode.com/GitHub_Trending/nh/nhost在 nhost 仓库中vendor/github.com/anthropics/anthropic-sdk-go目录下随附了 Anthropic Go SDK v1.26.0 的 vendored 副本其中tools.md是该 SDK「工具助手Tool Helpers」功能的官方文档。本文以该文档为核心完整覆盖三种工具定义方式、BetaToolRunner自动对话循环、流式运行与运行时参数控制并结合 vendored 源码 betatoolrunner.go 逐层还原循环机制、并行工具执行与错误回传的实现细节帮助读者在 Go 应用中构建可自我修复、可并行、可流式的 Claude 工具调用系统。文档定位与适用前提tools.md所属的 SDK 版本可通过 go.mod 确认仓库依赖github.com/anthropics/anthropic-sdk-go v1.26.0。该版本的能力边界需注意以下前提Go 版本README 声明最低要求 Go 1.22。而从源码结构看betatoolrunner.go 导入了iter标准库包All()/AllStreaming()返回iter.Seq2迭代器iter包自 Go 1.23 起提供因此工具 Runner 相关特性实际要求 Go 1.23Beta 命名空间Runner 挂在client.Beta.Messages之下BetaToolRunner、BetaMessageNewParams等类型前缀均带Beta与client.Messages的稳定 Messages API 并行存在vendored 副本范围当前仓库 vendor 目录保留了该 SDK 的 Runner 实现betatoolrunner.go、核心模型文件与本文档tools.md但 SDK 上游仓库中tools.md末尾指向的examples/tool-runner、examples/tool-runner-streaming示例目录并未包含在 vendor 快照内完整可运行示例需查阅 SDK 上游仓库。核心抽象BetaTool 接口Runner 能「自动执行工具」的前提是每个工具不只是 API 参数而是同时携带定义与执行逻辑。vendored 源码中BetaTool是一个接口betatoolrunner.go#L14-L23type BetaTool interface { // Name returns the tools name Name() string // Description returns the tools description Description() string // InputSchema returns the JSON schema for the tools input InputSchema() BetaToolInputSchemaParam // Execute runs the tool with raw JSON input and returns the result Execute(ctx context.Context, input json.RawMessage) (BetaToolResultBlockParamContentUnion, error) }从源码结构看Runner 在初始化时会把每个BetaTool转成 API 所需的BetaToolParam只取 Name/Description/InputSchema并写入请求参数同时以工具名为 key 建立toolMap供后续执行分发betatoolrunner.go#L47-L72。也就是说定义与处理函数分离定义上送模型处理函数留在本地——这正是toolrunner包三种构造函数的目标。定义工具三种构造方式与原始 JSON 输入文档给出了三种创建工具的方式推荐程度递减NewBetaToolFromJSONSchema自动从结构体生成 schema、NewBetaToolFromBytes直接提供 JSON schema 字节、NewBetaTool显式传入BetaToolInputSchemaParam。三者的泛型参数会从 handler 函数签名自动推断无需手动指定类型。方式一从结构体自动生成 Schema推荐NewBetaToolFromJSONSchema依据结构体字段上的jsonschema标签自动生成输入 schemarequired、description、enum等约束全部来自标签type GetWeatherInput struct { City string json:city jsonschema:required,descriptionThe city name Units string json:units,omitempty jsonschema:enumcelsius,enumfahrenheit,descriptionTemperature units } weatherTool, err : toolrunner.NewBetaToolFromJSONSchema( get_weather, Get current weather for a city, func(ctx context.Context, input GetWeatherInput) (anthropic.BetaToolResultBlockParamContentUnion, error) { return anthropic.BetaToolResultBlockParamContentUnion{ OfText: anthropic.BetaTextBlockParam{ Text: fmt.Sprintf(Weather in %s: 72°F, sunny, input.City), }, }, nil }, )注意标签语义json:city决定字段在 JSON 中的键名jsonschema:required表示必填enumcelsius,enumfahrenheit把取值约束为枚举omitempty表示该字段可缺省。README 的「Tool helpers」小节给出了同一模式的完整可运行程序含anthropic.NewClient()、RunToCompletion与MaxIterations: 5的组合可作为本节的落地参考。方式二使用 JSON 字节当 schema 由外部系统如数据库、配置文件维护或需要与多语言定义保持一致时用NewBetaToolFromBytes直接提供 schema 字节type GetWeatherInput struct { City string json:city } weatherTool, err : toolrunner.NewBetaToolFromBytes( get_weather, Get current weather for a city, []byte({ type: object, properties: { city: {type: string, description: The city name} }, required: [city] }), func(ctx context.Context, input GetWeatherInput) (anthropic.BetaToolResultBlockParamContentUnion, error) { // Your handler here }, )方式三显式 Schema完全控制NewBetaTool接受BetaToolInputSchemaParam以 Go map 直接描述 properties适合需要程序化拼装 schema 的场景weatherTool : toolrunner.NewBetaTool( get_weather, Get current weather for a city, anthropic.BetaToolInputSchemaParam{ Properties: map[string]any{ city: map[string]any{ type: string, description: The city name, }, }, }, handler, )原始 JSON 输入如果不想让 SDK 替你反序列化把 handler 的输入类型声明为json.RawMessage或[]byte即可自行解析rawTool, err : toolrunner.NewBetaToolFromBytes( process_data, Process raw JSON data, schemaBytes, func(ctx context.Context, input json.RawMessage) (anthropic.BetaToolResultBlockParamContentUnion, error) { // Parse the JSON yourself var data map[string]any json.Unmarshal(input, data) // ... }, )这与BetaTool接口的Execute(ctx, input json.RawMessage)签名呼应无论哪种构造方式底层都以原始 JSON 字节交给 handler 封装层完成解码。BetaToolRunner自动对话循环BetaToolRunner自动接管「模型发工具调用 → 本地执行 → 结果回填 → 再问模型」的循环。文档描述的每个迭代为将当前消息发给 Claude若 Claude 回复中包含工具调用则并行执行这些工具把工具结果追加进对话重复直到 Claude 产出最终响应不再有工具调用。基本用法RunToCompletiontools : []anthropic.BetaTool{weatherTool} runner : client.Beta.Messages.NewToolRunner(tools, anthropic.BetaToolRunnerParams{ BetaMessageNewParams: anthropic.BetaMessageNewParams{ Model: anthropic.ModelClaudeSonnet4_20250514, MaxTokens: 1024, Messages: []anthropic.BetaMessageParam{ anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(Whats the weather in Tokyo?)), }, }, }) // Run to completion跑完整段对话直到完成 message, err : runner.RunToCompletion(context.Background())BetaToolRunnerParams内嵌BetaMessageNewParams因此模型、MaxTokens、System、Messages等 Messages API 参数原样可用另加一个 Runner 专属字段MaxIterationsbetatoolrunner.go#L26-L31。遍历消息All()All()返回iter.Seq2[*BetaMessage, error]迭代器每一轮 assistant 消息都会 yield 出来便于实时渲染工具调用过程for message, err : range runner.All(ctx) { if err ! nil { log.Fatal(err) } for _, block : range message.Content { switch b : block.AsAny().(type) { case anthropic.BetaTextBlock: fmt.Println([assistant]:, b.Text) case anthropic.BetaToolUseBlock: fmt.Printf([tool call]: %s(%v)\n, b.Name, b.Input) } } }block.AsAny()是 SDK 响应联合类型的变体判别方式按内容块实际类型文本块 / 工具使用块分别处理。逐轮控制NextMessage()需要在中途插入用户干预、日志或暂停逻辑时用NextMessage()一次只推进一轮for { message, err : runner.NextMessage(ctx) if err ! nil { log.Fatal(err) } if message nil { break // Conversation complete } // Process the message... }源码级循环机制NextMessage的实现betatoolrunner.go#L226-L267揭示了精确的轮次语义上限检查若MaxIterations 0且iterationCount MaxIterations标记completed并返回lastMessage不返回 nil即「达限即停、保留最后一条模型回复」先执行后请求先检查lastMessage中的tool_use块并执行executeTools把工具结果作为一条 user 消息追加到Params.Messages若没有工具调用则标记完成并返回最后消息发起 API 调用iterationCount后调用messageService.New并把 assistant 回复的message.ToParam()追加进历史。RunToCompletion本身就是一个简单循环反复调用NextMessage直到拿到nil消息betatoolrunner.go#L273-L283最终返回最后一条 assistant 消息。All()则在每次NextMessage返回nil且无错误时结束迭代betatoolrunner.go#L296-L312。流式执行BetaToolRunnerStreaming流式版本通过NewToolRunnerStreaming()创建内部同样是逐轮执行工具但每轮以NewStreaming发起 SSE 流式请求并在本地用Accumulate累积出完整消息供下一轮使用。AllStreaming外层轮次、内层事件runner : client.Beta.Messages.NewToolRunnerStreaming(tools, anthropic.BetaToolRunnerParams{ BetaMessageNewParams: anthropic.BetaMessageNewParams{ Model: anthropic.ModelClaudeSonnet4_20250514, MaxTokens: 1024, Messages: []anthropic.BetaMessageParam{ anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(Whats the weather in Tokyo?)), }, }, }) for eventsIterator : range runner.AllStreaming(ctx) { for event, err : range eventsIterator { if err ! nil { log.Fatal(err) } switch e : event.AsAny().(type) { case anthropic.BetaRawContentBlockDeltaEvent: switch delta : e.Delta.AsAny().(type) { case anthropic.BetaTextDelta: fmt.Print(delta.Text) } } } }从源码看betatoolrunner.go#L426-L435AllStreaming是「迭代器的迭代器」外层在!r.completed时每轮 yield 一个内层事件序列内层事件即BetaRawMessageStreamEventUnion。NextStreaming逐轮流式for !runner.IsCompleted() { for event, err : range runner.NextStreaming(ctx) { // Handle streaming events... } }实现细节值得注意betatoolrunner.go#L345-L407每一轮流式请求内部先defer stream.Close()再对事件流逐条finalMessage.Accumulate(event)流结束后的stream.Err()若非空会作为迭代器错误 yield 出去并中止对话。因此内层迭代器必须被完全消费——文档明确说明这一点否则累积出的消息不完整下一轮工具执行会基于残缺状态。配置与运行时控制MaxIterations防止失控循环MaxIterations限制 API 调用次数。设为0默认值表示不限制Runner 会一直运行到模型停止使用工具runner : client.Beta.Messages.NewToolRunner(tools, anthropic.BetaToolRunnerParams{ // ... MaxIterations: 10, // Stop after 10 API calls (0 no limit) })源码中该检查出现在NextMessage与NextStreaming的入口处达限时把completed置真IsCompleted()随即返回 truebetatoolrunner.go#L232-L235、betatoolrunner.go#L352-L355。会话中途修改参数Params是导出字段可以直接改改动在下一轮请求生效// Update maximum tokens runner.Params.MaxTokens 2048 // Update maximum iterations runner.Params.MaxIterations 10 // Update system prompt runner.Params.System []anthropic.BetaTextBlockParam{ {Text: You are a helpful assistant.}, } // Add messages to the conversation (direct field access) runner.Params.Messages append(runner.Params.Messages, anthropic.NewBetaUserMessage( anthropic.NewBetaTextBlock(Now check the weather in London too), )) // Or use the convenience method runner.AppendMessages(anthropic.NewBetaUserMessage( anthropic.NewBetaTextBlock(Now check the weather in London too), ))AppendMessages等价于对Params.Messages做 appendbetatoolrunner.go#L83-L85。由于newBetaToolRunnerBase构造时就对初始Messages做了拷贝betatoolrunner.go#L64runner 内部历史与调用方传入的切片相互独立中途追加不会影响外部变量。检查运行状态// Get most recent assistant message lastMsg : runner.LastMessage() // Get full conversation history (returns a copy) messages : runner.Messages() // Check iteration count count : runner.IterationCount() // Check if completed if runner.IsCompleted() { // ... }对应源码Messages()返回历史的副本可安全修改而不影响 runner 状态betatoolrunner.go#L89-L93IterationCount()返回已发起的 API 调用次数。此外还有一个文档未列但源码存在的方法Err()用于在使用All()/AllStreaming()遍历结束后取出最后一次迭代中发生的错误betatoolrunner.go#L107-L112。错误处理工具错误回传给模型而非崩溃工具执行出错时Runner 不会向上传播 Go error而是把错误文本包装为「带is_error: true标记」的工具结果发回 Claude让模型自行恢复或换一种方式重试func handler(ctx context.Context, input MyInput) (anthropic.BetaToolResultBlockParamContentUnion, error) { if input.City { return anthropic.BetaToolResultBlockParamContentUnion{}, errors.New(city is required) } // ... }错误转发生成在executeToolUse中betatoolrunner.go#L165-L195覆盖三类情况失败情形回传给模型的错误文本工具名未注册Error: Tool name not found输入 JSON 序列化失败Error: Failed to marshal tool input: errhandler 返回 errorError: err三者在源码中统一经由newBetaToolResultErrorBlockParam构造betatoolrunner.go#L160-L162。而真正会导致迭代中断的 Go error 只有上下文取消与 API 请求失败两类executeTools返回ctx.Err()、NextMessage包装failed to get next message。并行工具执行errgroup 实现细节当 Claude 在一条消息里请求多个工具调用时Runner 使用golang.org/x/sync/errgroup并行执行betatoolrunner.go#L119-L158带来三个特性并发执行多个工具调用各自在 goroutine 中运行互不阻塞整体时延取决于最慢的工具正确的取消传播每个 goroutine 在开跑前检查派生 contextgctx是否已取消任一工具使组失败或 ctx 取消时其余工具随之中止结果顺序稳定结果写入预分配的results[i]下标即工具调用顺序保证tool_result块与tool_use块一一对应再打包成一条 user 消息NewBetaUserMessage(results...)追加进对话。并发模型上有一条重要约束写在类型注释中BetaToolRunner与BetaToolRunnerStreaming均不是并发安全的所有方法必须从单个 goroutine 调用但当一轮内触发多个工具时handler 会被并发调用因此 handler 自身必须线程安全betatoolrunner.go#L197-L207。与 nhost 仓库的关联Anthropic Provider 集成nhost 仓库实际消费该 SDK 的位置是 AI 服务的 agents 模块。services/ai/agents/provider/anthropic.go 基于anthropic-sdk-go实现了anthropicMessagesprovider它通过newAnthropicMessagesConfiguration支持自定义baseURL与额外请求头即兼容自建 Anthropic Messages 兼容端点并定义了anthropicMessagesMaxRetries 2、defaultMaxTokens 8192两个常量约束重试与输出长度。从源码结构看nhost 目前并未直接调用BetaToolRunner系列 API而是直接基于 Messages API 组织 agent 循环——这反而印证了本文的价值betatoolrunner.go展示的循环骨架先执行上一轮工具、再请求模型、达限即停、错误回传正是自行实现 agent 循环时需要覆盖的全部行为。若 nhost 需要接入「模型自主多轮调用工具」的场景toolrunner包提供的RunToCompletion/All/ 流式三档抽象是最直接的现成方案对应 SDK 能力在 CHANGELOG 中标记为 client 层面的BetaToolRunner新特性。小结tools.md描述的 Tool Helpers 把「工具定义、本地执行、对话循环、流式输出、错误自愈」收敛为两个入口toolrunner.New*构造BetaToolclient.Beta.Messages.NewToolRunner(Streaming)获得BetaToolRunner或BetaToolRunnerStreaming。结合 vendored 源码可以确认其工程要点MaxIterations的「达限即停并保留最后回复」语义、Params导出字段支持的中途干预、工具错误转is_error结果的自恢复设计以及 errgroup 支撑的保序并行执行。这些机制在 Go 1.23 环境下可直接复用也为在 nhost 这类自研 agent 框架中引入标准工具循环提供了可验证的实现参照。【免费下载链接】nhostThe Open Source Firebase Alternative with GraphQL.项目地址: https://gitcode.com/GitHub_Trending/nh/nhost创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表