Привет, Хабр!

Как не трудно догадаться из названия, сегодня пойдет речь о перспективном фреймворке для работы с языковыми моделями LangChain. Если вы хотите создать свой собственный ChatGPT, то этот инструмент поможет сильно ускорить процесс разработки вашего AI приложения. Langchain работает по принципу лего и позволяет собрать как заправку, так и Звезду Смерти – все детальки заботливо предоставлены разработчиками.

Основные компоненты

  • Модели: универсальный интерфейс для работы с различными языковыми моделями. Можно использовать API OpenAI, Cohere, Hugging Face и других. Также есть возможность работать с локальными моделями.

  • Промпты: LangChain предоставляет ряд функции для работы с промптами – представление промпта согласно типу модели, формировани шаблона на основе внешних данных, форматирование вывода модели.

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

  • Цепочки: С помощью цепочек можно объединять разные языковые модели и запросы в многоступенчатые конвееры. Цепочки могут быть применены для разговоров, ответов на вопросы, суммаризаций и других сценариев.

  • Агенты: С помощью агентов модель может получить доступ к различным источникам информации, таким как Google, Wikipedia итд.

  • Память: позволяет сохраняет состояния в цепочках. Например, для создания чат-бота можно сохранять предыдущие вопросы и ответы. Существует два типа памяти: краткосрочная и долгосрочная. Краткосрочная память передает данные в рамках одного разговора. Долгосрочная память отвечает за доступ и обновление информации между разговорами.

Готовим окружение и данные

Посмотрим как этот инструмент работает на практике. В своей прошлой статье я рассказывал про плагин-ретривер к ChatGPT. Плагин позволяет подключить свою базу данных к LLM и использовать эти знания для формирования ответов. Почему бы не попытаться воспроизвести его самостоятельно на LangChain, тем более, что OpenAI так и не открыл мне доступ. Что нам понадобится:

  1. Векторная база данных. За это будет отвечать модуль Indexes

  2. LLM для генерации ответа - блок Models. Здесь можно будет попробовать ChatGPT и какой-нибудь клон альпаки.

  3. Передать знания из нашей базы в LLM. На помощь придет модуль Prompts

Настраиваем окружение для проекта

  1. Создаем директорию mkdir langchain и переходим в нее cd langchain

  2. Создаем виртуальное окружение. Я буду использовать conda и python 3.11

conda create --name langchain
conda activate langchain
  1. Также зарегистрируем наш кернел в jupyter

conda install pip
pip install jupyterlab
pip install --user ipykernel
python -m ipykernel install --name langchain --user
  1. Устанавливаем LangChain и сопутствующие библиотеки

pip install langchain pandas tiktoken huggingface_hub

Готовим данные для нашего векторного хранилища

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

Сгенерируй набор из 10 примеров документов, которые далее будут добавлены в векторное хранилище. В документах содержится информаци о запросах в службу поддержки компании. Каждый документ должен иметь следующую структуру:
1. id: int (уникальный идентификатор документа)
2. question: str(вопрос пользователя)
3. answer: str(ответ службы поддержки)
4. url: str (ссылка на источник информации, предпочтительно страницу confluence)
Обрати внимание, что тексты документов должны быть разнообразными и содержательными. Результат должен быть реализован в виде python кода

ChatGPT выдал нам следующие варианты:

documents = [
{"id": 1, "question": "Как восстановить пароль?", "answer": "Для восстановления пароля перейдите по ссылке 'Забыли пароль?' на странице входа. Введите свой адрес электронной почты, и мы вышлем вам инструкции по восстановлению пароля.", "url": "https://example.com/confluence/recover-password"},
{"id": 2, "question": "Как связаться со службой поддержки?", "answer": "Вы можете связаться со службой поддержки, написав нам на электронную почту support@example.com или позвонив по телефону +1 (123) 456-7890.", "url": "https://example.com/confluence/contact-support"},
{"id": 3, "question": "Как настроить двухфакторную аутентификацию?", "answer": "Для настройки двухфакторной аутентификации перейдите в раздел 'Настройки безопасности' вашего аккаунта и следуйте инструкциям.", "url": "https://example.com/confluence/2fa-setup"},
...
]

Индексируем документы в FAISS

Перед индексацией нужно будет подумать над моделью векторизации. Можно использовать разные опции: обучить собственный векторайзер(например на базе моделей из SentenceTransformers), либо взять что-то из коробки. Я воспользуюсь решением от OpenAI(в этом случае нужно будет также добавить ключ доступа к API).

