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

А что если мы хотим использовать свежие библиотеки: новые или последние версии, которые сильно изменились с 2021 года. Если говорить про приватные репозитории, то данные этих репозиториев не были использованы для обучения GPT-4. Зачастую приватные репозитории хуже документированы и могут быть достаточно объемными - в общем черт ногу сломит, чтобы понять, что там к чему.

Обучается потихоньку
Обучается потихоньку

Проблема ясна, надо как то решать, потому что работать по-старому совсем не хочется. Уже привык, что можно достаточно просто попросить объяснить и сгенерировать код, пускай даже достаточно простого и можно сказать примитивного, но этого зачастую хватает, чтобы быстро понять как можно сделать задачу или найти нужную информацию. Также хочется получать обобщенную информацию или код по нескольким источникам (например добавить функциональность в UI, backend и базу данных).

Ищем решение

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

Первый вариант, мы можем обучить свою большую языковую модель на своих данных. Выглядит достаточно тяжело, долго и дорого. Недавно попалась статья про обучение модели 1.7B параметров, использовали 110GPU в течение 45 дней. Один GPU насколько я понимаю стоит от 10k$, да и модель хочется побольше, ну хотя бы 175B :)

Второй вариант, мы можем дообучить существующую языковую модель, обогатив её знания нашими данными. Вариант, сильно проще, чем делать все заново. Но насколько я понимаю это тоже не совсем тривиально и вроде это не совсем правильно, так как так называемый fine tunning предполагает добавление нового поведения в модель, а не обогащение новыми знаниями. Стоит отметить, что если мы дообучаем OpenAI модель, то мы можем использовать только модели предыдущих поколений (davinci, ada и тд), а они сильно уступают новым GPT-4 и GPT-3.5, к тому же предыдущее поколение достаточно дороги, если сравнивать с GPT-3.5. В общем хочется попробовать, но этот вариант пока не выглядит как самый очевидный.

Третий вариант - наш победитель.

Мы можем передать интересующую нас информацию как контекст в запрос. Контекст имеет ограниченный размер (подробнее можно почитать в моей прошлой статье - https://habr.com/ru/articles/727458/) Из-за ограничения контекста мы не сможем передать в контекст целый репозиторий, так как он понятное дело сильно больше.

Но мы можем передавать связанную с запросом информацию в контексте запроса. Например текст запроса будет выглядеть следующим образом:

Используй следующую информацию, чтобы помочь пользователю с его запросом:
{Документация по классу}
{Исходный код класса}
{Примеры использования класса}
User:
Сгенерируй наследника класса BabyAGI, добавь возможность запроса у пользователя, нужно ли продолжать выполнение задач при достижении лимитов на количество выполненных задач.

В этом случае мы можем использовать как GPT-3.5 модель (если хотим подешевле и ограничение его контекста нас устраивает - 4к токенов). Или можем использовать GPT-4, сильно дороже, но контекст уже до 32к токенов.

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

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

Минутка определений

Спросим GTP-4, что такое семантический поиск:

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

Что такое векторное представление (embeddings):

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

Алгоритм

  • подготовка данных для семантического поиска:

    • разбиваем наш текст, документ, репозиторий на части определенного размера, не очень большого, чтобы несколько частей можно было добавить в контекст запроса;

    • для каждой такой текстовой части получаем её векторное представление;

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

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

    • пользователь вводит запрос;

    • получаем вектор для пользовательского запроса;

    • получаем из базы данных релевантные тексты с использованием вектора для запроса;

    • добавляем полученные из базы данных тексты в контекст запроса.

В целом выглядит не сложно
В целом выглядит не сложно

Применение

Давайте перечислю несколько вариантов применения данного подхода для решения разных задач.

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

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

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

Поиск по личной/приватной библиотеке. У меня лежит около 1TB данных: куча книг, статей, курсов, видео. Все это можно векторизовать и потом запрашивать необходимые данные. Как векторизовать видео? Получаем текст из видео и этот текст векторизуем. В метаданные записи указываем источник - видео файл.

Поиск по приватным/корпоративным базам знаний (репозитории, система документооборота, система документации: Notion, Confluencе и тд). Просто все векторизуем.

Надо отметить, скорее всего необходимо использовать разные индексы для разных типов данных (память в чатах, документация, исходный код, полученные данные из интернета). Разные индексы позволят замешать правильный коктейль в контекст, чтобы не было перекосов в сторону одного источника данных. Можно добавить еще классический map-reduce, чтобы понять какую именно информацию стоит добавлять в контекст к запросу в случае если релевантных документов слишком много.

Можно сказать, что векторное преобразование и семантический поиск, позволяют достаточно просто реализовать существующий вариант поиска. Например: запрос в гугл -> список статей, запрос в gmail -> список писем.

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

Что будем векторизовать и спрашивать

С теорией разобрались, но хочется это попробовать руками и посмотреть на результат.

В качестве подопытного взял репозиторий langchain.

???? What is this?

Large language models (LLMs) are emerging as a transformative technology, enabling developers to build applications that they previously could not. But using these LLMs in isolation is often not enough to create a truly powerful app - the real power comes when you can combine them with other sources of computation or knowledge.

This library is aimed at assisting in the development of those types of applications. Common examples of these types of applications include:
❓ Question Answering over specific documents
???? Chatbots
???? Agents (Python, Terminal, Internet requests and more)

Надо сказать, что репозиторий достаточно свежий и GPT-4 ничего о нём не знает. Я посмотрел пару примеров - выглядит очень круто. Возможность быстро сделать какое-нибудь приложение в реальном мире, а не только переписываться в чат режиме. Очень много коннекторов к различным областям, которые можно менять:

  • LLM - не одним OpenAI, оказывается уже много различных языковых моделей, но видимо они не настолько "умны"

  • Tools - подключаем Pyhton, Terminal, Search (Google, Bing, etc)

Особенно порадовала возможность запустить Baby AGI.

AGI (Artificial General Intelligence, искусственный общий интеллект) — это термин, используемый для описания машины или системы, обладающей способностью выполнения любой интеллектуальной задачи, которую может выполнить человек. В отличие от специализированных искусственных интеллектов (например, системы распознавания изображений или машинного перевода), AGI может адаптироваться к новым задачам и обучаться самостоятельно, используя широкий спектр знаний и навыков.

Именно AGI погубит человечество - ну по мнению кучи статей в интернете. Может я смогу запустить AGI попроще, чтобы он сначала мне сделал хорошо :)

