Как стать автором
Обновить
121.98

Приручаем нейросети

Время на прочтение6 мин
Количество просмотров10K

Давно не виделись, уважаемые! Ну что ж, рад вас видеть, сегодня будем говорить и применять новые инструменты для создания RAG, улучшим качество наших результатов относительно прошлой статьи за счет использования других моделей для embeddings. Также затронем использование трушной векторной БД Chroma.

Видит Бог, я не хотел, но нам пора начинать использовать не No-Code платформы, так что сегодня вам понадобится Python. Начнем мы с установки зависимостей, нам понадобятся:

  • langchain - сам фреймворк для построения RAG, мы его использовали в некотором виде в прошлой статье.

  • openai - библиотечка для работы с API OpenAI. Мы будем придерживаться традиции использовать локальные модели, но так как LM Studio эмулирует сервер как OpenAI, нам нужна эта библиотека.

  • chromadb - для хранения наших embeddings.

  • tiktoken - нужен нам для генерации embeddings, используется другими библиотеками.

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

В этот раз, чтобы было интереснее, давайте будем разбирать книгу «Герой нашего времени», все-таки классика. :) Я предварительно ее скачал в формате txt, но вы можете использовать буквально любую информацию, в langchain встроена куча разных loader-ов, со списком можете ознакомится тут.

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

CHROMA_PATH = "chroma"
DATA_PATH = "data/books"
CHUNK_SIZE = 750
CHUNK_OVERLAP = 100

Начну с утильки, которая будет отдавать объект эмбедингов:

def get_embeddings():
   model_kwargs = {'device': 'cuda'}
   embeddings_hf = HuggingFaceEmbeddings(
       model_name='intfloat/multilingual-e5-large',
       model_kwargs=model_kwargs
   )
   return embeddings_hf

langchain умный, поэтому при первом запуске он подтянет и сохранит модельку, которую мы тут указываем. Как видите, я остановил свой выбор на intfloat/multilingual-e5-large в этот раз, так как она отлично себя показывает как с русским, так и другими языками. Также я буду запускать это добро на видеокарте, но если вдруг у вас нет такой возможности, можете вместо device: cuda написать cpu. Будет медленнее, но как минимум сможете попробовать.

На случай, если вы используете видеокарту Nvidia и Cuda, вам нужно установить CUDA-драйвера (просто загуглите их и ставьте с официального сайта). После этого небольшой танец с бубном: узнайте версию драйверов, которые вы установили, а затем идите вот сюда и ставьте все по своим параметрам. Сгенерится команда на установку PyTorch, ставим его. Вуаля, теперь ваш PyTorch, ну и модели в целом смогут работать с видеокартой.

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

def load_documents():
   loader = DirectoryLoader(DATA_PATH, glob="*.txt")
   documents = loader.load()
   return documents

Далее бьем по старой стратегии на чанки наш документ:

def split_text(documents: list[Document]):
   text_splitter = RecursiveCharacterTextSplitter(
       chunk_size=CHUNK_SIZE,
       chunk_overlap=CHUNK_OVERLAP,
       length_function=len,
       add_start_index=True,
   )
   chunks = text_splitter.split_documents(documents)
   print(f"Разбили {len(documents)} документов на {len(chunks)} чанков.")

   return chunks

Размер чанков я выбрал 750, для нашего теста с книгой лучше брать побольше, ибо чем больше контекста будет у LLM-ки, тем качественнее вы получите ответ.

Ну и, собственно, объединяем:

def main():
   documents = load_documents()
   chunks = split_text(documents)
   save_to_chroma(chunks)

Теперь, когда у нас заполнена база знаний, можем переходить к составлению запроса. Я создал файл ask.py. Начнем с промпта для нашей LLM-ки. Кстати, ее я не менял, также будем использовать SaigaMistral.

PROMPT_TEMPLATE = """
Ответь на вопрос базируясь только на этом контексте:

{context}

---

Ответь на вопрос, используя только контекст: {question}
"""

Такой вот простейший промпт будет четко объяснять нашей LLM, что мы от нее хотим.

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

