Преобразование конвейеров данных с помощью функции вызова функций OpenAI: реализация рабочего процесса отправки электронной почты с использованием PostgreSQL и FastAPI

Введение

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

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

Представьте себе ИИ службы поддержки клиентов, который может отвечать на вопросы и выполнять такие действия, как запись на прием, отправка информации на адрес электронной почты или обновление сведений о клиенте в режиме реального времени. Или рассмотрим конвейер данных, где агент LLM может извлекать, обновлять и манипулировать данными по команде.

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

Как всегда, код доступен на моем Github.

Понимание новых возможностей вызова функций и сравнение их с LangChain

Мы хотим понять, что новая функция вызова функций OpenAI привносит в гонку ИИ. Для этого давайте разберемся, что отличает его от других инструментов и фреймворков на рынке, таких как LangChain. Мы уже представили LangChain в первой статье этой серии. Это популярный фреймворк для разработки приложений на базе ИИ. Функция вызова функций и LangChain привносят в таблицу уникальные преимущества и возможности и построены на том, чтобы сделать ИИ более удобным, универсальным и динамичным.

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

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

Подход LangChain облегчает создание приложений, в которых LLM может сохранять свое состояние при различных взаимодействиях, последовательности и цепочке вызовов различных утилит и даже взаимодействовать с внешними источниками данных. Мы можем рассматривать их как возможности вызова функций на стероидах. Таким образом, это особенно полезно для разработчиков, создающих сложные многоэтапные приложения, использующие языковые модели. Недостатком дополнительной сложности является то, что она создает более крутую кривую обучения, чтобы использовать ее в полной мере.

Вариант использования — преобразование конвейеров данных

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

Традиционно запросы к базе данных требуют специальных знаний языков запросов, таких как SQL. Благодаря возможности LLM напрямую вызывать функции, службы и базы данных, пользователи могут извлекать данные в диалоговом режиме без необходимости в явной формулировке запроса. LLM может преобразовывать запрос пользователя в запрос к базе данных, извлекать данные и возвращать их в удобном для пользователя формате в режиме реального времени. Эта функция может упростить доступ к данным между различными ролями в организации.

Еще один аспект, который может измениться, — преобразование данных. Перед анализом часто требуются отдельные этапы очистки и обработки данных. LLM может упростить этот процесс, выполняя задачи очистки и обработки данных в интерактивном режиме на основе инструкций пользователя. Более того, манипулирование данными в реальном времени во время разговора позволяет проводить более исследовательский и итеративный анализ данных.

Третий вариант использования — мониторинг данных. Он включает в себя регулярные проверки для обеспечения точности и согласованности данных в конвейере данных. С LLM задачи мониторинга могут стать более интерактивными и эффективными. Например, LLM может предупреждать пользователей о несоответствиях данных во время разговоров и немедленно предпринимать корректирующие действия.

Наконец, LLM также могут автоматизировать создание и распространение отчетов о данных. Пользователи могут поручить LLM создать отчет на основе определенных критериев, а LLM может извлекать данные, создавать отчет и даже отправлять его соответствующим получателям.

Создание конвейера отправки электронной почты с возможностями вызова функций OpenAI

Мы стремимся создать конвейер, который отправляет электронное письмо пользователю. Хотя это может показаться простым, красота этого процесса заключается во взаимодействии различных компонентов, контролируемых LLM. Модель ИИ не просто генерирует тело письма; он динамически взаимодействует с базой данных для получения информации о пользователе, составляет соответствующее контексту электронное письмо, а затем дает указание службе отправить его.

Наш конвейер состоит из трех основных компонентов: PostgreSQL, FastAPI и OpenAI LLM. Мы используем PostgreSQL для хранения наших пользовательских данных. Эти данные включают имена пользователей и связанные с ними адреса электронной почты. Он служит нашим источником достоверной информации о пользователях. FastAPI — это современная высокопроизводительная веб-инфраструктура для создания API с помощью Python. Мы используем службу FastAPI для моделирования процесса отправки электронной почты. Когда служба получает запрос на отправку электронного письма, она возвращает ответ, подтверждающий отправку электронного письма. LLM служит организатором всего процесса. Он контролирует диалог, определяет необходимые действия в зависимости от контекста, взаимодействует с базой данных PostgreSQL для получения информации о пользователе, создает сообщение электронной почты и дает указание службе FastAPI отправить электронное письмо.

Реализация базы данных PostgreSQL

Первым компонентом нашего пайплайна является база данных PostgreSQL, в которой мы храним наши пользовательские данные. Настройка экземпляра PostgreSQL стала простой и воспроизводимой с помощью Docker — платформы, которая позволяет нам контейнеризовать и изолировать среду нашей базы данных.

Сначала вам нужно установить Docker, чтобы настроить контейнер PostgreSQL Docker. После установки вы можете извлечь образ PostgreSQL и запустить его как контейнер. Мы сопоставляем порт 5432 контейнера с портом 5432 хоста для доступа к базе данных. В производственной среде задайте свой пароль как переменную среды и не устанавливайте его непосредственно в команде, как показано ниже. Мы делаем это, чтобы ускорить наш процесс.

