Векторные представления слов Дающие вашему чат-боту контекст для более точных ответов

Векторные представления слов для точных ответов чат-бота

Нет сомнения в том, что ChatGPT компании OpenAI является исключительно интеллектуальной – он успешно прошел юридический экзамен, обладает знаниями, сравнимыми с доктором, и его IQ, согласно некоторым тестам, составляет 155. Однако он склонен выдумывать информацию вместо того, чтобы признавать свою невежество. Эта тенденция, в сочетании с тем, что его знания ограничены 2021 годом, представляет собой вызов при создании специализированных продуктов с использованием GPT API.

Как мы можем преодолеть эти препятствия? Как мы можем передать новые знания модели, подобной GPT-3? Моя цель – ответить на эти вопросы, создавая бота для вопросно-ответной системы с использованием Python, OpenAI API и векторных представлений слов.

Что я собираюсь создать

Я намерен создать бота, который будет генерировать непрерывные интеграционные конвейеры на основе запроса, которые, как вы можете знать, формулируются с использованием YAML в Semaphore CI/CD.

Вот пример работы бота:

Снимок экрана работающей программы. На экране выполняется команда python query.py "Create a CI pipeline that builds and uploads a Docker image to Docker Hub", и программа выводит YAML, соответствующий интеграционному конвейеру, выполняющему запрошенное действие.

В духе проектов, таких как DocsGPT, My AskAI и Libraria, я планирую “обучить” модель GPT-3 Semaphore и способности генерировать файлы конфигурации конвейера. Я сделаю это, опираясь на существующую документацию.

Я не буду предполагать, что у вас есть предварительные знания по созданию ботов, и буду писать чистый код, чтобы вы могли адаптировать его под свои требования.

Предварительные условия

Вам не требуется опыт программирования ботов или знания о нейронных сетях, чтобы следовать этому руководству. Однако вам понадобится:

  • Python 3.

  • Учетная запись Pinecone (бесплатная регистрация на Starter-плане).

  • API-ключ OpenAI (платный, требуется кредитная карта); новые пользователи могут использовать $5 бесплатного кредита в течение первых 3 месяцев.

Но ChatGPT не может учиться, не так ли?

ChatGPT, а точнее, GPT-3 и GPT-4, большие языковые модели (LLM), на которых они основаны, обучены на огромном наборе данных с обрезкой примерно в сентябре 2021 года.

По сути, GPT-3 знает очень мало о событиях после этой даты. Мы можем проверить это с помощью простого запроса:

Хотя некоторые модели OpenAI могут быть дообучены, более продвинутые модели, такие как те, которые нас интересуют, нет; мы не можем расширять их обучающие данные.

Как мы можем получать ответы от GPT-3, исходя за пределы его обучающих данных? Одним из методов является использование его способностей к пониманию текста; расширяя запрос соответствующим контекстом, мы скорее всего получим правильный ответ.

В приведенном ниже примере я предоставляю контекст с официального сайта FIFA, и ответ значительно отличается:

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

Что такое векторные представления слов?

В контексте языковых моделей встраивание – это способ представления слов, предложений или целых документов в виде векторов или списков чисел.

Для вычисления векторных представлений нам понадобится нейронная сеть, такая как word2vec или text-embedding-ada-002. Эти сети были обучены на огромных объемах текста и могут находить взаимосвязи между словами, анализируя частотность определенных шаблонов в обучающих данных.

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

  • Кот

  • Собака

  • Мяч

  • Дом

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

Получив векторы для каждого слова, мы можем использовать их для представления значения текста. Например, предложение “Кот преследует мяч” может быть представлено вектором [0.1, 0.2, 0.3, 0.4, 0.5] + [0.2, 0.4, 0.6, 0.8, 1.0] = [0.3, 0.6, 0.9, 1.2, 1.5]. Этот вектор представляет собой предложение о животном, преследующем объект.

Поперечные вложения слов можно визуализировать как многомерные пространства, в которых слова или предложения с похожими значениями находятся близко друг к другу. Мы можем вычислить “расстояние” между векторами, чтобы найти схожие значения для любого входного текста.

3D-представление вложений как векторных пространств. На самом деле эти пространства могут иметь сотни или тысячи измерений. Источник: Meet AI’s Multitool: Vector Embeddings

