如何让 ChatGPT 调用应用中的函数

2025-05-24

如何让 ChatGPT 调用应用中的函数

现在,您可以在回答提示时授予 OpenAI 访问您应用 API 的权限。这意味着,只需几行代码,您就可以让用户使用自然语言提示与您的 API 进行交互,而无需执行复杂的 API 调用链。

在本文中,我们将解释它的工作原理、何时可能使用它,并演示如何使用Encore构建一个简单的实现。

工作原理

除了向 OpenAI API(用于与支持 ChatGPT 的 LLM 模型交互)发送文本提示外,您现在还可以发送系统中定义的可供其使用的函数列表。AI 将自动决定是否调用一个或多个函数来回答提示。

值得一提的是,AI 本身并不会执行这些函数。相反,模型只是生成一些参数,用于调用你的函数,然后你的代码可以选择如何处理这些参数,通常是通过调用指定的函数。这是一件好事,因为你的应用程序始终处于完全的控制之下。

函数调用的生命周期流程:

函数调用生命周期流程图

你什么时候会用到它?

  1. 从各种服务中获取数据:人工智能助手现在可以通过从内部系统获取最新的客户数据来回答诸如“我最近的订单是什么?”之类的问题。

  2. 采取行动:AI 助手现在可以通过访问日历 API 代表用户执行操作,例如安排会议。现在只需几行代码即可将类似 Siri 的行为添加到您自己的应用中。

  3. 自然语言 API 调用:将用户以自然语言表达的查询转换为复杂的 API 调用链。让 LLM 创建所需的 SQL 查询,以便从您自己的数据库中获取数据。

  4. 提取结构化数据:现在,创建一个管道来获取原始文本,然后将其转换为结构化数据并保存到数据库中变得非常容易。无需再将相同类型的数据复制粘贴到 ChatGPT 中。

让我们尝试一下

在这个简单的示例中,我们正在构建一个对话助手,它可以帮助用户浏览图书数据库。为了使其更加实用,我们希望它能够在我们的数据库中查找图书。

我们将使用Encore.ts快速创建与我们的应用程序交互所需的端点,OpenAI API 将使用这些端点来回答用户的提示。

您可以在此处查看示例的完整代码:https://github.com/encoredev/examples/tree/main/ts/gpt-functions

1. 安装 Encore 并创建您的应用

要自己运行示例,首先安装 Encore:

  • macOS: brew install encoredev/tap/encore
  • Linux: curl -L https://encore.dev/install.sh | bash
  • 视窗: iwr https://encore.dev/install.ps1 | iex

接下来,使用以下命令通过示例代码创建一个新的 Encore 应用程序:



encore app create gpt-functions-example --example=ts/gpt-functions


Enter fullscreen mode Exit fullscreen mode

您还需要一个OpenAI API 密钥并通过运行以下命令进行设置:



encore secret set --type dev,local,pr,prod OpenAIAPIKey


Enter fullscreen mode Exit fullscreen mode

之后,您就可以运行该应用程序了:



encore run


Enter fullscreen mode Exit fullscreen mode

2.创建一些函数

函数调用的起点是在您自己的代码库中选择您希望模型为其生成参数的函数。

对于此示例,我们希望允许模型调用我们的 listgetsearch端点:



import { api } from "encore.dev/api";

export interface Book {
  id: string;
  name: string;
  genre: Genre;
  description: string;
}

export enum Genre {
  mystery = "mystery",
  nonfiction = "nonfiction",
  memoir = "memoir",
  romance = "romance",
  historical = "historical",
}

// Using a hardcoded database for convenience of this example
const db: Book[] = [
  {
    id: "a1",
    name: "To Kill a Mockingbird",
    genre: Genre.historical,
    description: `Compassionate, dramatic, and deeply moving, "To Kill A Mockingbird" takes readers to the roots of human behavior - to innocence and experience, kindness and cruelty, love and hatred, humor and pathos. Now with over 18 million copies in print and translated into forty languages, this regional story by a young Alabama woman claims universal appeal. Harper Lee always considered her book to be a simple love story. Today it is regarded as a masterpiece of American literature.`,
  },
  ...
];

export const list = api(
  {expose: true, method: 'GET', path: '/book'},
  async ({genre}: { genre: string }): Promise<{ books: { name: string; id: string }[] }> => {
    const books = db.filter((item) => item.genre === genre).map((item) => ({name: item.name, id: item.id}));
    return {books}
  },
);

export const get = api(
  {expose: true, method: 'GET', path: '/book/:id'},
  async ({id}: { id: string }): Promise<{ book: Book }> => {
    const book = db.find((item) => item.id === id)!;
    return {book}
  },
);

export const search = api(
  {expose: true, method: 'GET', path: '/book/search'},
  async ({name}: { name: string }): Promise<{ books: { name: string; id: string }[] }> => {
    const books = db.filter((item) => item.name.includes(name)).map((item) => ({name: item.name, id: item.id}));
    return {books}
  },
);


Enter fullscreen mode Exit fullscreen mode

值得注意的是,这些是我们在常规 CRUD 服务中可能拥有的端点,我们的业务逻辑中没有任何内容适合 LLM。

