2.7 Чат-боты на LangChain
Эта глава посвящена созданию и оптимизации разговорных чат‑ботов на LangChain — инструменте, который связывает языковые модели с системами извлечения для динамических QA‑возможностей. Мы сосредоточимся на практическом пути: от подготовки окружения и загрузки документов до построения векторного хранилища, выбора продвинутых стратегий извлечения и подключения памяти диалога, чтобы бот удерживал контекст и уверенно отвечал на уточняющие вопросы. Разговорные боты меняют взаимодействие с данными: вместо примитивных сценариев с независимыми репликами они понимают и запоминают ход беседы, а модульная архитектура LangChain позволяет шаг за шагом подключать загрузчики (80+ форматов), разбиение на чанки, эмбеддинги, семантический поиск, self‑query и контекстное сжатие. Важная деталь — ранняя настройка окружения и переменных: включённая наблюдаемость и аккуратная работа с ключами ускоряют отладку и эксплуатацию. Дальше переходим к сборке ядра — Conversational Retrieval Chain
, которая объединяет языковую модель, ретривер и память, — и к примерам, где память буфера сохраняет последовательность сообщений и передаёт её вместе с новыми вопросами, делая диалог естественным и цельным.
Начнём с инициализации окружения и ключей API, чтобы безопасно работать с облачными LLM и подготовить интерфейс:
Сначала корректно настраиваем окружение и безопасно работаем с API-ключами для доступа к облачным LLM (например, OpenAI GPT).
# Импортируем необходимые библиотеки для управления окружением и доступа к API
import os
from dotenv import load_dotenv, find_dotenv
# Убеждаемся, что Panel для GUI правильно импортирован и инициализирован для интерактивных приложений
import panel as pn
pn.extension()
# Загружаем файл .env для безопасного доступа к переменным окружения, включая ключ OpenAI API
_ = load_dotenv(find_dotenv())
# OPENAI_API_KEY читается интеграциями автоматически; прямое присваивание не требуется
Выберем версию языковой модели и зафиксируем её на время демонстрации:
# Импортируем библиотеку datetime для управления логикой на основе дат для выбора модели
import datetime
# Определяем текущую дату для принятия решения о версии языковой модели
current_date = datetime.datetime.now().date()
# Выбираем версию языковой модели
language_model_version = "gpt-3.5-turbo"
# Отображаем выбранную версию языковой модели
print(language_model_version)
Теперь подключим эмбеддинги и векторное хранилище для базового QA: загрузим/индексируем документы, найдём релевантные фрагменты и подготовим модель для ответов. Затем зададим шаблон промпта и соберём цепочку RetrievalQA, которая будет использовать наш ретривер и аккуратно формировать ответы на вопросы в контексте:
# Импортируем необходимые библиотеки для работы с эмбеддингами и векторными хранилищами
from langchain.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
# Настраиваем переменные окружения для доступа к API LangChain
# Примечание: Замените 'your_directory_path' на фактический путь к директории, где вы намерены хранить эмбеддинги документов
# и 'your_api_key' на ваш фактический ключ API LangChain для аутентификации
persist_directory = 'your_directory_path/'
embedding_function = OpenAIEmbeddings()
vector_database = Chroma(persist_directory=persist_directory, embedding_function=embedding_function)
# Определяем вопрос, для которого вы хотите найти релевантные документы
search_question = "What are the key subjects covered in this course?"
# Выполняем поиск по схожести для нахождения топ-3 документов, связанных с вопросом
top_documents = vector_database.similarity_search(search_question, k=3)
# Определяем количество найденных документов
number_of_documents = len(top_documents)
print(f"Количество найденных релевантных документов: {number_of_documents}")
# Импортируем модель Chat из LangChain для генерации ответов
from langchain_openai import ChatOpenAI
# Инициализируем языковую модель для чата, устанавливая температуру модели на 0 для детерминированных ответов
language_model = ChatOpenAI(model='gpt-4o-mini', temperature=0)
# Пример генерации простого приветственного ответа
greeting_response = language_model.invoke("Greetings, universe!")
print(greeting_response)
# Создаем шаблон промпта для структурированного ответа на вопросы
from langchain.prompts import PromptTemplate
# Определяем шаблон, который инструктирует, как использовать предоставленный контекст для предоставления краткого и полезного ответа
prompt_template = """
Use the following pieces of context to answer the question at the end. If you're unsure about the answer, indicate so rather than speculating.
Try to keep your response within three sentences for clarity and conciseness.
End your answer with "thanks for asking!" to maintain a polite tone.
Context: {context}
Question: {question}
Helpful Answer:
"""
# Инициализируем объект PromptTemplate с указанными входными переменными и определённым шаблоном
qa_prompt_template = PromptTemplate(input_variables=["context", "question"], template=prompt_template)
# Запускаем цепочку конверсационного извлечения и ответов на вопросы
from langchain.chains import RetrievalQA
# Определяем конкретный вопрос, на который нужно ответить в контексте беседы
specific_question = "Does this course require understanding of probability?"
# Инициализируем цепочку QA с языковой моделью, векторной базой данных как ретривером и пользовательским шаблоном промпта
qa_chain = RetrievalQA.from_chain_type(language_model,
retriever=vector_database.as_retriever(),
return_source_documents=True,
chain_type_kwargs={"prompt": qa_prompt_template})
# Выполняем цепочку QA с конкретным вопросом для получения структурированного и полезного ответа
qa_result = qa_chain({"query": specific_question})
# Выводим результирующий ответ из цепочки QA
print("Результирующий ответ:", qa_result["result"])
Реализация Conversational Retrieval Chain
с памятью в Q&A-системах
Этот раздел предназначен для ML-инженеров, специалистов по данным и разработчиков, создающих Q&A-системы, которые понимают и удерживают контекст беседы. Фокус сделан на интеграцию Conversational Retrieval Chain
с компонентом памяти из библиотеки LangChain.
Настройка памяти для истории диалога
Чтобы система запоминала контекст беседы, используем ConversationBufferMemory
. Этот класс хранит историю сообщений и позволяет ссылаться на предыдущие реплики для релевантных уточняющих ответов.
# Импортируем класс ConversationBufferMemory из модуля langchain.memory
from langchain.memory import ConversationBufferMemory
# Инициализируем ConversationBufferMemory с ключом для хранения истории чата
# и настраиваем его для возврата полного списка сообщений, обмененных во время беседы
conversation_history_memory = ConversationBufferMemory(
memory_key="conversation_history",
return_messages=True
)
Сборка Conversational Retrieval Chain
После настройки памяти собираем цепочку: объединяем языковую модель, ретривер документов и память диалога, чтобы обрабатывать вопросы и генерировать ответы в контексте беседы.
# Импортируем класс ConversationalRetrievalChain из модуля langchain.chains
from langchain.chains import ConversationalRetrievalChain
# Предполагаем, что 'vector_database' — это инициализированный экземпляр векторного хранилища, используемого для извлечения документов
# Преобразуем векторную базу данных в формат ретривера, совместимый с ConversationalRetrievalChain
document_retriever = vector_database.as_retriever()
# Инициализируем ConversationalRetrievalChain с языковой моделью, ретривером документов
# и компонентом памяти истории беседы
question_answering_chain = ConversationalRetrievalChain.from_llm(
llm=language_model,
retriever=document_retriever,
memory=conversation_history_memory
)
Обработка вопросов и генерация ответов
После установки Conversational Retrieval Chain
система может обрабатывать входящие вопросы и генерировать соответствующие ответы, используя сохранённую историю беседы для контекста.
# Определяем вопрос, связанный с темой беседы
initial_question = "Is probability a fundamental topic in this course?"
# Обрабатываем вопрос через Conversational Retrieval Chain
initial_result = question_answering_chain({"question": initial_question})
# Извлекаем и выводим ответ из результата
print("Ответ:", initial_result['answer'])
# Следуем с другим вопросом, опираясь на контекст первоначального вопроса
follow_up_question = "Why are those topics considered prerequisites?"
# Обрабатываем уточняющий вопрос, используя историю беседы для контекста
follow_up_result = question_answering_chain({"question": follow_up_question})
# Извлекаем и выводим уточняющий ответ
print("Ответ:", follow_up_result['answer'])
Создание чат-бота для Q&A на основе документов
Эта часть главы предоставляет комплексное руководство по разработке чат-бота, способного обрабатывать вопросы и ответы (Q&A) на основе содержания документов. Ориентированная на ML-инженеров, специалистов по данным, разработчиков программного обеспечения и связанных специалистов, эта секция охватывает процесс от загрузки документов до реализации конверсационной цепочки извлечения. Следующие инструкции и фрагменты кода разработаны для обеспечения ясности, улучшения читаемости и предоставления практического руководства для создания эффективного чат-бота с использованием LangChain.
Начальная настройка и импорты
Перед тем как углубиться в процесс создания чат-бота, критически важно импортировать необходимые классы и модули из LangChain. Эти компоненты облегчают загрузку документов, разбиение текста, генерацию эмбеддингов и создание конверсационных цепочек.
# Импортируем классы для генерации эмбеддингов, разбиения текста, поиска в памяти, загрузки документов,
# конверсационных цепочек и обработки памяти из LangChain
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import DocArrayInMemorySearch
from langchain.document_loaders import TextLoader, PyPDFLoader
from langchain.chains import ConversationalRetrievalChain
from langchain_openai import ChatOpenAI
Загрузка и обработка документов
Первый шаг в создании чат-бота — это загрузка и обработка документов, которые будут служить базой знаний для ответов на вопросы. Это включает чтение документов, разбиение их на управляемые фрагменты и генерацию эмбеддингов для каждого фрагмента.
def load_documents_and_prepare_database(file_path, chain_type, top_k_results):
"""
Загружает документы из указанного файла, разбивает их на управляемые фрагменты,
генерирует эмбеддинги и подготавливает векторную базу данных для извлечения.
Args:
- file_path: Путь к файлу документа (PDF, текст и т.д.).
- chain_type: Указывает тип конверсационной цепочки для использования.
- top_k_results: Количество топ-результатов для извлечения в поисках.
Returns:
- Экземпляр конверсационной цепочки извлечения, готовый для ответов на вопросы.
"""
# Загружаем документы, используя соответствующий загрузчик на основе типа файла
document_loader = PyPDFLoader(file_path)
documents = document_loader.load()
# Разбиваем документы на фрагменты для более лёгкой обработки и извлечения
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)
document_chunks = text_splitter.split_documents(documents)
# Генерируем эмбеддинги для каждого фрагмента документа
embeddings_generator = OpenAIEmbeddings()
vector_database = DocArrayInMemorySearch.from_documents(document_chunks, embeddings_generator)
# Подготавливаем ретривер документов для конверсационной цепочки
document_retriever = vector_database.as_retriever(search_type="similarity", search_kwargs={"k": top_k_results})
# Инициализируем конверсационную цепочку извлечения с указанными параметрами
chatbot_chain = ConversationalRetrievalChain.from_llm(
llm=ChatOpenAI(model='gpt-4o-mini', temperature=0),
chain_type=chain_type,
retriever=document_retriever,
return_source_documents=True,
return_generated_question=True,
)
return chatbot_chain
Перейдём к созданию чат‑бота и пользовательского интерфейса на Panel: сначала импортируем необходимые библиотеки — Panel (pn)
для UI и Param (param)
для параметров — и опишем класс бота, инкапсулирующий загрузку документов, обработку запросов и ведение истории.
import panel as pn
import param
Определим класс чат‑бота DocumentBasedChatbot
, который хранит историю, формирует ответы и позволяет подменять базовый документ.
class DocumentBasedChatbot(param.Parameterized):
conversation_history = param.List([]) # Для хранения пар запрос-ответ
current_answer = param.String("") # Последний ответ чат-бота
database_query = param.String("") # Запрос, отправленный в базу данных документов
database_response = param.List([]) # Документы, извлечённые как ответы
def __init__(self, **params):
super(DocumentBasedChatbot, self).__init__(**params)
self.interface_elements = [] # Хранит элементы UI для отображения беседы
self.loaded_document = "docs/cs229_lectures/MachineLearning-Lecture01.pdf" # Документ по умолчанию
self.chatbot_model = load_db(self.loaded_document, "retrieval_type", 4) # Инициализируем модель чат-бота
Добавим загрузку документов: load_document
проверяет пользовательский файл (или берёт документ по умолчанию), перезагружает базу знаний и очищает историю беседы при смене источника.
def load_document(self, upload_count):
if upload_count == 0 or not file_input.value: # Проверяем, загружен ли новый файл
return pn.pane.Markdown(f"Загруженный документ: {self.loaded_document}")
else:
file_input.save("temp.pdf") # Сохраняем загруженный файл временно
self.loaded_document = file_input.filename
self.chatbot_model = load_db("temp.pdf", "retrieval_type", 4) # Загружаем новый документ в модель
self.clear_conversation_history()
return pn.pane.Markdown(f"Загруженный документ: {self.loaded_document}")
Далее — обработка запросов: process_query
получает реплику, обращается к модели, обновляет историю и UI и показывает выдержки из источников.
def process_query(self, user_query):
if not user_query:
return pn.WidgetBox(pn.Row('Пользователь:', pn.pane.Markdown("", width=600)), scroll=True)
result = self.chatbot_model({"question": user_query, "chat_history": self.conversation_history})
self.conversation_history.extend([(user_query, result["answer"])])
self.database_query = result["generated_question"]
self.database_response = result["source_documents"]
self.current_answer = result['answer']
self.interface_elements.extend([
pn.Row('Пользователь:', pn.pane.Markdown(user_query, width=600)),
pn.Row('ЧатБот:', pn.pane.Markdown(self.current_answer, width=600, style={'background-color': '#F6F6F6'}))
])
input_field.value = '' # Очищаем поле ввода после обработки
return pn.WidgetBox(*self.interface_elements, scroll=True)
Для прозрачности реализуем отображение последнего запроса к базе и полученных документов‑источников.
def display_last_database_query(self):
if not self.database_query:
return pn.Column(
pn.Row(pn.pane.Markdown("Последний запрос к базе данных:", style={'background-color': '#F6F6F6'})),
pn.Row(pn.pane.Str("Пока не было сделано запросов к базе данных"))
)
return pn.Column(
pn.Row(pn.pane.Markdown("Запрос к базе данных:", style={'background-color': '#F6F6F6'})),
pn.pane.Str(self.database_query)
)
def display_database_responses(self):
if not self.database_response:
return
response_list = [pn.Row(pn.pane.Markdown("Результат поиска в базе данных:", style={'background-color': '#F6F6F6'}))]
for doc in self.database_response:
response_list.append(pn.Row(pn.pane.Str(doc)))
return pn.WidgetBox(*response_list, width=600, scroll=True)
И не забудем про сброс: clear_conversation_history
очищает весь текущий контекст диалога.
def clear_conversation_history(self, count=0):
self.conversation_history = []
Ниже вынесем вспомогательные импорты и инициализацию второго варианта класса бота, а также элементы интерфейса — загрузка документа, очистка истории и поле ввода вопроса.
import panel as pn
import param
# Определяем класс чат-бота с необходимыми функциями
class ChatWithYourDataBot(param.Parameterized):
# Инициализируем параметры для хранения истории беседы, ответов и запросов к документам
conversation_history = param.List([])
latest_answer = param.String("")
document_query = param.String("")
document_response = param.List([])
def __init__(self, **params):
super(ChatWithYourDataBot, self).__init__(**params)
# Заполнитель для элементов UI
self.interface_elements = []
# Путь к документу по умолчанию
self.default_document_path = "docs/cs229_lectures/MachineLearning-Lecture01.pdf"
# Инициализируем модель чат-бота с документом по умолчанию
self.chatbot_model = load_db(self.default_document_path, "retrieval_mode", 4)
Создадим элементы UI для загрузки, управления историей и ввода запросов и свяжем их с методами класса.
# Компоненты UI для загрузки документов и взаимодействия
document_upload = pn.widgets.FileInput(accept='.pdf')
load_database_button = pn.widgets.Button(name="Загрузить документ", button_type='primary')
clear_history_button = pn.widgets.Button(name="Очистить историю", button_type='warning')
clear_history_button.on_click(ChatWithYourDataBot.clear_history)
user_query_input = pn.widgets.TextInput(placeholder='Введите ваш вопрос здесь…')
# Привязка компонентов UI к функциям чат-бота
load_document_action = pn.bind(ChatWithYourDataBot.load_document, load_database_button.param.clicks)
process_query = pn.bind(ChatWithYourDataBot.process_query, user_query_input)
Соберём интерфейс беседы: панель с вводом, областью диалога и дополнительными вкладками для последних запросов к базе и источников ответов.
# Панель изображения для визуального представления
conversation_visual = pn.pane.Image('./img/conversation_flow.jpg')
# Организация вкладки беседы
conversation_tab = pn.Column(
pn.Row(user_query_input),
pn.layout.Divider(),
pn.panel(process_query, loading_indicator=True, height=300),
pn.layout.Divider(),
)
# Организация дополнительных информационных вкладок (Запросы к базе данных, Исходные документы, История чата)
database_query_tab = pn.Column(
pn.panel(ChatWithYourDataBot.display_last_database_query),
pn.layout.Divider(),
pn.panel(ChatWithYourDataBot.display_database_responses),
)
chat_history_tab = pn.Column(
pn.panel(ChatWithYourDataBot.display_chat_history),
pn.layout.Divider(),
)
configuration_tab = pn.Column(
pn.Row(document_upload, load_database_button, load_document_action),
pn.Row(clear_history_button, pn.pane.Markdown("Очищает историю беседы для новой темы.")),
pn.layout.Divider(),
pn.Row(conversation_visual.clone(width=400)),
)
# Сборка панели управления
chatbot_dashboard = pn.Column(
pn.Row(pn.pane.Markdown('# ChatWithYourDat-Bot')),
pn.Tabs(('Беседа', conversation_tab), ('Запросы к базе данных', database_query_tab), ('История чата', chat_history_tab), ('Настройка', configuration_tab))
)
Итогом становится цельная методика: настроили окружение и ключи, загрузили документы и собрали векторное хранилище, подключили продвинутые извлечения (self‑query, компрессию, семантический поиск), добавили память диалога и собрали Conversational Retrieval Chain
, где модель, ретривер и память работают в связке. Примеры и код показывают, как шаги складываются в рабочего бота; такие решения легко расширять и отлаживать благодаря модульности LangChain.
Теоретические вопросы
- Какие компоненты нужны для настройки окружения разработки чат-ботов на LangChain?
- Как учёт истории диалога повышает функциональность чат-бота?
- Как преобразуются фрагменты документов в эмбеддинги и зачем это нужно?
- Чем полезны self-query, компрессия и семантический поиск?
- Как
Conversational Retrieval Chain
объединяет модель, ретривер и память? - Как
ConversationBufferMemory
помогает удерживать контекст беседы? - Какие шаги по настройке векторного хранилища для семантического поиска в LangChain?
- Зачем управлять переменными окружения и ключами API?
- Как модульность методов извлечения LangChain повышает гибкость разработки?
- Почему важно выбирать подходящую версию языковой модели?
Практические задания
- Создайте и наполните векторное хранилище (
create_vector_store
) из списка строк (используйте заглушку эмбеддинговembed_document
). - Реализуйте семантический поиск (
perform_semantic_search
): эмбеддинг запроса, поиск ближайшего документа, возврат индекса. - Добавьте учёт истории в класс
Chatbot
и методrespond_to_query
(генерация через заглушкуgenerate_response
). - Соберите упрощённый
Conversational Retrieval Chain
с заглушками (LanguageModel
/DocumentRetriever
/ConversationMemory
). - В
Chatbot
добавьте методы пополнения и сброса истории; учитывайте её при генерации. - Документный QA: загрузка строки, разбиение, эмбеддинги, векторное хранилище, семантический поиск и генерация ответа (заглушки).
- Интегрируйте память в retrieval-цепочку (используйте расширение из п.5–6).
- Сделайте простой CLI для общения с
Chatbot
: ввод запросов, ответы, просмотр/сброс истории.