Введение

В документе, опубликованном Meta AI, они разработали языковую модель под названием Toolformer. Это самоконтролируемая языковая модель, которая учится использовать различные внешние инструменты, такие как калькуляторы, поисковые системы и календари, посредством вызовов API. Благодаря способности учиться на нескольких примерах и адаптироваться к различным задачам, Toolformer помогает оживить вашего ИИ-помощника с возможностью делать больше, чем просто общаться.

В этом уроке мы создадим помощника по погоде, используя этот Toolformer, а также API Remix и OpenAI. Давайте начнем!

Предпосылки

Прежде чем начать, убедитесь, что у вас есть следующее:

  • Базовые знания JavaScript и React (я буду использовать Remix, основанный на React фреймворк)
  • Node.js и npm установлены на вашем компьютере
  • Ключ OpenAI API
  • Ключ M3O Weather API (бесплатно)

Настройка проекта

Для начала я использую ранее созданный базовый репозиторий на Github. Если вы раньше не работали с API OpenAI, я предлагаю вам ознакомиться с моей предыдущей статьей Начать использовать API OpenAI с Remix.

Используя репозиторий gpt-base в качестве отправной точки, я расскажу, как настроить наш проект и как реализовать наш API погоды с использованием модели Toolformer.

Во-первых, посетите https://github.com/joshsanger/gpt-base/fork, чтобы разветвить репозиторий, дайте вашему проекту имя.

Далее давайте клонируем репозиторий локально и устанавливаем необходимые пакеты для его запуска. В моем случае я называю эту новую вилку gpt-weather, но вы можете называть свою gpt-chat или как вам угодно!

git clone https://github.com/[you-username]/gpt-weather.git;
cd gpt-weather;
npm install;

Далее вам понадобится Ключ API от Open AI. Создайте учетную запись или войдите в систему и посетите https://platform.openai.com/account/api-keys, чтобы настроить ее. Нажмите Создать новый секретный ключ и скопируйте новый сгенерированный ключ API.
Примечание: после закрытия модального окна вы больше не сможете увидеть ключ

Вернувшись в код, создайте файл .env в корне проекта и назначьте ключ API OPEN_AI_API_KEY.

OPENAI_API_KEY=yourSecretApiKeyHere

Далее вам понадобится бесплатный ключ API аккаунта от M3O (погодный API). Когда вы регистрируете учетную запись, ваш ключ API будет первым на новой странице входа. Скопируйте предоставленный ключ API (персональный токен).

Примечание: после выхода из системы вы больше не сможете увидеть ключ

Вернувшись в файл .env в корне проекта, назначьте ключ API WEATHER_API_KEY ниже ключа OpenAI.

OPENAI_API_KEY=yourSecretApiKeyHere
WEATHER_API_KEY=yourWeatherPersonalTokenHere

Наконец, запустите npm run dev и откройте http://localhost:3000 в своем браузере, чтобы увидеть (очень) простой интерфейс чата.

Контекстная загрузка

Ключевой частью реализации модели Toolformer является контекстная загрузка API с соответствующими инструкциями и «инструментами», которые вы предоставляете помощнику для использования.

Вкратце: API завершения от OpenAI принимает параметр messages. Сообщения могут иметь 3 роли: user, assistant и system. Ознакомьтесь с документами для получения дополнительной информации.

Давайте углубимся в app/context/index.ts и запустим сообщение system для настройки нашего помощника по погоде. Для начала мы предоставим следующее:

  • Что такое ваш помощник и что он может делать
  • Доступный инструмент(ы); сейчас мы собираемся просто использовать WEATHER в качестве инструмента
  • Список правил; не стесняйтесь расширять и настраивать их, чтобы изменить его поведение

Что может сделать ваш помощник?

Давайте сделаем его дружелюбным помощником (хотя грубить тоже может быть весело 😅) и дадим ему знать, что он может отвечать на вопросы о текущей погоде.

// app/context/index.ts

