likes
comments
collection
share

使用 openai Function calling 实现天气查询

作者站长头像
站长
· 阅读数 5

OpenAI 开放的 Function calling 函数调用 功能使您的应用程序能够根据用户输入执行操作。这意味着它可以代表您的用户搜索网络、发送电子邮件或预订门票,使其比常规聊天机器人更强大。

接下来我们将使用 OpenAI 函数以及最新版本的 Node.js SDK 的构建应用程序。实现实时查询当前天气,并根据天气给出一些你的今天活动建议。

我们的应用程序是一个简单的代理,可帮助您查找您所在地区的活动。它可以访问两个功能,getLocation() 和 getCurrentWeather(),这意味着它可以确定您所在的位置以及当前的天气情况。

在这一点上,重要的是要了解 OpenAI 不会为您执行任何代码。它只是告诉应用在给定方案中应该使用哪些函数,然后由应用来调用它们。这一点一定要注意,这个也是 Function calling 执行的核心逻辑;也就是openai 不会执行你的代码。只是告诉你应该调用那个代码。

一旦我们的代理知道您的位置和天气,它将使用 GPT 的内部知识为您推荐合适的当地活动。

调用 openai

async function fetchOpenAI(body) {
  const response = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer sk-xxxxxxx`
    },
    body: JSON.stringify({
      max_tokens: 2048,
      ...body
    })
  });
  const data = await response.json();
  return data;
}

创建我们的两个函数

接下来,我们将创建这两个函数。第一个 - getLocation - 使用 IP API 获取用户的位置。

async function getLocation() {
  const response = await fetch("https://ipapi.co/json/");
  const locationData = await response.json();
  return locationData;
}

IP API 返回一堆关于您的位置的数据,包括您的纬度和经度,我们将在第二个函数 getCurrentWeather 中将其用作参数。它使用 Open Meteo API 获取当前天气数据,如下所示:

async function getCurrentWeather(latitude, longitude) {
  const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&hourly=apparent_temperature`;
  const response = await fetch(url);
  const weatherData = await response.json();
  return weatherData;
}

描述我们为 OpenAI 提供的功能

为了让 OpenAI 理解这些函数的用途,我们需要使用特定的模式来描述它们。我们将创建一个名为的数组 functionDefinitions ,该数组包含每个函数一个对象。每个对象将有三个键: name 、 description 和 parameters 。

const functionDefinitions = [
  {
    name: "getCurrentWeather",
    description:
      "获取给定位置的当前天气(以纬度和经度表示)",
    parameters: {
      type: "object",
      properties: {
        latitude: {
          type: "string",
        },
        longitude: {
          type: "string",
        },
      },
      required: ["longitude", "latitude"],
    },
  },
  {
    name: "getLocation",
    description: "根据 IP 地址获取用户的位置",
    parameters: {
      type: "object",
      properties: {},
    },
  },
];

设置 messages 数组

我们还需要定义一个 messages 数组。这将跟踪我们的应用程序和 OpenAI 之间的所有消息来回。

数组中的第一个对象应始终将 role 属性设置为 "system" ,这告诉 OpenAI 这就是我们希望它的行为方式。

const messages = [
  {
    role: "system",
    content: `你是一个有用的助手。仅使用为您提供的功能`,
  },
];

创建代理函数

现在,我们已准备好构建应用的逻辑,该逻辑存在于 agent 函数中。它是异步的,并接受一个参数 userInput 。

我们首先将 userInput 推送到 messages 数组。这一次,我们将 role 设置为 "user" ,以便 OpenAI 知道这是来自用户的输入。

async function agent(userInput) {
  messages.push([
    {
      role: "user",
      content: userInput,
    },
  ]);
  const response = await fetchOpenAI({
      model: "gpt-4-1106-preview", 
      messages: messages,
      functions: functionDefinitions,
    });
  console.log(response);
}

通过简单的输入运行我们的应用程序

让我们尝试使用需要函数调用才能给出合适回复的输入来运行。 agent

agent("Where am I located right now?");