docker run --name user_db -e POSTGRES_PASSWORD=testpass -p 5432:5432 -d postgres

Теперь, когда наш экземпляр PostgreSQL запущен, мы можем создать базу данных и таблицу для хранения наших пользовательских данных. Мы воспользуемся сценарием инициализации, чтобы создать usersтаблицу с username и emailстолбцами и заполнить ее некоторыми фиктивными данными. Этот скрипт помещается в каталог, который затем сопоставляется с каталогом /docker-entrypoint-initdb.d в контейнере. PostgreSQL выполняет все сценарии, найденные в этом каталоге, при запуске. Вот как выглядит скрипт (user_init.sql):

CREATE DATABASE user_database;
\c user_database;

CREATE TABLE users (
    username VARCHAR(50),
    email VARCHAR(50)
);

INSERT INTO users (username, email) VALUES
    ('user1', '[email protected]'),
    ('user2', '[email protected]'),
    ('user3', '[email protected]'),
    ...
    ('user10', '[email protected]');

LLM способен понимать команды SQL и может использоваться для запросов к базе данных PostgreSQL. Когда LLM получает запрос на получение пользовательских данных, он может сформулировать SQL-запрос для получения необходимых данных из базы данных.

Например, если вы попросите LLM отправить электронное письмо на user10, LLM может сформулировать запрос:

SELECT  email FROM users WHERE username=’user10'; 

Это позволяет получить user10 адрес электронной почты из таблицы users. Затем LLM может использовать этот адрес электронной почты, чтобы дать указание службе FastAPI отправить электронное письмо.

В следующем разделе мы проведем вас через реализацию службы FastAPI, которая отправляет электронные письма.

Создание службы электронной почты FastAPI

Наш второй компонент — это служба FastAPI. Этот сервис будет имитировать процесс отправки электронных писем. Это простой API, который получает запрос POST, содержащий имя получателя, адрес электронной почты и текст сообщения. Он вернет ответ, подтверждающий отправку электронного письма. Мы снова будем использовать Docker, чтобы обеспечить изоляцию и воспроизводимость нашего сервиса.

Во-первых, вам нужно установить Docker (если он еще не установлен). Затем создайте новый каталог для службы FastAPI и перейдите в него. Здесь создайте новый файл Python (например, main.py) и добавьте следующий код:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class User(BaseModel):
    name: str
    email: str
    body: str


@app.post("/send_email")
async def send_email(user: User):
    return {
        "message": f"Email successfully sent to {user.name} with email {user.email}. Email body:\n\n{user.body}"
    }

Этот код определяет приложение FastAPI с одной конечной точкой /send_email/. Эта конечная точка принимает запросы POST и ожидает тело JSON, содержащее имя получателя, адрес электронной почты и тело сообщения.

Затем создайте Dockerfile в том же каталоге со следующим содержимым:

FROM python:3.9-slim-buster

WORKDIR /app
ADD . /app

RUN pip install --no-cache-dir fastapi uvicorn

EXPOSE 1000

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "1000"]

Этот Dockerfile указывает Docker создать образ на основе образа thepython:3.9-slim-buster, легкого образа, идеально подходящего для эффективного запуска приложений Python. Затем он копирует наш файл main.py в каталог /app/ образа.

Вы можете собрать образ Docker с помощью команды:

docker build -t fastapi_email_service .

А затем запустите его с помощью:

docker run -d -p 1000:1000 fastapi_email_service

LLM взаимодействует со службой FastAPI с помощью запроса POST. Когда LLM решает отправить электронное письмо, он генерирует вызов функции send_email. Аргументы вызова этой функции содержат имя, адрес электронной почты и текст сообщения.

Вызов функции обрабатывается нашим скриптом Python, который извлекает аргументы функции и использует их для отправки запроса POST в нашу службу FastAPI. Служба FastAPI отвечает сообщением об успешной отправке сообщения электронной почты.

Теперь у нас есть все компоненты нашего пайплайна. В следующем разделе мы свяжем все вместе и объясним, как LLM организует взаимодействие между базой данных PostgreSQL и службой FastAPI для отправки электронной почты.

Интеграция с OpenAI LLM

Последняя часть нашего конвейера — интеграция OpenAI LLM. LLM выступает в роли оркестратора, интерпретируя наши команды, запрашивая информацию о пользователях в базе данных и предписывая службе FastAPI отправлять электронные письма.

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

Вот функция chat_completion_request, которую мы используем для отправки запроса к API:

@retry(wait=wait_random_exponential(min=1, max=40), stop=stop_after_attempt(3))
def chat_completion_request(messages, functions=None, model=GPT_MODEL):
    headers = {
        "Content-Type": "application/json",
        "Authorization": "Bearer " + openai.api_key,
    }
    json_data = {"model": model, "messages": messages}
    if functions is not None:
        json_data.update({"functions": functions})

    response = requests.post(
        "https://api.openai.com/v1/chat/completions",
        headers=headers,
        json=json_data,
    )
    return response