3. 向模型描述你的功能

现在,我们可以创建一个“函数定义”,向模型描述这些函数。这个定义描述了函数的功能(以及何时调用),以及调用每个函数所需的参数。

您可以通过在系统消息中提供清晰的指导、使用直观的函数名称以及函数和参数的详细描述来确保模型调用正确的函数。



import {api} from "encore.dev/api";
import {book} from "~encore/clients";
import OpenAI from "openai";
import {secret} from "encore.dev/config";

// Getting API from from Encore secrets
const apiKey = secret("OpenAIAPIKey");

const openai = new OpenAI({
  apiKey: apiKey(),
});

// Encore endpoint the receives a text prompt as a query param and returns a message as a response
export const gpt = api(
  {expose: true, method: "GET", path: "/gpt"},
  async ({prompt}: { prompt: string }): Promise<{ message: string | null }> => {

    // Using the runTools method on the Chat Completions API
    const runner = openai.beta.chat.completions
      .runTools({
        model: "gpt-3.5-turbo",
        messages: [
          {
            role: "system",
            content:
              "Please use our book database, which you can access using functions to answer the following questions.",
          },
          {
            role: "user",
            content: prompt,
          },
        ],
        // The tools array contains the functions you are exposing
        tools: [
          {
            type: "function",
            function: {
              name: "list",
              strict: true,
              description:
                "list queries books by genre, and returns a list of names of books",
              // The model will use this information to generate
              // arguments according to your provided schema.  
              parameters: {
                type: "object",
                properties: {
                  genre: {
                    type: "string",
                    enum: [
                      "mystery",
                      "nonfiction",
                      "memoir",
                      "romance",
                      "historical",
                    ],
                  },
                },
                additionalProperties: false,
                required: ["genre"],
              },
              // Calling the list endpoint in our book service 
              function: async (args: { genre: string }) => await book.list(args),
              parse: JSON.parse,
            },
          },

          ...

        ],
      })

    // Get the final message and return the message content
    const result = await runner.finalChatCompletion();
    const message = result.choices[0].message.content;
    return {message};
  },
);


Enter fullscreen mode Exit fullscreen mode

在上面的代码中,我们定义了LLM应该如何以及何时调用我们的list端点。我们指定端点接受五个预定义类型之一作为输入,并且genre参数是必需的。

4.调用示例

让我们利用 Encore 附带的本地开发仪表板,并尝试使用提示调用我们的助手,Recommend me a book that is similar to Kill a Mockingbird看看它会做出什么响应。

助手回答道:

我建议你看看以下与《杀死一只知更鸟》类似的历史书籍:

  1. 所有我们看不见的光
  2. 《蝲蛄吟唱的地方》

这些书与《杀死一只知更鸟》有一些相似之处,值得探索

这似乎是一个合适的答案,因为这两本书也存在于我们的数据库中,并且与《杀死一只知更鸟》属于同一类型。

Encore 在调用端点时为我们生成了跟踪信息gpt。在跟踪详细信息中,我们可以准确地看到 LLM 调用了哪些函数来生成最终响应:

  1. LLM 从提示中提取了“杀死一只知更鸟”并使用它来调用search端点。
  2. 端点search返回“杀死一只知更鸟”的ID。
  3. LLM 使用 ID 来调用get端点。
  4. 端点get返回书籍的描述和类型。
  5. LLM 使用类型来调用list端点。
  6. 端点list返回所请求类型的书籍列表。
  7. 法学硕士推荐两本与《杀死一只知更鸟》同一类型的书。

LLM 召集了我们三个职能部门来提出建议,非常令人印象深刻!

使用结构化输出进行函数调用

默认情况下,当您使用函数调用时,API 将为您的参数提供尽力匹配,这意味着在使用复杂模式时,模型偶尔可能会丢失参数或错误地获取参数类型。

2024 年 8 月,OpenAI 推出了结构化输出 (Structured Outputs )。通过在函数定义中设置启用 strict: true该功能(参见上面的代码示例),结构化输出可确保模型为函数调用生成的参数与您在函数定义中提供的 JSON 模式完全匹配。

函数调用的陷阱

使用函数调用时需要注意的一些事项:

  • LLM 会读取函数返回的数据,以确定下一步(返回响应或继续调用函数)。如果函数返回大量有效载荷,那么每次提交的请求都可能产生巨额费用。解决这个问题的一个方法是限制函数返回的数据。您还可以为 max_tokens 每个请求指定允许的数据量,如果达到限制,模型将切断响应。

  • 与您想象的相反,在单个工具调用中发送大量函数会降低模型选择正确工具的能力。OpenAI 建议将单个工具调用中的函数数量限制为不超过 20 个。

总结

现在您知道如何使用 OpenAI 函数调用功能将 ChatGPT 连接到外部工具。

文章来源:https://dev.to/encore/how-to-let-chatgpt-call-functions-in-your-app-27j8
PREV
Node.js 框架综述 2024 — Elysia / Hono / Nest / Encore — 你应该选择哪一个?
NEXT
那么...Linux?