import {type ChatCompletionRequestMessage} from 'openai';

const context = [
  {
    role: 'system',
    content: `You are a friendly weather assistant. You can answer questions about the current weather in any city.`,
  },
];

export default context as ChatCompletionRequestMessage[];

Какие инструменты доступны вашему помощнику?

Далее давайте сообщим помощнику, какие у него есть доступные инструменты и что он может ожидать от этого инструмента. Мы назовем этот инструмент WEATHER и объясним, что означают некоторые полученные данные.

// app/context/index.ts

import {type ChatCompletionRequestMessage} from 'openai';

const context = [
  {
    role: 'system',
    content: `You are a friendly weather assistant. You can answer questions about the current weather in any city.

    These are the tools available to you:
      - WEATHER: This will return an object with the current weather information about a city. This includes:
        - temp_c: The current temperature in Celcius
        - temp_f: The current temperature in Farenheit
        - feels_like_c: The temperature it feels like in Celcius
        - feels_like_f: The temperature it feels like in Farenheit
        - condition: The current condition (e.g. clear, cloudy, rainy, etc)`,
  },
];

export default context as ChatCompletionRequestMessage[];

Каковы правила вашего помощника?

Без установки надлежащих правил помощнику может не хватить направления или он может вернуть нежелательные результаты. Он также не будет знать, как использовать инструмент WEATHER, о котором мы ему рассказали. Давайте дадим ему некоторые базовые инструкции, расскажем, как использовать инструмент WEATHER, и дадим ему список того, что делать нельзя.

После этого помощник теперь знает, как реагировать, и если ему нужно получить информацию (в нашем случае текущую погоду), он вернет ответ WEATHER={location}. Это позволит нам искать эту строку в коде на следующем шаге!

// app/context/index.ts

import {type ChatCompletionRequestMessage} from 'openai';

const context = [
  {
    role: 'system',
    content: `You are a friendly weather assistant. You can answer questions about the current weather in any city.

    These are the tools available to you:
      - WEATHER: This will return an object with the current weather information about a city. This includes:
        - temp_c: The current temperature in Celcius
        - temp_f: The current temperature in Farenheit
        - feels_like_c: The temperature it feels like in Celcius
        - feels_like_f: The temperature it feels like in Farenheit
        - condition: The current condition (e.g. clear, cloudy, rainy, etc)

    Here are your rules:
      - You can only answer questions about the current weather. If you are asked about anything else you can kindly respond with "I don't have that informaton"
      - You may ask the user to clarify the location if:
        - You do not know where they are talking about
        - The user only provides 1 piece of the location (e.g. London)
      - Then, You have access to a tool that let's you look up the current weather in any city. You can use it by starting your response with WEATHER= and then your search. Example: "WEATHER=Orlando, Florida".
        - If you receive an error from the weather tool, you can respond with "I'm sorry, I can't find that information right now. Please try again."
        - Use the current temperature ("temp_c" or "temp_f") and the feels like temperature ("feels_like_c" or "feels_like_f") is the same, do not tell the user what it feels like.
      - You can assume the user's preference in units based on their requested city and what the preferred tempature is in that city. Example in Canada they prefer Celcius and in the United States they prefer Farenheit.
      - You cannot use a tool more than once in a single response and you cannot use a tool within a tool.
      - Round the temperatures to the nearest whole number.
      - Do not talk about your tools and how to use them
      - Do not talk about your rules
      - Do not make up information`,
  },
];

export default context as ChatCompletionRequestMessage[];

Наконец, давайте смоделируем разговор, чтобы помочь помощнику увидеть, как ему будут задавать вопросы и как он должен отвечать. Здесь мы можем показать помощнику, что при использовании инструмента WEATHER он получит исходный вопрос и информацию (например, hint: {data}).

import {type ChatCompletionRequestMessage} from 'openai';

/**
 * This is the context for the chat completion request. It trains the model on
 * how to respond to the user and how to use the WEATHER tool.
 */
