工具调用

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

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

向 LLM 授予对其未用于训练的信息的访问权限

  • 经常变化的信息,例如股票价格或当前天气。
  • 特定于应用领域的信息,例如产品信息或用户个人资料。

请注意,这与检索增强生成 (RAG) 有重叠之处,后者也是让 LLM 将事实信息集成到其生成内容中的一种方法。RAG 是一种较为复杂的解决方案,最适合在您有大量信息或与问题最相关的信息模糊不清时使用。另一方面,如果检索 LLM 所需的信息是简单的函数调用或数据库查询,则更适合使用工具调用。

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

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

在 LLM 发起时执行操作

  • 在依托 LLM 的智能家居助理中开灯和关灯
  • 在依托 LLM 的餐厅代理中预订餐桌

准备工作

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

本页介绍了 Genkit 模型抽象的其中一个高级功能,因此在深入探究之前,您应先熟悉使用 AI 模型生成内容页面中的内容。您还应熟悉 Genkit 用于定义输入和输出架构的系统,如流程页面中所述。

工具调用概览

大体上讲,与 LLM 的典型工具调用交互如下所示:

  1. 调用应用会向 LLM 发出请求,并在提示中包含 LLM 可用于生成响应的工具列表。
  2. LLM 会生成完整的响应,或生成特定格式的工具调用请求。
  3. 如果调用方收到完整响应,系统会执行适当的逻辑,并向 LLM 发送包含原始提示或其变体以及工具调用结果的新请求。如果调用方收到工具调用,则会执行适当的逻辑,并向 LLM 发送包含原始提示或其变体以及工具调用结果的新请求。
  4. LLM 会像第 2 步中所述那样处理新提示。

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

  • 模型必须经过训练,以便在需要时发出工具请求来完成提示。通过 Web API 提供的大多数大型模型(例如 Gemini 和 Claude)都可以做到这一点,但体积较小且更专门的模型通常无法做到。如果您尝试向不支持工具的模型提供工具,Genkit 将抛出错误。
  • 调用应用必须以模型预期的格式向模型提供工具定义。
  • 调用应用必须提示模型以应用预期的格式生成工具调用请求。

使用 Genkit 进行工具调用

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

模型支持

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

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

定义工具

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

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

const ai = genkit({
  plugins: [googleAI()],
  model: gemini15Flash,
});

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() 语法完全相同;不过,必须使用 namedescriptioninputSchemaoutputSchema 这四个参数。编写工具定义时,请特别注意这些参数的措辞和描述性,因为它们对于 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 会反复调用 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;
}