Реализация

Тут встает проблема курицы и яйца. Чтобы по-быстрому попросить GPT-4 написать код для векторизации репозитория с использованием langchain, надо, чтобы языковая модель использовала репозиторий langchain при обучении. Но к сожалению GPT ничего не знает про langchain. А свою утилиту для векторизации мы еще не написали. Поэтому применяем старинный способ - зажигаем лучины и идем читать доки, примеры и исходники.

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

  • подготавливаем данные (отбрасываем ненужное, разбиваем на части);

  • векторизуем и загружаем вместе с исходными текстами в базу;

  • настраиваем RetrivalQA для поиска с векторизованными данными.

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

  • Markdown файлы, так как там есть документация по репозиторию, use cases, немного примеров;

  • ipynb файлы - очень много примеров;

  • Исходных код на Python.

В принципе langchain содержит все что нужно, чтобы обработать все типы файлов:

  • DirectoryLoader - позволяет прочитать все файлы с нужным расширением по определенному пути;

  • PythonCodeTextSplitter - позволяет разбить Python файлы на части с нужной гранулярностью, чтобы функции и классы были целые;

  • MarkdownTextSplitter - то же самое, но для Markdown файлов.

А вот ipynb файлы очень громоздкие с кучей json внутри. Попросил GPT-4 написать конвертер в ipynb в Markdown отбросив Output, так как он не особо нужен для моих целей:

ChatGPT преобразовывает ipynb в markdown

I would like to use ipynb as an embeddings, so i need to get text and other useful things from it at python.
.....
Now i need to create short file from ipynb to text markdown file with all text and code
.....
Can you cut off ipynb output
.....
Why HTML exporter and not markdownexporter

import nbformat
from nbconvert import MarkdownExporter
Load the notebook file
with open('notebook.ipynb', 'r') as f:
nb = nbformat.read(f, as_version=4)
Remove the output from the notebook
for cell in nb.cells:
if cell.get('outputs'):
cell['outputs'] = []
if cell.get('execution_count'):
cell['execution_count'] = None
Create a Markdown exporter without output
exporter = MarkdownExporter(exclude_output=True)
Export the notebook to a Markdown file
(body, _) = exporter.from_notebook_node(nb)
with open('notebook.md', 'w') as f:
f.write(body)

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

Для получения векторов я использовал OpenAI и их модель - text ext-embedding-ada-002. Оказалось, что она не очень дорогая (на порядок дешевле GPT-3.5). Думаю я потратил около 1$ на векторизацию всего репозитория.

Векторы и текст надо было где-то хранить, из списка выбрал Redis, так как это единственное с чем я был знаком :). Если объем репозитория большой нужно выбрать, что-то другое.

В итоге код выглядел вот так (для Python файлов):

