От текстового хаоса к ясности: RAG и LangChain на практике
Практический пример извлечения смысла из групп текстов с помощью RAG, LangChain, FAISS и моделей OpenAI.

В современном мире, где решения всё чаще принимаются на основе данных, объём доступной информации поражает. Главная задача — пройти через этот массив и получить выводы, которые можно использовать на практике. Здесь на сцену выходят обработка естественного языка (NLP) и Retrieval-Augmented Generation (RAG).
NLP помогает понимать и обрабатывать текстовые данные, а RAG дополняет возможности языковых моделей: находит релевантную информацию и использует её для более обоснованного ответа. Вместе они образуют мощную цепочку обработки языка. В этой статье мы извлечём общий смысл из нескольких групп публикаций с помощью RAG и LangChain.
Настройка окружения
Сначала подключим необходимые библиотеки и загрузим API-ключ OpenAI из файла окружения.
from operator import itemgetter
import openai
import os
import pandas as pd
from dotenv import load_dotenv, dotenv_values
load_dotenv()
load_openai_key_from_env()
def load_openai_key_from_env(filepath=".env"):
config = dotenv_values(filepath)
openai_api_key = config['open_ai_key']
set_openai_key(openai_api_key)
def set_openai_key(api_key):
openai.api_key = api_key
os.environ['OPENAI_API_KEY'] = api_key
Предполагается, что файл .env находится в одной папке с остальными скриптами. Его содержимое:
open_ai_key=YOUR_API_KEY
Данные
Максимально упростим формат данных. Каждый столбец — Group_1, Group_2 и так далее — содержит серию публикаций, для которых нужно найти общий смысл. Здесь также можно загрузить CSV или получить данные из базы и преобразовать их в переменную data.
data = {
'Group': [1, 2, 3],
'Group_1': [
"@JamesSmith James, great job on the project! Now imagine if everyone in the protest group, soldiers, pilots, and others were this efficient.",
"@LauraMiles Laura, please apologize to the entire community rather than playing the hero.",
'What about the "Brothers in Arms" group? Suddenly they agree on the new policy?'
],
'Group_2': [
"Hey everyone! Starting a new week with some exciting news. Keep an eye on our Instagram for an exclusive offer.",
"BREAKING: The mock test is now LIVE. Download it from our blog or check our Instagram profile.",
"Hello everyone! We have a new section in our Instagram stories: quizzes and shortcut challenges."
],
'Group_3': [
"Go team! #newproject #teamwork #worklife #colleagues #bestteam",
"With renowned journalist and friend, David Foster. Presented a talk about the life of Beethoven.",
"Study hard, play hard! #worklife #balance #goals #unilife"
]
}
df = pd.DataFrame(data)
df.head()
Где здесь RAG?
Прежде чем использовать RAG, разберёмся, что это такое и зачем он нужен. RAG — подход в NLP и информационном поиске, который объединяет два основных компонента: ретривер и генератор.
- Гибридный подход. Обычные языковые модели хорошо генерируют естественный текст, но не всегда точны в фактах. RAG сначала находит подходящие фрагменты в корпусе, а затем передаёт их генератору, например GPT, для подготовки ответа.
- Более надёжные ответы на фактические вопросы. Ответ опирается на найденные документы, а не только на знания, запомненные моделью.
- Эффективность. Вместо обработки всего корпуса ретривер отбирает несколько релевантных фрагментов, и генератор работает уже с ними.
- Данные не всегда актуальны. Если источник статичен, система не узнает о новых событиях без регулярного обновления корпуса или динамического поиска.
- Не требуется исчерпывающий fine-tuning. RAG способен работать с вашими данными без полного дообучения модели на всём наборе документов.
Цепочка поиска и генерации
from typing import List, Callable
def setup_chain(selected_columns: List[str]) -> Callable:
def get_context_from_columns() -> str:
return " ".join(selected_columns)
template = """Answer the question based only on the following context:
{context}
Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI() # генератор
vectorstore = FAISS.from_texts(
selected_columns,
embedding=OpenAIEmbeddings()
)
retriever = vectorstore.as_retriever()
chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
return chain
Логика этой функции состоит из четырёх шагов:
setup_chainсоздаёт цепочку, которая совмещает поиск по текстам через FAISS и ответы модели OpenAI.- FAISS находит в
selected_columnsнаиболее релевантные вопросу фрагменты. - Шаблон промпта явно требует отвечать только на основании найденного контекста.
- Цепочка получает контекст, формирует запрос, отправляет его модели и преобразует результат в строку.
Как использовать код для подготовки сводок
В переменной question можно задать практически любой вопрос. Ниже — пример запуска цепочки для нескольких столбцов датафрейма.
def print_chain_results(df, question: str, start_col: int, end_col: int) -> None:
for col in df.columns[start_col:end_col + 1]:
output = summarize_all_posts(df, col).split("
")
answer_chain = setup_chain(output)
answer = answer_chain.invoke(question)
print(f"Question: {question}")
print(f"Number of group: {col}")
print(f"Answer: {answer}
")
question = "What is most popular topic in text?"
start_col_index = 1
end_col_index = 3
print_chain_results(df, question, start_col_index, end_col_index)
Итоги
В этой статье я показал, как относительно безболезненно извлекать информацию из текстов с помощью LLM OpenAI. Код можно использовать как шаблон для других моделей: достаточно заменить модель и векторное хранилище под свои требования.
Полный код находится в репозитории GitHub. Полезна также документация LangChain по retrieval.