Фактическая математика, лежащая в основе всего этого, выходит за рамки данной статьи. Однако ключевой вывод заключается в том, что векторные операции позволяют нам манипулировать или определять значение с помощью математики. Возьмите вектор, представляющий слово “королева”, вычтите из него вектор “женщины” и добавьте вектор “мужчины”. Результат должен быть вектором, близким к “королю”. Если мы добавим “сына”, мы должны получить что-то близкое к “принцу”.

Встраивание нейронных сетей с помощью токенов

До сих пор мы обсуждали встраивание нейронных сетей, принимающих слова на вход и выдающих числа на выходе. Однако многие современные сети перешли от обработки слов к обработке токенов.

Токен – это наименьшая единица текста, которую модель может обработать. Токены могут быть словами, символами, знаками препинания, символами или частями слов.

Мы можем увидеть, как слова преобразуются в токены, экспериментируя с онлайн-токенизатором OpenAI, который использует кодирование байт-пар для преобразования текста в токены и представляет каждый из них числом:

Часто существует 1:1 соответствие между токенами и словами. Большинство токенов включают слово и ведущий пробел. Однако есть особые случаи, например “вложение”, которое состоит из двух токенов, “встраивание” и “ding”, или “возможности”, которое состоит из четырех токенов. Если нажать на “Идентификаторы токенов”, вы можете увидеть числовое представление каждого токена модели.

Проектирование умного бота с использованием вложений

Теперь, когда у нас есть представление о том, что такое вложения, следующий вопрос: Как они могут помочь нам создать более умного бота?

Во-первых, давайте рассмотрим, что происходит, когда мы используем API GPT-3 напрямую. Пользователь задает запрос, и модель отвечает наилучшим образом.

Однако, когда мы добавляем контекст в уравнение, все меняется. Например, когда я спросил ChatGPT о победителе Чемпионата мира после предоставления контекста, это сделало всю разницу.

Итак, план по созданию более умного бота выглядит следующим образом:

  1. Перехватить запрос пользователя.

  2. Вычислить вложения для этого запроса, получив вектор.

  3. Поиск в базе данных документов, близких к вектору, так как они должны быть семантически связаны с исходным запросом.

  4. Отправить исходный запрос в GPT-3 вместе с любым соответствующим контекстом.

  5. Переслать ответ GPT-3 пользователю.

Начнем, как и большинство проектов, с проектирования базы данных.

Создание базы знаний с использованием вложений

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

Векторные базы данных – это специализированные базы данных, разработанные для хранения и извлечения высокомерных векторных данных. Вместо использования языка запросов, такого как SQL, для поиска, мы предоставляем вектор и запрашиваем N наиболее близких соседей.

Для генерации векторов мы будем использовать text-embedding-ada-002 от OpenAI, так как это самая быстрая и экономически эффективная модель, которую они предлагают. Модель преобразует входной текст в токены и использует механизм внимания, известный как Transformer, для изучения их взаимосвязей. Выход этой нейронной сети – векторы, представляющие смысл текста.

Для создания базы контекста я сделаю следующее:

  1. Соберу всю исходную документацию.

  2. Отфильтрую нерелевантные документы.

  3. Вычислю вложения для каждого документа.

  4. Сохраню векторы, исходный текст и любую другую соответствующую метадату в базе данных.

Преобразование документов в векторы

Сначала мне нужно инициализировать файл среды с ключом API OpenAI. Этот файл никогда не должен попадать в систему контроля версий, так как ключ API является конфиденциальным и привязан к вашей учетной записи.

export OPENAI_API_KEY=YOUR_API_KEY

Далее я создам виртуальное окружение для моего приложения на Python:

$ virtualenv venv
$ source venv/bin/activate
$ source .env

И установлю пакет OpenAI:

```bash
$ pip install openai numpy

Давайте попробуем вычислить векторное представление для строки “Docker Container”. Вы можете запустить это в Python REPL или в виде Python-скрипта:

$ python

>>> import openai

>>> embeddings = openai.Embedding.create(input="Docker Containers", engine="text-embedding-ada-002")

>>> embeddings

 JSON: {
 "data": [
 {
 "embedding": [
 -0.00530336843803525,
 0.0013223182177171111,
 
 ... 1533 more items ...,
 
 -0.015645816922187805
 ],
 "index": 0,
 "object": "embedding"
 }
 ],
 "model": "text-embedding-ada-002-v2",
 "object": "list",
 "usage": {
 "prompt_tokens": 2,
 "total_tokens": 2
 }
}

Как видите, модель OpenAI возвращает список embedding, содержащий 1536 элементов – размер вектора для text-embedding-ada-002.

Хранение векторов в Pinecone

Хотя существует несколько движков баз данных векторов на выбор, таких как Chroma, который является open-source, я выбрал Pinecone, потому что это управляемая база данных с бесплатным тарифным планом, что делает все проще. Их тарифный план Starter более чем способен обрабатывать все данные, которые мне понадобятся.

После создания учетной записи Pinecone и получения моего API-ключа и среды, я добавляю оба значения в мой файл .env.

Теперь .env должен содержать мои секреты Pinecone и OpenAI.

export OPENAI_API_KEY=YOUR_API_KEY

# Секреты Pinecone
export PINECONE_API_KEY=YOUR_API_KEY
export PINECONE_ENVIRONMENT=YOUR_PINECONE_DATACENTER

Затем устанавливаю клиент Pinecone для Python:

$ pip install pinecone-client

Мне нужно инициализировать базу данных; вот содержимое скрипта db_create.py:

# db_create.py

import pinecone
import openai
import os

index_name = "semaphore"
embed_model = "text-embedding-ada-002"

api_key = os.getenv("PINECONE_API_KEY")
env = os.getenv("PINECONE_ENVIRONMENT")
pinecone.init(api_key=api_key, environment=env)

embedding = openai.Embedding.create(
 input=[
 "Пример текста документа здесь",
 "в каждом пакете будет несколько фраз"
 ], engine=embed_model
)

if index_name not in pinecone.list_indexes():
 print("Создание индекса Pinecone: " + index_name)
 pinecone.create_index(
 index_name,
 dimension=len(embedding['data'][0]['embedding']),
 metric='cosine',
 metadata_config={'indexed': ['source', 'id']}
 )

Создание базы данных может занять несколько минут.

$ python db_create.py

Затем я установлю пакет tiktoken. Я буду использовать его для расчета количества токенов в исходных документах. Это важно, потому что модель векторного представления может обрабатывать только до 8191 токенов.

$ pip install tiktoken

При установке пакетов давайте также установим tqdm, чтобы создать красивый индикатор выполнения.

$ pip install tqdm

Теперь мне нужно загрузить документы в базу данных. Скрипт для этого будет называться index_docs.py. Давайте начнем с импорта необходимых модулей и определения некоторых констант:

# index_docs.py

# Имя базы данных Pinecone и размер пакета загрузки
index_name = 'semaphore'
upsert_batch_size = 20

# Модели векторного представления и токенизатора OpenAI
embed_model = "text-embedding-ada-002"
encoding_model = "cl100k_base"
max_tokens_model = 8191

Затем нам понадобится функция для подсчета токенов. На странице OpenAI есть пример подсчета токенов:

import tiktoken
def num_tokens_from_string(string: str) -> int:
 """Возвращает количество токенов в текстовой строке."""
 encoding = tiktoken.get_encoding(encoding_model)
 num_tokens = len(encoding.encode(string))
 return num_tokens

Наконец, мне понадобятся некоторые функции фильтрации для преобразования исходного документа в используемые примеры. Большинство примеров в документации находится между кодовыми заборами, поэтому я просто извлеку весь код YAML из каждого файла:

import re
def extract_yaml(text: str) -> str:
 """Возвращает список всех найденных блоков кода YAML в тексте."""
 matches = [m.group(1) for m in re.finditer("```yaml([\w\W]*?)```", text)]
 return matches

Я закончил с функциями. Далее этот код загрузит файлы в память и извлечет примеры:

from tqdm import tqdm
import sys
import os
import pathlib

repo_path = sys.argv[1]
repo_path = os.path.abspath(repo_path)
repo = pathlib.Path(repo_path)

markdown_files = list(repo.glob("**/*.md")) + list(
 repo.glob("**/*.mdx")
)

print(f"Извлечение YAML из файлов Markdown в {repo_path}")
new_data = []
for i in tqdm(range(0, len(markdown_files))):
 markdown_file = markdown_files[i]
 with open(markdown_file, "r") as f:
 relative_path = markdown_file.relative_to(repo_path)
 text = str(f.read())
 if text == '':
 continue
 yamls = extract_yaml(text)
 j = 0
 for y in yamls:
 j = j+1
 new_data.append({
 "source": str(relative_path),
 "text": y,
 "id": f"github.com/semaphore/docs/{relative_path}[{j}]"
 })

На этом этапе все YAML-файлы должны быть сохранены в списке new_data. Последний шаг – загрузить векторы в Pinecone.

import pinecone
import openai

api_key = os.getenv("PINECONE_API_KEY")
env = os.getenv("PINECONE_ENVIRONMENT")
pinecone.init(api_key=api_key, enviroment=env)
index = pinecone.Index(index_name)

print(f"Создание векторов и загрузка в базу данных")
for i in tqdm(range(0, len(new_data), upsert_batch_size)):
 
 i_end = min(len(new_data), i+upsert_batch_size)
 meta_batch = new_data[i:i_end]
 ids_batch = [x['id'] for x in meta_batch]
 texts = [x['text'] for x in meta_batch]

 embedding = openai.Embedding.create(input=texts, engine=embed_model)
 embeds = [record['embedding'] for record in embedding['data']]

 # очистка метаданных перед вставкой
 meta_batch = [{
 'id': x['id'],
 'text': x['text'],
 'source': x['source']
 } for x in meta_batch] 

 to_upsert = list(zip(ids_batch, embeds, meta_batch))
 index.upsert(vectors=to_upsert)

В качестве справки, вы можете найти полный файл index_docs.py в репозитории демонстрации.

Запустим скрипт индексации, чтобы завершить настройку базы данных:

$ git clone https://github.com/semaphoreci/docs.git /tmp/docs
$ source .env
$ python index_docs.py /tmp/docs

Тестирование базы данных

Панель управления Pinecone должна отображать векторы в базе данных.

Мы можем запросить базу данных с помощью следующего кода, который можно выполнить как скрипт или непосредственно в REPL Python:

$ python

>>> import os
>>> import pinecone
>>> import openai

# Вычисляем векторы для строки "Docker Container"
>>> embeddings = openai.Embedding.create(input="Docker Containers", engine="text-embedding-ada-002")


# Подключаемся к базе данных
>>> index_name = "semaphore"
>>> api_key = os.getenv("PINECONE_API_KEY")
>>> env = os.getenv("PINECONE_ENVIRONMENT")
>>> pinecone.init(api_key=api_key, environment=env)
>>> index = pinecone.Index(index_name)

# Запрос к базе данных
>>> matches = index.query(embeddings['data'][0]['embedding'], top_k=1, include_metadata=True)

>>> matches['matches'][0]
{'id': 'github.com/semaphore/docs/docs/ci-cd-environment/docker-authentication.md[3]',
 'metadata': {'id': 'github.com/semaphore/docs/docs/ci-cd-environment/docker-authentication.md[3]',
 'source': 'docs/ci-cd-environment/docker-authentication.md',
 'text': '\n'
 '# .semaphore/semaphore.yml\n'
 'version: v1.0\n'
 'name: Using a Docker image\n'
 'agent:\n'
 ' machine:\n'
 ' type: e1-standard-2\n'
 ' os_image: ubuntu1804\n'
 '\n'
 'blocks:\n'
 ' - name: Run container from Docker Hub\n'
 ' task:\n'
 ' jobs:\n'
 ' - name: Authenticate docker pull\n'
 ' commands:\n'
 ' - checkout\n'
 ' - echo $DOCKERHUB_PASSWORD | docker login '
 '--username "$DOCKERHUB_USERNAME" --password-stdin\n'
 ' - docker pull /\n'
 ' - docker images\n'
 ' - docker run /\n'
 ' secrets:\n'
 ' - name: docker-hub\n'},
 'score': 0.796259582,
 'values': []}

Как видите, первое совпадение – это YAML для конвейера Semaphore, который загружает образ Docker и запускает его. Это хороший старт, так как он относится к нашей строке поиска “Docker Containers”.

Создание бота

У нас есть данные, и мы знаем, как с ними работать. Давайте применим их в работе бота.

Шаги для обработки подсказки:

  1. Взять подсказку пользователя.

  2. Рассчитать ее вектор.

  3. Извлечь соответствующий контекст из базы данных.

  4. Отправить подсказку пользователя вместе с контекстом в GPT-3.

  5. Переслать ответ модели пользователю.

Как обычно, я начну с определения некоторых констант в файле complete.py, основном скрипте бота:

# complete.py

# Имя базы данных Pinecone, количество совпадений для извлечения
# оценка сходства с отсечением и количество токенов в контексте
index_name = 'semaphore'
context_cap_per_query = 30
match_min_score = 0.75
context_tokens_per_query = 3000

# Параметры модели OpenAI LLM
chat_engine_model = "gpt-3.5-turbo"
max_tokens_model = 4096
temperature = 0.2 
embed_model = "text-embedding-ada-002"
encoding_model_messages = "gpt-3.5-turbo-0301"
encoding_model_strings = "cl100k_base"

import pinecone
import os

# Соединение с базой данных Pinecone и индексирование
api_key = os.getenv("PINECONE_API_KEY")
env = os.getenv("PINECONE_ENVIRONMENT")
pinecone.init(api_key=api_key, environment=env)
index = pinecone.Index(index_name)

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

import tiktoken

def num_tokens_from_string(string: str) -> int:
 """Возвращает количество токенов в текстовой строке."""
 encoding = tiktoken.get_encoding(encoding_model_strings)
 num_tokens = len(encoding.encode(string))
 return num_tokens


def num_tokens_from_messages(messages):
 """Возвращает количество токенов, используемых списком сообщений. Совместимо с моделью."""

 try:
 encoding = tiktoken.encoding_for_model(encoding_model_messages)
 except KeyError:
 encoding = tiktoken.get_encoding(encoding_model_strings)

 num_tokens = 0
 for message in messages:
 num_tokens += 4 # каждое сообщение следует за {роль/имя}\n{содержимое}\n
 for key, value in message.items():
 num_tokens += len(encoding.encode(value))
 if key == "name": # если есть имя, роль опускается
 num_tokens += -1 # роль всегда требуется и всегда занимает 1 токен
 num_tokens += 2 # каждый ответ начинается с помощника
 return num_tokens

Следующая функция принимает исходную подсказку и контекстные строки, чтобы вернуть обогащенную подсказку для GPT-3:

def get_prompt(query: str, context: str) -> str:
 """Возвращает подсказку с запросом и контекстом."""
 return (
 f"Создайте код YAML для непрерывной интеграции, чтобы выполнить запрошенную задачу.\n" +
 f"Ниже вы найдете некоторый контекст, который может помочь. Проигнорируйте его, если он кажется несущественным.\n\n" +
 f"Контекст:\n{context}" +
 f"\n\nЗадача: {query}\n\nYAML-код:"
 )

Функция get_message форматирует подсказку в формат, совместимый с API:

def get_message(role: str, content: str) -> dict:
 """Генерирует сообщение для завершения API OpenAI."""
 return {"role": role, "content": content}

Существуют три типа ролей, которые влияют на реакцию модели:

  • Пользователь: для исходной подсказки пользователя.

  • Система: помогает установить поведение помощника. Несмотря на то, что есть некоторые споры относительно его эффективности, он, кажется, более эффективен, когда отправляется в конце списка сообщений.

  • Помощник: представляет прошлые ответы модели. API OpenAI не имеет “памяти”; вместо этого мы должны отправлять предыдущие ответы модели на каждое взаимодействие, чтобы сохранить диалог.

Теперь перейдем к увлекательной части. Функция get_context берет запрос, обращается к базе данных и генерирует строку контекста до тех пор, пока не выполняется одно из следующих условий:

  • Полный текст превышает значение context_tokens_per_query, которое я зарезервировал для контекста.

  • Функция поиска извлекает все запрошенные совпадения.

  • Совпадения, у которых коэффициент сходства ниже match_min_score, игнорируются.

import openai

def get_context(query: str, max_tokens: int) -> list:
 """Генерирует сообщение для модели OpenAI. Добавляет контекст до достижения предела `context_token_limit`. Возвращает строку с запросом."""

 embeddings = openai.Embedding.create(
 input=[query],
 engine=embed_model
 )

 # поиск в базе данных
 vectors = embeddings['data'][0]['embedding']
 embeddings = index.query(vectors, top_k=context_cap_per_query, include_metadata=True)
 matches = embeddings['matches']

 # фильтрация и агрегация контекста
 usable_context = ""
 context_count = 0
 for i in range(0, len(matches)):

 source = matches[i]['metadata']['source']
 if matches[i]['score'] < match_min_score:
 # пропускаем контекст с низким коэффициентом сходства
 continue
 
 context = matches[i]['metadata']['text']
 token_count = num_tokens_from_string(usable_context + '\n---\n' + context)

 if token_count < context_tokens_per_query:
 usable_context = usable_context + '\n---\n' + context 
 context_count = context_count + 1

 print(f"Найдено {context_count} контекстов для вашего запроса")

 return usable_context

Следующая и последняя функция complete отправляет запрос API к OpenAI и возвращает ответ модели.

def complete(messages):
 """Запрос к модели OpenAI. Возвращает первый ответ. """

 res = openai.ChatCompletion.create(
 model=chat_engine_model,
 messages=messages,
 temperature=temperature
 )
 return res.choices[0].message.content.strip()

Вот и все; теперь мне осталось только работать с аргументами командной строки и вызывать функции в правильном порядке:

import sys

query = sys.argv[1]

context = get_context(query, context_tokens_per_query)
prompt = get_prompt(query, context)

# инициализируем список сообщений для отправки в API OpenAI
messages = []
messages.append(get_message('user', prompt))
messages.append(get_message('system', 'Вы - полезный помощник, который пишет код YAML для конвейеров непрерывной интеграции Semaphore и объясняет их. Возвращает код YAML внутри блоков кода.'))

if num_tokens_from_messages(messages) >= max_tokens_model:
 raise Exception('Достигнуто ограничение размера токенов модели') 

print("Работаю над вашим запросом... ")
answer = complete(messages)
print("Ответ:\n")
print(answer)
messages.append(get_message('assistant', answer))

Пришло время запустить скрипт и посмотреть, как он справляется:

$ python complete.py "Создать конвейер непрерывной интеграции, который собирает и загружает образ Docker в Docker Hub"

Результат:

version: v1.0
name: Docker Build and Push
agent:
 machine:
 type: e1-standard-2
 os_image: ubuntu1804

blocks:
 - name: "Build and Push Docker Image"
 task:
 jobs:
 - name: "Docker Build and Push"
 commands:
 - checkout
 - docker build -t /: .
 - echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
 - docker push /:

promotions:
 - name: Deploy to production
 pipeline_file: deploy-production.yml
 auto_promote:
 when: "result = 'passed' and branch = 'master'"

Это первый хороший результат. Модель вывела синтаксис из примеров контекста, которые мы предоставили.

Мысли о расширении возможностей бота

Помните, что я начал с скромной цели: создания помощника для написания конвейеров YAML. С более богатым содержанием в моей базе векторов я могу обобщить бота, чтобы он отвечал на любые вопросы о Semaphore (или любом другом продукте – помните о клонировании документов в /tmp?).

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

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

Внедрение правильного чатбота

Вы могли заметить, что мой бот не позволяет нам вести настоящий разговор, как ChatGPT. Мы задаем один вопрос и получаем один ответ.

Превращение бота в полноценного чатбота в принципе не так сложно. Мы можем поддерживать разговор, отправляя предыдущие ответы модели с каждым запросом к API. Предыдущие ответы GPT-3 возвращаются в роли “assistant”. Например:

messages = []

while True:

 query = input('Введите ваш запрос:\n')
 
 context = get_context(query, context_tokens_per_query)
 prompt = get_prompt(query, context)
 messages.append(get_message('user', prompt))
 messages.append(get_message('system', 'Вы - полезный помощник, который пишет код YAML для непрерывной интеграции Semaphore и объясняет его. Возвращайте код YAML внутри кодовых заборов.'))

 if num_tokens_from_messages(messages) >= max_tokens_model:
 raise Exception('Достигнуто ограничение на количество токенов модели') 

 print("Работаю над вашим запросом... ")
 answer = complete(messages)
 print("Ответ:\n")
 print(answer)
 
 # удаляем системное сообщение и добавляем ответ модели
 messages.pop() 
 messages.append(get_message('assistant', answer))

К сожалению, эта реализация довольно примитивна. Она не поддерживает продолжительные разговоры, так как количество токенов увеличивается с каждым взаимодействием. Рано или поздно мы достигнем предела в 4096 токенов для GPT-3, что не позволит продолжать диалог.

Так что нам нужно найти способ оставаться в пределах лимита токенов. Следуют несколько стратегий:

  • Удалить старые сообщения. Хотя это самое простое решение, оно ограничивает “память” разговора только последними сообщениями.

  • Делать сводки предыдущих сообщений. Мы можем использовать “Спросить модель”, чтобы сжать более ранние сообщения и заменить их на исходные вопросы и ответы. Хотя такой подход увеличивает стоимость и задержку между запросами, он может привести к более качественным результатам по сравнению с простым удалением прошлых сообщений.

  • Установить строгий лимит на количество взаимодействий.

  • Дождаться общедоступности GPT-4 API, который не только умнее, но имеет удвоенную емкость токенов.

  • Использовать новую модель, например “gpt-3.5-turbo-16k”, которая может обрабатывать до 16 тысяч токенов.

Заключение

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

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

Удачного создания!