# выбираем все документы по расширению
loader = DirectoryLoader('./source/langchain/langchain', glob='**/*.py', silent_errors=True)
documents = loader.load()

# разбиваем докуметы на пачки
text_splitter = PythonCodeTextSplitter(chunk_size=1500, chunk_overlap=0)
texts = text_splitter.split_documents(documents)

# используем OpenAI
embeddings = OpenAIEmbeddings(openai_api_key=os.environ['OPENAI_API_KEY'])

# здесь все векторизуется
docsearch = Redis.from_documents(texts, embeddings, redis_url="redis://localhost:6379",  index_name='langchain')

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

# пользовательский запрос надо векторизировать, используем OpenAI
embeddings = OpenAIEmbeddings(openai_api_key=os.environ['OPENAI_API_KEY'])

# подключаем нашу БД c векторами
docsearch = Redis.from_documents([], embeddings, redis_url="redis://localhost:6379", index_name='langchain')

# настраиваем цепочку для получения ответов, использовал k=8, чтобы к запросу добавлялось до 8 релевантных документа
qa_chain = load_qa_chain(ChatOpenAI(temperature=0), chain_type="stuff", verbose=True)
qa = RetrievalQA(combine_documents_chain=qa_chain, retriever=docsearch.as_retriever(k=8), verbose=True, return_source_documents=True)

# запрос
query = """
Provide information how to made complex questions to LLM with Langchain.
I want to index Langchain git repository with OpenAI embeddings.
I want to be able to ask questions with this repository.
"""

result = qa({"query": query})
print(result['result'])
print(len(result['source_documents']))

Ответ ChatGPT по запросу выше:

To index the LangChain git repository with OpenAI embeddings, you can follow these steps:

Clone the LangChain git repository to your local machine. Install the required packages for LangChain, including OpenAI's API key. Use the TextLoader document loader to load the repository files into LangChain. Use the CharacterTextSplitter to split the files into smaller chunks. Use the OpenAIEmbeddings to embed the chunks. Store the embeddings in a VectorStore, such as ElasticVectorSearch.

Here's an example code snippet to accomplish this:

from langchain.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores.elastic_vector_search import ElasticVectorSearch

# Clone the LangChain git repository to your local machine
# Install the required packages for LangChain, including OpenAI's API key

# Load the repository files into LangChain
loader = TextLoader(root_directory="/path/to/langchain/repository")

# Split the files into smaller chunks
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(loader)

# Embed the chunks
embeddings = OpenAIEmbeddings()
embedded_texts = embeddings.embed(texts)

# Store the embeddings in a VectorStore
vector_store = ElasticVectorSearch(index_name="langchain_embeddings")
vector_store.add_documents(embedded_texts)

To ask questions of the indexed LangChain repository, you can use the load_qa_chain function from the langchain.chains.question_answering module. Here's an example code snippet:

from langchain.chains.question_answering import load_qa_chain
from langchain.llms import OpenAI

# Load the OpenAI LLM
llm = OpenAI()

# Load the question answering chain
chain = load_qa_chain(llm, chain_type="stuff")

# Ask a question
question = "How do I use LangChain to index a git repository?"
answers = chain.run(input_documents=embedded_texts, question=question)

# Print the answer
print(answers[0]["answer"])

This code will load the OpenAI LLM, load the question answering chain, and ask a question of the indexed LangChain repository. The answer will be printed to the console. Note that the load_qa_chain function takes the LLM and a chain type as arguments. The chain type determines the type of chain that will be used for question answering. In this example, we're using the "stuff" chain type, which is a simple chain that just returns the most relevant document.

Выглядит ничего так, маловероятно что заработает, так как на вход chain'у передаем все векторизованные значения :). А вот сама векторизация выглядит хорошо.

Исходники я не прикладываю, есть похожий пример в документации. Узнал о нем, когда запросил у LLM, как сделать то, что я сделал и посмотрел какие документы попались в контекст :)

https://python.langchain.com/en/latest/use_cases/code/code-analysis-deeplake.html

Совместно с векторизацией можно использовать и другие подходы (map_reduce, refine, map_rerank) - https://python.langchain.com/en/latest/modules/chains/index_examples/qa_with_sources.html

Было бы неплохо еще прикрутить какой-нибудь простенький UI поиграться. Если кто-нибудь знает простенький UI для ChatGpt на Python дайте знать в комментариях, буду признателен.

Завел Telegram канал, где публикую заметки и статьи. Если статья понравилась, подключайтесь - https://t.me/needmoredetails

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


  1. alexkenbo
    26.04.2023 09:24
    +1

    UI для моделей. Может подойти.

    gradio.app