当我们运行上面的代码时,我们看到 OpenAI 的响应注销到控制台,如下所示:

 {
  model: 'gpt-4-1106-preview',
  object: 'chat.completion',
  usage: { prompt_tokens: 108, completion_tokens: 9, total_tokens: 117 },
  id: 'chatcmpl-8LCJE8i12wCzCcy9pfO2ocnyz03vm',
  created: 1700062896,
  choices: [
    {
      index: 0,
      delta: null,
      message: {
          role: 'assistant',
          function_call: { name: 'getLocation', arguments: '{}' }
      },
      finish_reason: 'function_call'
    }
  ]
}

此响应告诉我们,我们应该调用我们的函数之一,因为它包含以下键: finish:_reason: "function_call" 函数的名称可以在键中找到,该 response.choices[0].message.function_call.name 键设置为 "getLocation" 。

将 OpenAI 响应转换为函数调用

现在我们已经将函数的名称转换为字符串,我们需要将其转换为函数调用。为了帮助我们解决这个问题,我们将把两个函数都收集到一个名为 availableFunctions

const availableFunctions = {
  getCurrentWeather,
  getLocation,
};

这很方便,因为我们可以通过括号表示法和从 OpenAI 返回的字符串来访问该 getLocation 函数,如下所示: availableFunctions["getLocation"] .

const { finish_reason, message } = response.choices[0];
 
if (finish_reason === "function_call") {
  const functionName = message.function_call.name;
  const functionToCall = availableFunctions[functionName];
  const functionArgs = JSON.parse(message.function_call.arguments);
  const functionArgsArr = Object.values(functionArgs);
  const functionResponse = await functionToCall.apply(null, functionArgsArr);
  console.log(functionResponse);
}

我们还抓住了 OpenAI 希望我们传递到函数中的任何论点: message.function_call.arguments .但是,对于第一个函数调用,我们不需要任何参数。

如果我们使用相同的输入 ( "Where am I located right now?" ) 再次运行代码,我们将看到一个 functionResponse 对象,里面填充了用户现在所处位置的位置。就我而言,那是北京。

getCurrentWeather 调用时候生成的链接如下:api.open-meteo.com/v1/forecast…

我们将此数据添加到数组中的新项中 messages ,其中我们还指定了我们调用的函数的名称。

messages.push({
  role: "function",
  name: functionName,
  content: `最后一个函数的结果是这样的:: ${JSON.stringify(
    functionResponse
  )}
  `,
});

请注意,设置为 role "function" 。这告诉 OpenAI,该参数包含函数调用的结果, content 而不是用户的输入。

此时,我们需要使用这个更新 messages 的数组向 OpenAI 发送一个新请求。但是,我们不想对新的函数调用进行硬编码,因为我们的代理可能需要在自身和 GPT 之间来回切换几次,直到它为用户找到最终答案。

这可以通过几种不同的方式解决,例如递归、while 循环或 for 循环。为了简单起见,我们将使用一个好的旧 for 循环。

创建循环

在 agent 函数的顶部,我们将创建一个循环,让我们最多可以运行整个过程五次。

如果我们从 GPT 返回 finish_reason: "function_call" ,我们只会将函数调用的结果推送到数组并 messages 跳转到循环的下一次迭代,从而触发新的请求。

如果我们回来 finish_reason: "stop" ,那么 GPT 已经找到了合适的答案,所以我们将返回函数并取消循环。

for (let i = 0; i < 1; i++) {
    const response = await fetchOpenAI({
      model: "gpt-4-1106-preview",
      messages: messages,
      functions: functionDefinitions,
    });
    const { finish_reason, message } = response.choices[0];
    console.log("response:", message);
 
    if (finish_reason === "function_call") {
      const functionName = message.function_call.name;
      const functionToCall = availableFunctions[functionName];
      const functionArgs = JSON.parse(message.function_call.arguments);
      const functionArgsArr = Object.values(functionArgs);
      const functionResponse = await functionToCall.apply(
        null,
        functionArgsArr
      );
 
      messages.push({
        role: "function",
        name: functionName,
        content: `
                最后一个函数的结果是这样的: ${JSON.stringify(
                  functionResponse
                )}
                `,
      });
    } else if (finish_reason === "stop") {
      messages.push(message);
      return message.content;
    }
  }
  return "已达到最大迭代次数但没有合适的答案。请使用更具体的输入重试。";

