工具调用

工具调用(也称为“函数调用”)是一种结构化方式,可让 LLM 向调用它的应用发出请求。您可以定义要向模型提供的工具,模型会根据需要向您的应用发出工具请求,以执行您给出的提示。

工具调用的用例通常可归纳为以下几个主题:

向 LLM 授予其未经训练的信息访问权限

  • 经常变化的信息,例如股票价格或当前天气。
  • 与您的应用网域相关的信息,例如商品信息或用户个人资料。

请注意,这与检索增强生成 (RAG) 有一定重叠,RAG 也是让 LLM 将事实信息整合到其生成内容中的一种方式。RAG 是一种更复杂的解决方案,最适合以下情况:您拥有大量信息,或者与提示最相关的信息不明确。另一方面,如果检索 LLM 所需的信息是简单的函数调用或数据库查询,则工具调用更合适。

在 LLM 工作流中引入一定程度的确定性

  • 执行 LLM 本身无法可靠完成的计算。
  • 在某些情况下强制 LLM 生成逐字文本,例如在回答有关应用服务条款的问题时。

在 LLM 发起时执行操作

  • 在依托 LLM 的家庭助理中开关灯
  • 在 LLM 支持的餐厅代理中预订餐桌

准备工作

如果您想运行本页面中的代码示例,请先完成使用入门指南中的步骤。所有示例都假定您已设置项目并安装了 Genkit 依赖项。

本页面介绍了 Genkit 模型抽象的其中一项高级功能,因此在深入研究之前,您应该先熟悉使用 AI 模型生成内容页面上的内容。您还应熟悉 Genkit 用于定义输入和输出架构的系统,Flow 页面对此进行了讨论。

工具调用概览

概括来说,与 LLM 的典型工具调用交互如下所示:

  1. 调用方应用会通过请求向 LLM 发出提示,并在提示中添加 LLM 可用于生成回答的工具列表。
  2. LLM 会生成完整的回答,或以特定格式生成工具调用请求。
  3. 如果调用方收到完整的回答,则请求会得到满足,并且互动会结束;但如果调用方收到工具调用,则会执行适当的逻辑,并向 LLM 发送包含原始提示或其变体以及工具调用结果的新请求。
  4. LLM 会处理第 2 步中的新提示。

为此,必须满足以下几项要求:

  • 必须对模型进行训练,以便在需要完成提示时发出工具请求。通过 Gemini 等网络 API 提供的大多数较大的模型都可以做到这一点,但较小且更专业的模型通常无法做到这一点。如果您尝试向不支持工具调用的模型提供工具,Genkit 会抛出错误。
  • 调用方应用必须以模型所需的格式向模型提供工具定义。
  • 调用应用必须提示模型以应用所需的格式生成工具调用请求。

使用 Genkit 进行工具调用

Genkit 为支持该功能的模型提供了一个工具调用单一接口。每个模型插件都确保满足上述最后两项条件,并且 genkit.Generate() 函数会自动执行前面所述的工具调用循环。

模型支持

工具调用支持取决于模型、模型 API 和 Genkit 插件。请参阅相关文档,确定是否可能支持工具调用。此外:

  • 如果您尝试向不支持工具调用的模型提供工具,Genkit 会抛出错误。
  • 如果插件导出了模型引用,则 ModelInfo.Supports.Tools 属性将指示该模型是否支持工具调用功能。

定义工具

使用 genkit.DefineTool() 函数编写工具定义:

import (
    "context"
    "log"

    "github.com/firebase/genkit/go/ai"
    "github.com/firebase/genkit/go/genkit"
    "github.com/firebase/genkit/go/plugins/googlegenai"
)

func main() {
    ctx := context.Background()

    g, err := genkit.Init(ctx,
        genkit.WithPlugins(&googlegenai.GoogleAI{}),
        genkit.WithDefaultModel("googleai/gemini-2.0-flash"),
    )
    if err != nil {
      log.Fatal(err)
    }

    getWeatherTool := genkit.DefineTool(
        g, "getWeather", "Gets the current weather in a given location",
        func(ctx *ai.ToolContext, location string) (string, error) {
            // Here, we would typically make an API call or database query. For this
            // example, we just return a fixed value.
            return fmt.Sprintf("The current weather in %s is 63°F and sunny.", location);
        })
}

此处的语法看起来与 genkit.DefineFlow() 语法类似;不过,您必须编写说明。请特别注意说明的措辞和表达清晰度,因为 LLM 是否会正确调用该工具在很大程度上取决于说明的质量。

使用工具

在提示中添加已定义的工具以生成内容。

生成

resp, err := genkit.Generate(ctx, g,
    ai.WithPrompt("What is the weather in San Francisco?"),
    ai.WithTools(getWeatherTool),
)

DefinePrompt

weatherPrompt, err := genkit.DefinePrompt(g, "weatherPrompt",
    ai.WithPrompt("What is the weather in {{location}}?"),
    ai.WithTools(getWeatherTool),
)
if err != nil {
    log.Fatal(err)
}

resp, err := weatherPrompt.Execute(ctx,
    with.Input(map[string]any{"location": "San Francisco"}),
)

提示文件

---
system: "Answer questions using the tools you have."
tools: [getWeather]
input:
  schema:
    location: string
---

What is the weather in {{location}}?

然后,您可以在代码中执行提示,如下所示:

// Assuming prompt file named weatherPrompt.prompt exists in ./prompts dir.
weatherPrompt := genkit.LookupPrompt("weatherPrompt")
if weatherPrompt == nil {
    log.Fatal("no prompt named 'weatherPrompt' found")
}

resp, err := weatherPrompt.Execute(ctx,
    ai.WithInput(map[string]any{"location": "San Francisco"}),
)

如果 LLM 需要使用 getWeather 工具来回答提示,Genkit 会自动处理该工具调用。

显式处理工具调用

如果您想完全控制此工具调用循环(例如,应用更复杂的逻辑),请将 WithReturnToolRequests() 选项设置为 true。现在,您有责任确保所有工具请求都得到满足:

getWeatherTool := genkit.DefineTool(
    g, "getWeather", "Gets the current weather in a given location",
    func(ctx *ai.ToolContext, location string) (string, error) {
        // Tool implementation...
    })

resp, err := genkit.Generate(ctx, g,
    ai.WithPrompt("What is the weather in San Francisco?"),
    ai.WithTools(getWeatherTool),
    ai.WithReturnToolRequests(true),
)
if err != nil {
    log.Fatal(err)
}

parts := []*Part{}
for _, req := range resp.ToolRequests() {
    tool := genkit.LookupTool(g, req.Name)
    if tool == nil {
        log.Fatalf("tool %q not found", req.Name)
    }

    output, err := tool.RunRaw(ctx, req.Input)
    if err != nil {
        log.Fatalf("tool %q execution failed: %v", err)
    }

    parts = append(parts,
        ai.NewToolResponsePart(&ai.ToolResponse{
            Name:   req.Name,
            Ref:    req.Ref,
            Output: output,
        }))
}

resp, err = genkit.Generate(ctx, g,
    ai.WithMessages(resp.History()..., NewMessage(ai.RoleTool, nil, parts...)),
)
if err != nil {
    log.Fatal(err)
}