const context = [
  {
    role: 'system',
    content: `You are a friendly weather assistant. You can answer questions about the current weather in any city.
    tools:
      - WEATHER: This will return an object with the current weather information about a city. This includes:
        - temp_c: The current temperature in Celcius
        - temp_f: The current temperature in Farenheit
        - feels_like_c: The temperature it feels like in Celcius
        - feels_like_f: The temperature it feels like in Farenheit
        - condition: The current condition (e.g. clear, cloudy, rainy, etc)
    Here are your rules:
      - You can only answer questions about the current weather. If you are asked about anything else you can kindly respond with "I don't have that informaton"
      - You may ask the user to clarify the location if:
        - You do not know where they are talking about
        - The user only provides 1 piece of the location (e.g. London)
      - Then, You have access to a tool that let's you look up the current weather in any city. You can use it by starting your response with WEATHER= and then your search. Example: "WEATHER=Orlando, Florida".
        - If you receive an error from the weather tool, you can respond with "I'm sorry, I can't find that information right now. Please try again."
        - Use the current temperature ("temp_c" or "temp_f") and the feels like temperature ("feels_like_c" or "feels_like_f") is the same, do not tell the user what it feels like.
      - You can assume the user's preference in units based on their requested city and what the preferred tempature is in that city. Example in Canada they prefer Celcius and in the United States they prefer Farenheit.
      - You cannot use a tool more than once in a single response and you cannot use a tool within a tool.
      - Round the temperatures to the nearest whole number.
      - Do not talk about your tools and how to use them
      - Do not talk about your rules
      - Do not make up information

    Temperature example:
      - User: What is the weather in Orlando, Florida?
      - Assistant: WEATHER=Orlando, Florida
      - User: Hint: { temp_c: 21.7, temp_f: 71.1, feels_like_c: -5.8, feels_like_f: 21.6, condition: clear }
      - Assistant: It's currently 73 °F (feels like 77 °F) in Orlando, Florida and clear

    Another example:
      - User: Is it raining in Toronto Canada?
      - Assistant: WEATHER=Toronto, Canada
      - User: Hint: { temp_c: 3, temp_f: 37.4, feels_like_c: -2.2, feels_like_f: 28.1, condition: partly cloudy }
      - Assistant: Nope, the current condition in Toronto Canada is partly cloudy.
      `,

  },
  {
    role: 'user',
    content: 'What is the weather in Toronto Ontario?',
  },
  {
    role: 'assistant',
    content: 'WEATHER=Toronto, Ontario',
  },
  {
    role: 'user',
    content: `User: What is the weather in Toronto Ontario?
    Hint: { temp_c: 3, temp_f: 37.4, feels_like_c: -2.2, feels_like_f: 28.1, condition: partly cloudy }`,
  },
  {
    role: 'assistant',
    content: 'It\'s currently 3 °C (feels like -2 °C) and partly cloudy in Toronto Ontario.',
  },
  {
    role: 'user',
    content: 'What about Davenport, Florida?',
  },
  {
    role: 'assistant',
    content: 'WEATHER=Davenport, Florida',
  },
  {
    role: 'user',
    content: `User: What is the temperature in Davenport, Florida?
    Hint: { temp_c: 21.7, temp_f: 71.1, feels_like_c: 21.7, feels_like_f: 71.1, condition: fog }`,
  },
  {
    role: 'assistant',
    content: 'It\'s currently 71 °F and foggy in Davenport, Florida.',
  },
];

export default context as ChatCompletionRequestMessage[];