如果我们在五次迭代中没有看到 a finish_reason: "stop" ,我们将返回一条消息,说我们找不到合适的答案。

运行最终应用

在这一点上,我们准备尝试我们的应用程序!我会要求程序根据我的位置和当前的天气建议一些活动。

const response = await agent(
  "请根据我的位置和天气建议一些活动。需要返回天气的信息。"
);
console.log("response:", response);

以下是我们在控制台中看到的内容(格式化以使其更易于阅读):

使用 openai Function calling 实现天气查询

如果我们在引擎盖下达到顶峰,并在循环的每次迭代中注销 response.choices[0].message ,我们会看到 GPT 在得出答案之前已经指示我们使用这两个函数。

{role: "assistant", content: null, function_call: {name: "getLocation", arguments: "{}"}}
{role: "assistant", content: null, function_call: {name: "getCurrentWeather", arguments: " { "longitude": "39.911", "latitude": "116.395" }"}}

现在,您已经使用 OpenAI 函数和 Node.js SDK 构建了 AI 代理!如果您正在寻找额外的挑战,请考虑增强此应用程序。例如,您可以添加一个函数,用于获取有关用户所在位置的事件和活动的最新信息。

Complete code 完整代码

async function fetchOpenAI(body) {
  const response = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer sk-xxxxxx`
    },
    body: JSON.stringify({
      max_tokens: 2048,
      ...body
    })
  });
  const data = await response.json();
  console.log("data:", data);
  return data;
}

async function getLocation() {
  const response = await fetch("https://ipapi.co/json/");
  const locationData = await response.json();
  return locationData;
}
 
async function getCurrentWeather(latitude, longitude) {
  const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&hourly=apparent_temperature`;
  const response = await fetch(url);
  const weatherData = await response.json();
  return weatherData;
}
 
const functionDefinitions = [
  {
    name: "getCurrentWeather",
    description:
      "获取给定位置的当前天气(以纬度和经度表示)",
    parameters: {
      type: "object",
      properties: {
        latitude: {
          type: "string",
        },
        longitude: {
          type: "string",
        },
      },
      required: ["longitude", "latitude"],
    },
  },
  {
    name: "getLocation",
    description: "根据 IP 地址获取用户的位置",
    parameters: {
      type: "object",
      properties: {},
    },
  },
];
 
const availableFunctions = {
  getCurrentWeather,
  getLocation,
};
 
const messages = [
  {
    role: "system",
    content: `你是一个有用的助手。仅使用为您提供的功能`,
  },
];
 
async function agent(userInput) {
  messages.push({
    role: "user",
    content: userInput,
  });
 
  for (let i = 0; i < 1; i++) {
    const response = await fetchOpenAI({
      model: "gpt-4-1106-preview",
      messages: messages,
      functions: functionDefinitions,
    });
    const { finish_reason, message } = response.choices[0];
    console.log("response:", message);
 
    if (finish_reason === "function_call") {
      const functionName = message.function_call.name;
      const functionToCall = availableFunctions[functionName];
      const functionArgs = JSON.parse(message.function_call.arguments);
      const functionArgsArr = Object.values(functionArgs);
      const functionResponse = await functionToCall.apply(
        null,
        functionArgsArr
      );
 
      messages.push({
        role: "function",
        name: functionName,
        content: `
                最后一个函数的结果是这样的: ${JSON.stringify(
                  functionResponse
                )}
                `,
      });
    } else if (finish_reason === "stop") {
      messages.push(message);
      return message.content;
    }
  }
  return "已达到最大迭代次数但没有合适的答案。请使用更具体的输入重试。";
}
 
const response = await agent(
  "请根据我的位置和天气建议一些活动。需要返回天气的信息。"
);
 
console.log("response:", response);
转载自:https://juejin.cn/post/7301574373789515811
评论
请登录