В качестве векторной БД буду использовать FAISS - сейчас это самое шустрое решение для поиска ближайших соседей. В LangChain также есть реализации для множества других хранилищ(ElasticSearch, Redis, Pinecone итд.)

import pandas as pd # если нет импорта пандаса, то остальной код не имеет смысла

from langchain.document_loaders import DataFrameLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import FAISS

# создаем из наших документов датафрейм
df = pd.DataFrame(documents)
df.head()
Создаем датафрейм из документов
Создаем датафрейм из документов
# грузим фрейм в лоадер, выделив колонку для векторизации (здесь может быть место для дискуссий)
loader = DataFrameLoader(df, page_content_column='question')
documents = loader.load()
Документ из лоадера
Документ из лоадера
# создаем сплиттер документов, чтобы уложиться в лимит по токенам, в нашем случае это не очень полезный шаг
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)

# задаем векторайзер
embeddings = OpenAIEmbeddings(openai_api_key=YOUR_API_KEY)

# создаем хранилище
db = FAISS.from_documents(texts, embeddings)
db.as_retriever()

# также можно сохранить хранилище локально
db.save_local('faiss_index')

# тестируем ретривер
db.similarity_search_with_score('не знаю как прикрепить сотрудника')
Ответ ретривера
Ответ ретривера

Вроде все работает.

Добавляем силу ChatGPT

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

  1. Построить цепочку load_qa_chain из ответов нашего ретривера.

  2. Обратиться напрямую к векторной БД RetrievalQA.

  3. Получить конкретный контекст и передать в цепочке LLM LLMChain.

Попробуем пойти самым коротким путем:

from langchain.chains import RetrievalQA
from langchain.llms import OpenAI

# создаем цепочку
qa_chain = RetrievalQA.from_chain_type(
llm=OpenAI(temperature=0, openai_api_key=openai_api_key),
chain_type='stuff',
retriever=db.as_retriever()
)

query = 'не знаю как прикрепить сотрудника'

qa_chain.run(query)
LLM через RetrievalQA
LLM через RetrievalQA

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

from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain

# создаем шаблон для промта
prompt_template = """Используй контекст для ответа на вопрос, пользуясь следующими правилами:

Не изменяй текст, который находится в кавычках.
В конце обязательно добавь ссылку на полный документ
{answer}
url: {url}
"""

PROMPT = PromptTemplate(
template=prompt_template, input_variables=['answer', 'url']
)

# цепочка с кастомным промтом
chain = LLMChain(
llm=OpenAI(temperature=0, openai_api_key=openai_api_key, max_tokens=500),
prompt=PROMPT)

relevants = db.similarity_search('не знаю как прикрепить сотрудника')
doc = relevants[0].dict()['metadata']

chain.run(doc)
Используем шаблон промпта
Используем шаблон промпта

А что Alpaca?

ChatGPT хорош, но как же новомодные легковесные архитектуры, которые позволят каждому школьнику создать свой скайнет. Для русского языка есть уже целое стадо таких моделек.

from langchain import HuggingFaceHub

#подключение по API huggingface
alpaca_chain = LLMChain(
prompt=PROMPT,
llm=HuggingFaceHub(repo_id='IlyaGusev/fred_t5_ru_turbo_alpaca',
                    huggingfacehub_api_token=YOUR_API_KEY,
                    model_kwargs={'temperature':0, 'max_length':128}
                    )
)

alpaca_chain.run(doc)
Ответ альпаки
Ответ альпаки

Такое. Явно нужно как-то иначе формулировать промпт и поиграться с шрифтами настройками модели.

Использовать модели по API может быть не очень оптимально. В LangChain вы можете припарковать их на собственный хост через функционал SelfHostedPipeline и SelfHostedHuggingFaceLLM.

Осталось протестировать возможности памяти, но я, конечно же, это делать не буду. Пусть это будет задачкой со звездочкой для вдумчивого читателя. Спасибо за внимание!

Пишу про AI и NLP в телеграм.

Комментарии (4)


  1. rsashka
    18.04.2023 05:45

    Агенты: С помощью агентов модель может получить доступ к различным источникам информации, таким как Google, Wikipedia итд.

    Правильно ли я понял, что данный фреймворке имеет рантайм доступ к внешнему миру при обучении?


  1. Mark_K Автор
    18.04.2023 05:45
    +1

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


    1. rsashka
      18.04.2023 05:45
      +1

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


      1. Mark_K Автор
        18.04.2023 05:45

        Во время выполнения