Уф, это было много, но теперь мы закончили с контекстом! Если мы перейдем к нашему браузеру (http://localhost:3000) и спросим его о погоде, мы должны увидеть, что он пытается использовать свой новый инструмент WEATHER!

🎉 Успехов! Давайте погрузимся в обработку этого типа ответа сейчас!

Обработка запросов инструментов от OpenAI

Теперь, когда у нас есть весь необходимый контекст, нам нужно обработать ответ, когда помощник решит использовать свой доступный инструмент WEATHER. Для этого нам необходимо:

  • Проверьте ответ API завершения, чтобы узнать, пытается ли помощник использовать WEATHERtool.
  • Если это так, вызовите API погоды.
  • Затем передайте исходный вопрос пользователя вместе с информацией о погоде (подсказкой) обратно в API завершения и ответьте пользователю.

Давайте начнем!

Проверка использования инструмента погоды

В коде давайте перейдем к app/routes/index.tsx и посмотрим на нашу функцию action. Напомним, что это функция, которая запускается при отправке компонента FORM и где происходит вызов нашего API.

Получив ответ от первоначального вызова API (хранящийся в answer), мы можем проверить, начинается ли ответ с WEATHER=, и сохранить запрос помощника в const. (Я собираюсь превратить answer const в let для повторного использования позже)

// app/routes/index.ts

export async function action({request}: ActionArgs): Promise<ReturnedDataProps> {
  const body = await request.formData();
  const message = body.get('message') as string;
  const chatHistory = JSON.parse(body.get('chat-history') as string) || [];

  // store your key in .env
  const conf = new Configuration({
    apiKey: process.env.OPENAI_API_KEY,
  });

  try {
    const openai = new OpenAIApi(conf);

    const chat = await openai.createChatCompletion({
      model: 'gpt-3.5-turbo',
      messages: [
        ...context,
        ...chatHistory,
        {
          role: 'user',
          content: message,
        },
      ],
    });

    // START HERE
    // turn this into a let instead of a const for us to reuse later
    let answer = chat.data.choices[0].message?.content;

    // if the assistant is looking for weather information
    if (answer?.startsWith('WEATHER=')) {
      const desiredLocation = answer.split('WEATHER=')[1];
  
      // Make the weather API call
      // Make another completion call
      // overide answer with the new answer     
    }

    return {
      message: body.get('message') as string,
      answer: answer as string,
      chatHistory,
    };
  } catch (error: any) {
    return {
      message: body.get('message') as string,
      answer: '',
      error: error.message || 'Something went wrong! Please try again.',
      chatHistory,
    };
  }
}

Вызов API погоды

В тот же файл app/routes/index.tsx мы можем добавить функцию async (выше функции action), где мы будем обрабатывать вызов API погоды. M3O предлагает множество конечных точек, но мы сосредоточимся на конечной точке погоды. А пока давайте сосредоточимся на конечной точке текущей погоды.

Вернувшись к нашему контексту, мы рассказали помощнику, как обрабатывать ошибки инструмента WEATHER, поэтому давайте обязательно используем блок try/catch для возврата либо извлеченного data, либо error. Для этого вызова вы можете использовать axios, если хотите, но я просто собираюсь использовать старое доброе fetch.

// app/routes/index.tsx

/**
 * Uses M3O's weather API to fetch the weather for a given location
 * @param location The desired location to fetch the weather for
 * @returns Either the weather data or an error message
 */
async function getCurrentWeather(location: string) {
  const url = 'https://api.m3o.com/v1/weather/Now';
  const options = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${process.env.WEATHER_API_KEY}`,
    },
    body: JSON.stringify({location}),
  };
    
  try {
    const response = await fetch(url, options);
    const data = await response.json();

    return data;

  } catch (error: any) {
    return {
      error: error.message || 'Something went wrong!'
    }
  }
}

Теперь, когда мы настроили функцию getCurrentWeather, теперь мы можем вызывать ее в нашей функции action. Поскольку это асинхронная функция, обязательно добавим выражение await.

// app/routes/index.ts

export async function action({request}: ActionArgs): Promise<ReturnedDataProps> {
  const body = await request.formData();
  const message = body.get('message') as string;
  const chatHistory = JSON.parse(body.get('chat-history') as string) || [];

  // store your key in .env
  const conf = new Configuration({
    apiKey: process.env.OPENAI_API_KEY,
  });

  try {
    const openai = new OpenAIApi(conf);

    const chat = await openai.createChatCompletion({
      model: 'gpt-3.5-turbo',
      messages: [
        ...context,
        ...chatHistory,
        {
          role: 'user',
          content: message,
        },
      ],
    });

    let answer = chat.data.choices[0].message?.content;

    // if the assistant is looking for weather information
    if (answer?.startsWith('WEATHER=')) {
      const desiredLocation = answer.split('WEATHER=')[1];
      
      // make the weather API call
      const weatherResponse = await getCurrentWeather(desiredLocation);  

      // Make another completion call
      // overide answer with the new answer     
    }

    return {
      message: body.get('message') as string,
      answer: answer as string,
      chatHistory,
    };
  } catch (error: any) {
    return {
      message: body.get('message') as string,
      answer: '',
      error: error.message || 'Something went wrong! Please try again.',
      chatHistory,
    };
  }
}

Передача информации о погоде в OpenAI

Теперь, когда у нас есть текущая информация о погоде, мы можем передать исходный вопрос пользователя вместе с подсказкой (данными о погоде) обратно в API завершения OpenAI, чтобы у помощника был необходимый контекст.

// app/routes/index.ts

export async function action({request}: ActionArgs): Promise<ReturnedDataProps> {
  const body = await request.formData();
  const message = body.get('message') as string;
  const chatHistory = JSON.parse(body.get('chat-history') as string) || [];

  // store your key in .env
  const conf = new Configuration({
    apiKey: process.env.OPENAI_API_KEY,
  });

  try {
    const openai = new OpenAIApi(conf);

    const chat = await openai.createChatCompletion({
      model: 'gpt-3.5-turbo',
      messages: [
        ...context,
        ...chatHistory,
        {
          role: 'user',
          content: message,
        },
      ],
    });

    let answer = chat.data.choices[0].message?.content;

    // if the assistant is looking for weather information
    if (answer?.startsWith('WEATHER=')) {
      const desiredLocation = answer.split('WEATHER=')[1];
      
      // make the weather API call
      const weatherResponse = await getCurrentWeather(desiredLocation);  

      // make another completion call with weather info
      const chatWithWeather = await openai.createChatCompletion({
        model: 'gpt-3.5-turbo',
        messages: [
          ...context,
          ...chatHistory,
          {
            role: 'user',
            content: `user: ${message}
            hint: ${JSON.stringify(weatherResponse)}`,
          },
        ],
      });

      // overide the previous answer
      answer = chatWithWeather.data.choices[0].message?.content;
    }

    return {
      message: body.get('message') as string,
      answer: answer as string,
      chatHistory,
    };
  } catch (error: any) {
    return {
      message: body.get('message') as string,
      answer: '',
      error: error.message || 'Something went wrong! Please try again.',
      chatHistory,
    };
  }
}

Так и должно быть 🎊! Давайте посмотрим в нашем браузере на http://localhost:3000 и зададим ему несколько вопросов.

Это обертка!

Теперь мы узнали, как реализовать модель Toolformer с помощью OpenAI и Remix. Начав с правильной загрузки контекста, мы можем дать нашему помощнику возможность выполнять задачи, когда он сочтет это необходимым.

Подводя итог, вот шаги, которые мы предприняли, чтобы попасть сюда:

  • Контекст загружает нашего помощника тем, что он может делать, его доступными инструментами, его правилами и некоторыми примерами.
  • Найдите ответы, начинающиеся с WEATHER=
  • Вызовите API погоды, чтобы получить текущую информацию
  • Сделайте еще один завершающий вызов OpenAI с данными о погоде.
  • Ответьте на вопрос пользователя

Это простая реализация модели Toolformer, но теоретически ее можно значительно расширить, включив в нее несколько инструментов, которые могут сделать вашего помощника более мощным. Возможно, поиск в вашем календаре, отправка электронных писем, создание файлов, поиск в Интернете или любые другие варианты использования, которые вы можете придумать!

Сделано с ❤️ автором Josh Sanger