工具调用

工具调用(也称为“函数调用”)是一种结构化方式,可让 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 发送包含原始提示或其变体以及工具调用结果的新请求。如果调用方收到工具调用,则会执行适当的逻辑,并向 LLM 发送包含原始提示或其变体以及工具调用结果的新请求。
  4. LLM 会处理第 2 步中的新提示。

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

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

使用 Genkit 进行工具调用

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

模型支持

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

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

定义工具

使用 Genkit 实例的 defineTool() 函数编写工具定义:

import { genkit, z } from 'genkit';
import { googleAI } from '@genkitai/google-ai';

const ai = genkit({
  plugins: [googleAI()],
  model: googleAI.model('gemini-2.0-flash'),
});

const getWeather = ai.defineTool(
  {
    name: 'getWeather',
    description: 'Gets the current weather in a given location',
    inputSchema: z.object({ 
      location: z.string().describe('The location to get the current weather for')
    }),
    outputSchema: z.string(),
  },
  async (input) => {
    // Here, we would typically make an API call or database query. For this
    // example, we just return a fixed value.
    return `The current weather in ${input.location} is 63°F and sunny.`;
  }
);

此处的语法看起来与 defineFlow() 语法类似;不过,namedescriptioninputSchema 参数是必需的。编写工具定义时,请特别注意这些参数的措辞和描述性。它们对于 LLM 有效利用可用工具至关重要。

使用工具

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

生成

const response = await ai.generate({
  prompt: 'What is the weather in Baltimore?',
  tools: [getWeather],
});

definePrompt

const weatherPrompt = ai.definePrompt(
  {
    name: 'weatherPrompt',
    tools: [getWeather],
  },
  'What is the weather in {{location}}?'
);

const response = await weatherPrompt({ location: 'Baltimore' });

提示文件

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

What is the weather in {{location}}?

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

// assuming prompt file is named weatherPrompt.prompt
const weatherPrompt = ai.prompt('weatherPrompt');

const response = await weatherPrompt({ location: 'Baltimore' });

聊天

const chat = ai.chat({
  system: 'Answer questions using the tools you have.',
  tools: [getWeather],
});

const response = await chat.send('What is the weather in Baltimore?');

// Or, specify tools that are message-specific 
const response = await chat.send({
  prompt: 'What is the weather in Baltimore?',
  tools: [getWeather],
});

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

在运行时动态定义工具

与 Genkit 中的大多数内容一样,您需要在应用初始化期间预定义工具。您必须执行此操作,才能从 Genkit 开发者界面与工具进行交互。这通常是推荐的方法。不过,在某些情况下,必须根据用户请求动态定义该工具。

您可以使用 ai.dynamicTool 函数动态定义工具。此函数与 ai.defineTool 方法非常相似。不过,Genkit 运行时不会跟踪动态工具,因此您无法使用 Genkit 开发者界面与其交互。而是必须通过引用将其传递给 ai.generate 调用(对于常规工具,您还可以使用字符串工具名称)。

import { genkit, z } from "genkit";
import { googleAI } from "@genkit-ai/googleai";

const ai = genkit({
  plugins: [googleAI()],
  model: googleAI.model("gemini-2.0-flash"),
});

const weatherFlow = ai.defineFlow("weatherFlow", async () => {
  const getWeather = ai.dynamicTool(
    {
      name: "getWeather",
      description: "Gets the current weather in a given location",
      inputSchema: z.object({
        location: z
          .string()
          .describe("The location to get the current weather for"),
      }),
      outputSchema: z.string(),
    },
    async (input) => {
      return `The current weather in ${input.location} is 63°F and sunny.`;
    }
  );

  const { text } = await ai.generate({
    prompt: "What is the weather in Baltimore?",
    tools: [getWeather],
  });

  return text;
});

在定义动态工具时,如需指定输入和输出架构,您可以使用 Zod(如上例所示),也可以传入手动构建的 JSON 架构。

const getWeather = ai.dynamicTool(
  {
    name: "getWeather",
    description: "Gets the current weather in a given location",
    inputJsonSchema: myInputJsonSchema,
    outputJsonSchema: myOutputJsonSchema,
  },
  async (input) => { /* ... */ }
);

动态工具不需要实现函数。如果您不传入函数,该工具将像中断一样运行,您可以手动处理工具调用:

const getWeather = ai.dynamicTool(
  {
    name: "getWeather",
    description: "Gets the current weather in a given location",
    inputJsonSchema: myInputJsonSchema,
    outputJsonSchema: myOutputJsonSchema,
  }
);

使用中断暂停工具循环

默认情况下,Genkit 会反复调用 LLM,直到所有工具调用都得到解析为止。您可以在需要时有条件地暂停执行,例如:

  • 向用户提问或显示界面。
  • 与用户确认可能存在风险的操作。
  • 请求对某项操作进行非正式审批。

中断是一种特殊工具,可暂停循环并将控制权返回给您的代码,以便您处理更高级的场景。请参阅中断指南,了解如何使用中断。

显式处理工具调用

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

const getWeather = ai.defineTool(
  {
    // ... tool definition ...
  },
  async ({ location }) => {
    // ... tool implementation ...
  },
);

const generateOptions: GenerateOptions = {
  prompt: "What's the weather like in Baltimore?",
  tools: [getWeather],
  returnToolRequests: true,
};

let llmResponse;
while (true) {
  llmResponse = await ai.generate(generateOptions);
  const toolRequests = llmResponse.toolRequests;
  if (toolRequests.length < 1) {
    break;
  }
  const toolResponses: ToolResponsePart[] = await Promise.all(
    toolRequests.map(async (part) => {
      switch (part.toolRequest.name) {
        case 'specialTool':
          return {
            toolResponse: {
              name: part.toolRequest.name,
              ref: part.toolRequest.ref,
              output: await getWeather(part.toolRequest.input),
            },
          };
        default:
          throw Error('Tool not found');
      }
    })
  );
  generateOptions.messages = llmResponse.messages;
  generateOptions.prompt = toolResponses;
}