Мы используем класс Chat для управления историей разговоров. У него есть методы для добавления нового сообщения в историю и отображения всей беседы:

class Chat:
    def __init__(self):
        self.conversation_history = []

    def add_prompt(self, role, content):
        message = {"role": role, "content": content}
        self.conversation_history.append(message)

    def display_conversation(self):
        for message in self.conversation_history:
            print(f"{message['role']}: {message['content']}")

В нашем случае LLM должен взаимодействовать с нашей базой данных PostgreSQL и службой FastAPI. Мы определяем эти функции и включаем их в наш запрос на завершение. Вот как мы определяем наши функции sql_query_email и send_email:

functions = [
    {
        "name": "send_email",
        "description": "Send a new email",
        "parameters": {
            "type": "object",
            "properties": {
                "to": {
                    "type": "string",
                    "description": "The destination email.",
                },
                "name": {
                    "type": "string",
                    "description": "The name of the person that will receive the email.",
                },
                "body": {
                    "type": "string",
                    "description": "The body of the email.",
                },
            },
            "required": ["to", "name", "body"],
        },
    },
    {
        "name": "sql_query_email",
        "description": "SQL query to get user emails",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "The query to get users emails.",
                },
            },
            "required": ["query"],
        },
    },
]

Когда мы отправляем запрос на завершение, LLM отвечает запланированными действиями. Если ответ включает вызов функции, наш сценарий выполняет эту функцию. Например, если LLM решит вызвать функцию sql_query_email, наш скрипт извлечет адрес электронной почты пользователя из базы данных, а затем добавит результат в историю разговоров. Когда вызывается функция send_email, наш скрипт отправляет электронное письмо с помощью службы FastAPI.

Основной цикл нашего скрипта проверяет вызовы функций в ответе LLM и действует соответствующим образом:

chat = Chat()
chat.add_prompt("user", "Send an email to user10 saying that he needs to pay the monthly subscription fee.")
result_query = ''

for i in range(2):
    chat_response = chat_completion_request(
        chat.conversation_history,
        functions=functions
    )
    response_content = chat_response.json()['choices'][0]['message']

    if 'function_call' in response_content:
        if response_content['function_call']['name'] == 'send_email':
            res = json.loads(response_content['function_call']['arguments'])
            send_email(res['name'], res['to'], res['body'])
            break
        elif response_content['function_call']['name'] == 'sql_query_email':
            result_query = query_db(json.loads(response_content['function_call']['arguments'])['query'])
            chat.add_prompt('user', str(result_query))
    else:
        chat.add_prompt('assistant', response_content['content'])

Когда мы запускаем скрипт, мы получаем следующий вывод:

{
  "message": "Email successfully sent to User 10 with email [email protected].",
  "Email body": "\n\nDear User 10, \n\nThis is a reminder that your monthly subscription fee is due. Please make the payment as soon as possible to ensure uninterrupted service. Thank you for your cooperation. \n\nBest regards, \nYour Subscription Service Team"
}

Давайте разберем, что произошло для нас, чтобы получить этот вывод. Наша подсказка была «Отправить электронное письмо пользователю 10, в котором говорится, что ему необходимо оплатить ежемесячную абонентскую плату». Обратите внимание, что в нашем сообщении нет информации об электронном письме user10. LLM определил недостающую информацию и понял, что наша функция query_email позволит ему запросить базу данных, чтобы получить электронное письмо от этого пользователя. Получив электронное письмо, он снова сделал две вещи правильно: во-первых, он должен сгенерировать тело электронного письма, а во-вторых, он должен вызвать функцию send_email, чтобы активировать электронное письмо с помощью службы электронной почты FastAPI.

Заключение

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

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

Хроники больших языковых моделей: навигация по рубежу НЛП

Эта статья относится к новой еженедельной серии статей «Хроники больших языковых моделей: Навигация по границам НЛП», в которых рассказывается, как использовать возможности больших моделей для различных задач НЛП. Погружаясь в эти передовые технологии, мы стремимся дать разработчикам, исследователям и энтузиастам возможность использовать потенциал НЛП и открывать новые возможности.

Статьи, опубликованные на данный момент:

  1. Подведение итогов последних выпусков Spotify с ChatGPT
  2. Мастер семантического поиска в масштабе: индексируйте миллионы документов с молниеносным временем вывода с использованием FAISS и преобразователей предложений
  3. Раскройте потенциал аудиоданных: расширенная транскрипция и диаризация с помощью Whisper, WhisperX и PyAnnotate
  4. Whisper JAX против PyTorch: раскрытие правды о производительности ASR на графических процессорах
  5. Vosk для эффективного распознавания речи корпоративного уровня: руководство по оценке и внедрению
  6. Тестирование модели массовой многоязычной речи (MMS), поддерживающей 1162 языка
  7. Использование модели Falcon 40B, самого мощного LLM с открытым исходным кодом

Оставайтесь на связи: LinkedIn