def main():
   # Читаем аргументы запуска
   parser = argparse.ArgumentParser()
   parser.add_argument("query_text", type=str)
   args = parser.parse_args()
   query_text = args.query_text

   # Создаем БД
   db = Chroma(persist_directory=chroma.CHROMA_PATH, embedding_function=chroma.get_embeddings())

   # Ищем по БД
   # Мы будем использовать 3 чанка из БД, которые наиболее похожи на наш вопрос
   # c этим количеством можете экспериментировать как угодно, главное, не переборщите, ваша LLM
   # должна поддерживать такое количество контекста, чтобы уместить весь полученный промпт
   results = db.similarity_search_with_relevance_scores(query_text, k=3)
   if len(results) == 0 or results[0][1] < 0.7:
       print(f"Нет фрагментов текста, на которые можно опираться для ответа.")
       return

   # Собираем запрос к LLM, объединяя наши чанки. Их мы записываем через пропуск строки и ---
   # помещаем мы контекст в переменную context, которую обозначали еще в самом промпте
   # ну и по аналогии вопрос просто записываем в переменную question.
   context_text = "\n\n---\n\n".join([doc.page_content for doc, _score in results])
   prompt_template = ChatPromptTemplate.from_template(PROMPT_TEMPLATE)
   prompt = prompt_template.format(context=context_text, question=query_text)
   print(f"Полученный промпт {prompt}")

   # Подключение к LM Studio и отправка запроса
   model = ChatOpenAI(temperature=0.7, base_url="http://localhost:1234/v1", api_key="not-needed")
   response_text = model.predict(prompt)

   # Выводим результаты ответа
   sources = [doc.metadata.get("source", None) for doc, _score in results]
   formatted_response = f"Ответ: {response_text}\nДанные взяты из: {sources}"
   print(formatted_response)

Итак, как же теперь воспользоваться всем тем, что мы написали?

Каждый раз, когда вы меняете состав документов или сами документы, вам нужно запустить запись в БД, делаем это командой python chroma.py. Или как вы назвали файл создания БД.

Вот что мы должны получить в выводе:

  • Разбили 1 документов на 491 чанков.

  • Сохранили 491 чанков в chroma.

Теперь не забываем стартануть LM Studio в режиме сервера, подробнее про это я рассказывал в прошлой статье.

После того как база заполнена можем начать задавать вопросы (python ask.py "ваш вопрос"). Например: python ask.py "У кого из героинь была черная родинка?"

Ответ будет примерно таким:

Полученный промпт Human:
Ответь на вопрос базируясь только на этом контексте:

Сегодня я встал поздно; прихожу к колодцу - никого уже нет. Становилось жарко; белые мохнатые тучки быстро бежали от снеговых гор, обещая грозу; голова Машука дымилась, как загашенный факел; кругом него вились и ползали, как змеи, серые клочки облаков, задержанные в своем стремлении и будто зацепившиеся за колючий его кустарник. Воздух был напоен электричеством. Я углубился в виноградную аллею, ведущую в грот; мне было грустно. Я думал о той молодой женщине с родинкой на щеке, про которую говорил мне доктор... Зачем она здесь? И она ли? И почему я думаю, что это она? и почему я даже так в этом уверен? Мало ли женщин с родинками на щеках? Размышляя таким образом, я подошел к самому гроту. Смотрю: в прохладной тени его свода, на каменной

___

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

___

А вы были в Москве, доктор?

Да, я имел там некоторую практику.

Продолжайте.

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

Вы никого у них не видали сегодня?

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

Родинка! - пробормотал я сквозь зубы. - Неужели?

___

Ответь на вопрос, используя только контекст: У кого из героинь на правой щеке была чёрная родинка?

Как видите контекст задается из отрывков книги, где фигурирует родинка. Ну и, собственно, сам ответ:

Ответ: Родительница княгини по мужу.

Данные взяты из: ['data\books\geroy-nashego-vremeni.txt', 'data\books\geroy-nashego-vremeni.txt', 'data\books\geroy-nashego-vremeni.txt']

Возможно на экзамене у вас бы такой ответ не приняли, но ответ по сути-то правильный. :)

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

Теги:
Хабы:
Всего голосов 14: ↑14 и ↓0+14
Комментарии8

Публикации

Информация

Сайт
www.reksoft.ru
Дата регистрации
Дата основания
Численность
1 001–5 000 человек
Местоположение
Россия