Genkit Go 工具(Tools)开发指南:从 DefineTool 到中断恢复的完整实战 Genkit Go 工具Tools开发指南从 DefineTool 到中断恢复的完整实战【免费下载链接】skillsAgent Skills for Google products and technologies项目地址: https://gitcode.com/GitHub_Trending/skills29/skillsGenkit 是 Google 出品的跨模型 AI 开发框架Genkit Go 是其 Go 语言 SDK而工具Tool调用function calling是让模型突破文本生成边界、真正动手做事的关键能力。本文以 Genkit Go 技能包 中的 tools.md 为核心骨架系统讲解工具的定义、注入、控制策略、多媒体返回与中断恢复机制并结合仓库内 middleware.md、agents-human-in-the-loop.md 等文档揭示底层原理。读完本文你将掌握用DefineTool/DefineMultipartTool定义任意能力的工具、通过ai.WithTools注入生成调用、用 Tool Choice 与 Max Turns 控制调用策略以及实现暂停 → 人工确认 → 恢复执行这一完整的人类在环human-in-the-loop流程。一、工具在 Genkit Go 生态中的定位在 Genkit Go 中工具是注册在*Genkit实例上的可调用能力模型在生成过程中按需调用它并将结果融入最终回答。工具是 Agent 行为的载体无论是天气查询、文件访问还是转账操作都通过工具暴露给模型。从 agents.md 可以看到Agent 本质就是prompt tools之上的多轮对话原语而 middleware.md 中的ToolApproval、Filesystem、Skills等内置中间件也都通过注入/拦截工具来实现审批门控、沙箱文件访问与技能加载。工具调用发生在Generate的工具循环tool loop中模型产生输出 → 若含工具调用则执行 → 结果回填到新一轮模型调用 → 重复直至模型停止。理解这个循环是掌握本文后半部分中断恢复的前提。二、DefineTool定义模型可调用的工具genkit.DefineTool是定义工具的核心入口它把模型可调用什么能力和Go 如何实现该能力绑定在一起。以下是文档中的完整示例——一个返回指定城市天气的工具type WeatherInput struct { Location string json:location jsonschema:descriptionCity name } type WeatherOutput struct { Temperature float64 json:temperature Conditions string json:conditions } weatherTool : genkit.DefineTool(g, getWeather, Gets the current weather for a location., func(ctx *ai.ToolContext, input WeatherInput) (WeatherOutput, error) { // Call your weather API return WeatherOutput{Temperature: 72, Conditions: sunny}, nil }, )2.1 四个关键要素g*Genkit实例由genkit.Init返回的中心注册表工具、Flow、Prompt 都注册其上。SKILL.md 明确要求显式传递g而非存为全局变量这是贯穿整个 SDK 的核心模式。工具名getWeather模型在工具请求中引用的唯一标识。描述Gets the current weather for a location.SKILL.md 特别强调模型依据描述字符串决定是否调用某个工具描述模糊会导致漏调或错调。描述应写清楚工具做什么、何时该调用。实现函数接收*ai.ToolContext携带调用上下文和强类型输入WeatherInput返回强类型输出WeatherOutput与error。2.2 jsonschema 标签的作用示例中的jsonschema:descriptionCity name标签会把字段语义注入模型可见的 JSON Schema帮助模型正确填充参数。SKILL.md 的最佳实践同样强调在输出类型上使用jsonschema:description...标签模型需要这些描述才能理解每个字段应包含什么内容缺失会显著降低结构化输出的质量。这一定义方式与 prompts.md 中DefineSchemaFor的注册机制一脉相承——Go 结构体通过json/jsonschema标签自动推导为 JSON Schema。三、在生成调用中使用工具定义工具之后需要把它显式注入生成调用模型才会在回答过程中看到并自主调用它。3.1 通过 WithTools 注入resp, err : genkit.Generate(ctx, g, ai.WithModelName(googleai/gemini-flash-latest), ai.WithPrompt(Whats the weather in San Francisco?), ai.WithTools(weatherTool), ) // The model calls the tool automatically and incorporates the result fmt.Println(resp.Text())当提示词中的问题超出模型静态知识如实时天气时模型会自动发起工具调用Genkit 执行工具并把结果回填给模型最终resp.Text()返回的是整合了工具结果的完整回答。除Generate外GenerateText、GenerateData结构化输出以及DefinePrompt定义的 Prompt见 prompts.md其中.prompt文件的前置元数据tools:字段也会映射到WithTools都支持注入工具。3.2 Tool Choice控制模型的调用策略ai.WithToolChoice(ai.ToolChoiceAuto) // model decides (default) ai.WithToolChoice(ai.ToolChoiceRequired) // model must use a tool ai.WithToolChoice(ai.ToolChoiceNone) // model cannot use toolsToolChoiceAuto默认由模型自主决定是否调用工具适合大多数场景。ToolChoiceRequired强制模型必须调用工具适用于无论如何都要走工具路径的流程如必须查询数据库再回答。ToolChoiceNone禁用工具调用等价于一次纯文本生成。在.prompt文件中该选项由前置元数据toolChoice: auto | required | none配置见 prompts.md 中的字段映射表。3.3 Max Turns限制工具调用轮数工具调用可能形成多轮往返模型调用工具 → 结果回填 → 模型再次调用……为防止失控循环需要限制往返次数ai.WithMaxTurns(3) // default is 5默认值为 5当工具链较长例如一次任务需要连续查询多个服务时可适当调大。WithMaxTurns与工具循环的次数语义一一对应——在中间件章节见下中可以看到工具循环的每一轮迭代都会触发WrapGenerate钩子。四、DefineMultipartTool结构化输出 媒体内容普通DefineTool返回纯结构化数据而DefineMultipartTool支持结构化输出 媒体内容的组合返回适合截图、图像生成等需要把媒体带给模型或用户的场景screenshotTool : genkit.DefineMultipartTool(g, screenshot, Takes a screenshot of the current page, func(ctx *ai.ToolContext, input any) (*ai.MultipartToolResponse, error) { return ai.MultipartToolResponse{ Output: map[string]any{success: true}, Content: []*ai.Part{ai.NewMediaPart(image/png, base64Data)}, }, nil }, )MultipartToolResponse包含两部分Output结构化结果map[string]any供逻辑判断使用Content[]*ai.Part媒体内容列表ai.NewMediaPart(image/png, base64Data)创建一个 MIME 类型为image/png的媒体片段。这与 middleware.md 中WrapTool钩子的签名(*ai.MultipartToolResponse, error)完全一致说明MultipartToolResponse是工具执行层的统一返回类型——无论是普通工具还是多部分工具最终都归约为这一类型。五、工具中断Tool Interrupts人类在环的关键机制现实业务中许多工具调用需要人工确认转账、删除、高额操作。Genkit Go 提供了一套完整的中断机制工具执行时通过ai.InterruptWith暂停interrupt控制权交还给调用方代码或人类之后再以重跑或直接应答两种方式恢复。5.1 发起中断InterruptWith以下是一个转账工具当金额超过账户余额时暂停执行并抛出中断载荷而不是返回错误type TransferInput struct { ToAccount string json:toAccount Amount float64 json:amount } type TransferOutput struct { Status string json:status Message string json:message Balance float64 json:balance } type TransferInterrupt struct { Reason string json:reason ToAccount string json:toAccount Amount float64 json:amount Balance float64 json:balance } transferTool : genkit.DefineTool(g, transferMoney, Transfers money to another account., func(ctx *ai.ToolContext, input TransferInput) (TransferOutput, error) { if input.Amount accountBalance { return TransferOutput{}, ai.InterruptWith(ctx, TransferInterrupt{ Reason: insufficient_balance, ToAccount: input.ToAccount, Amount: input.Amount, Balance: accountBalance, }) } // Process transfer... return TransferOutput{Status: success, Balance: newBalance}, nil }, )关键点是ai.InterruptWith(ctx, TransferInterrupt{...})它暂停工具执行并把类型化载荷这里包括原因、目标账户、金额、当前余额随中断一起带回给调用方。注意中断与返回error有本质区别——返回普通 Goerror是让这一轮失败而中断是暂停这一轮等待恢复。5.2 处理中断RestartWith 与 RespondWith调用方通过检查resp.FinishReason ai.FinishReasonInterrupted感知中断遍历resp.Interrupts()中的每个中断载荷用ai.InterruptAs[T]还原为强类型数据再分派处理resp, err : genkit.Generate(ctx, g, ai.WithModelName(googleai/gemini-flash-latest), ai.WithTools(transferTool), ai.WithPrompt(userRequest), ) for resp.FinishReason ai.FinishReasonInterrupted { var restarts, responses []*ai.Part for _, interrupt : range resp.Interrupts() { meta, ok : ai.InterruptAsTransferInterrupt if !ok { continue } switch meta.Reason { case insufficient_balance: // RestartWith: re-execute the tool with adjusted input part, err : transferTool.RestartWith(interrupt, ai.WithNewInput(TransferInput{ ToAccount: meta.ToAccount, Amount: meta.Balance, // transfer whats available }), ) if err ! nil { return err } restarts append(restarts, part) case confirm_large: // RespondWith: provide a response directly without re-executing part, err : transferTool.RespondWith(interrupt, TransferOutput{Status: cancelled, Message: User declined}, ) if err ! nil { return err } responses append(responses, part) } } // Continue generation with the resolved interrupts resp, err genkit.Generate(ctx, g, ai.WithMessages(resp.History()...), ai.WithTools(transferTool), ai.WithToolRestarts(restarts...), ai.WithToolResponses(responses...), ) if err ! nil { return err } }两种恢复方式的语义差异恢复方式方法行为适用场景重跑工具transferTool.RestartWith(interrupt, ai.WithNewInput(...))以调整后的输入重新执行工具修正参数后重试如余额不足时转出可用余额直接应答transferTool.RespondWith(interrupt, output)不重新执行直接把结果注入对话人工决定拒绝/覆盖如用户取消转账最终通过ai.WithToolRestarts(restarts...)与ai.WithToolResponses(responses...)把恢复结果连同resp.History()一并送入新一轮Generate对话历史得以延续。整个过程会循环执行直到FinishReason不再是Interrupted——即可能出现恢复后又触发新的中断的级联情况。5.3 检查恢复状态IsResumed 与 OriginalInputAs在工具函数内部可以判断当前调用是否是从中断恢复而来的func(ctx *ai.ToolContext, input TransferInput) (TransferOutput, error) { if ctx.IsResumed() { // This is a resumed call after an interrupt original, ok : ai.OriginalInputAsTransferInput // original contains the input from the first call } // ... }ctx.IsResumed()返回true表示这是中断后的恢复调用ai.OriginalInputAsTransferInput还原首次调用的输入便于对比原始参数与调整后参数的差异例如记录审计日志。六、中断机制的底层原理与源码佐证结合仓库内其他参考文档可以更深入地理解这套机制的设计6.1 中断 用作控制流的工具调用agents-human-in-the-loop.md 明确指出中断本质上是把工具调用当作控制流使用——发起中断的工具不产生正常结果而是暂停回合turn之后从暂停点精确恢复。这也解释了为何中断与持久化正交无论 Agent 使用会话存储还是客户端托管状态暂停的回合只需被带回恢复调用会话/快照 ID 或状态 blob。6.2 恢复载荷必须与历史校验同一文档还指出Respond/Resume从原始请求推导工具名与引用框架会自动执行aix.ValidateResumeAgainstHistory校验工具名/引用必须匹配Restart的输入必须与原始请求一致手工构造的载荷会被拒绝。这保证了恢复流程的安全性防止伪造的工具调用混入对话历史。6.3 ToolApproval 中间件复用同一机制middleware.md 中的ToolApproval中间件是中断机制的典型落地它钩住WrapTool对任何不在白名单中的工具调用返回ai.NewToolInterruptError触发中断把审批暴露为人类在环步骤批准时在恢复元数据中设置toolApproved: truerestart, _ : tool.Restart(interruptPart, ai.RestartOptions{ ResumedMetadata: map[string]any{toolApproved: true}, }) genkit.Generate(ctx, g, ai.WithMessages(resp.History()...), ai.WithToolRestarts(restart))注意裸恢复不视为批准——没有该标志的恢复流程无法绕过门控体现了安全设计的严谨性。这与本文RestartWith/RespondWith的用法同源都是围绕中断载荷构造恢复片段。七、调试与验证工具调用工具是否真的被调用、参数是否正确不能靠盲跑判断。SKILL.md 推荐使用 Genkit CLI 捕获 trace 来验证genkit start -- go run . # 附带 Dev UI默认 http://localhost:4000运行应用 genkit flow:run myFlow {data: input} -- go run . # 一次性运行指定 flow genkit trace:list # 列出最近的 trace ID genkit trace:get traceId # 查看完整 trace输入、输出、工具调用、错误 genkit trace:get traceId --format json # 机器可读 JSON可管道给 jq其中genkit start -- go run .会在不修改代码的情况下包裹应用运行并捕获每个 Genkit action 的 trace从而在终端里直接证明工具确实被调用了并检查模型的输入输出。直接go run .会跳过 trace 捕获相当于盲调。详细 CLI 与 Dev UI 说明见 getting-started.md。八、最佳实践总结综合本文与仓库文档实践工具调用时应遵循以下原则写高质量的工具描述。模型依据描述决定是否调用工具模糊描述会导致漏调或错调SKILL.md Key Guidance。在输入/输出结构体上使用jsonschema:description...标签。这直接影响模型对字段语义的理解与结构化输出质量。显式传递*Genkit实例g不要存为全局变量工具、Flow、Prompt 均注册其上。把 AI 逻辑包进 Flow。Flow 提供 trace、可观测性与 HTTP 部署genkit.Handler并用core.Run划分带 trace 的子步骤见 flows-and-http.md方便在 Dev UI 与 CLI 中验证工具调用。优先复用内置中间件。Retry、Fallback、ToolApproval、Filesystem、Skills覆盖了重试、模型降级、工具审批、沙箱文件访问等横切需求其中ToolApproval已把工具调用审批封装成可配置能力无需手写中断逻辑。注意工具并发执行。同一轮迭代中多个并行工具调用会并发执行WrapTool见 middleware.md任何被其修改的共享状态都需要加锁保护。九、关联文档导航tools.md本文主体工具定义与中断机制的权威参考SKILL.mdGenkit Go 技能包总览含 Hello World、Agent 概览与 CLI 用法generation.mdGenerate/GenerateText/GenerateData与全部生成选项middleware.md中间件体系与ToolApproval审批机制agents-human-in-the-loop.mdAgent 场景下的中断/恢复与ToolResume载荷prompts.mdPrompt 定义与.prompt文件中的tools/toolChoice/maxTurns配置getting-started.md项目初始化、genkit.Init与 CLI 安装【免费下载链接】skillsAgent Skills for Google products and technologies项目地址: https://gitcode.com/GitHub_Trending/skills29/skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考