spinny:~/writing $ vim rag-langchain-deep-dive.md
1~2Большие языковые модели (LLMs), такие как GPT-4 и Claude, необычайно мощны, но страдают от фундаментального ограничения: их знания заморожены на момент обучения. Они не могут получить доступ к вашим внутренним документам, вашей базе данных или информации в реальном времени. **Генерация с дополненной выборкой (RAG)** решает именно эту проблему, сочетая генеративную мощь LLM со способностью извлекать информацию из внешних источников.3~4## Проблема: ограничения LLM5~6Прежде чем говорить о RAG, важно понять, зачем он нужен.7~81. **Статические знания**: LLM знает только то, что видел во время обучения. Если вы спросите о событии, произошедшем после его отсечки, он не сможет ответить.92. **Галлюцинации**: Когда LLM не знает ответа, он склонен его выдумывать, генерируя правдоподобную, но полностью ложную информацию.103. **Нет доступа к приватным данным**: Обычный LLM не имеет доступа к внутренней документации вашей компании, тикетам или кодовой базе.11~12RAG решает все три эти проблемы, предоставляя модели **релевантный контекст**, извлечённый из внешних источников в момент запроса.13~14## Что такое RAG?15~16Генерация с дополненной выборкой - это архитектура, которая обогащает промпт, отправляемый LLM, информацией, извлечённой из внешней базы знаний. Вместо того чтобы полагаться исключительно на параметрические знания модели, RAG сначала **ищет** релевантную информацию, а затем **внедряет** её в промпт, позволяя модели генерировать точные, обоснованные ответы.17~18```mermaid19graph LR20 User["User"] -- "Question" --> Retriever21 Retriever -- "Search relevant\ndocuments" --> VectorStore["Vector Store"]22 VectorStore -- "Relevant\ndocuments" --> Retriever23 Retriever -- "Context + Question" --> LLM24 LLM -- "Grounded\nresponse" --> User25```26~27## Как RAG работает в деталях28~29Архитектура RAG состоит из двух основных фаз: **Индексация** (офлайн) и **Извлечение + Генерация** (онлайн).30~31### Фаза 1: Индексация (загрузка документов)32~33Фаза индексации подготавливает ваши документы для семантического поиска. Она состоит из четырёх шагов.34~35```mermaid36graph TD37 A["Documents\n(PDF, HTML, MD, DB)"] --> B["Document Loader"]38 B --> C["Text Splitter"]39 C --> D["Text Chunks"]40 D --> E["Embedding Model"]41 E --> F["Numerical Vectors"]42 F --> G["Vector Store\n(ChromaDB, Pinecone, FAISS)"]43```44~45#### 1. Загрузка документов46~47Документы могут поступать из любого источника: PDF-файлы, веб-страницы, базы данных, Markdown-файлы, API. **Document Loader** читает эти документы и преобразует их в структурированный текст.48~49#### 2. Разбиение текста (Chunking)50~51LLM имеют ограниченное контекстное окно, а документы могут быть очень длинными. **Text Splitter** делит документы на меньшие фрагменты, называемые *chunks*. Качество разбиения критически важно: слишком маленькие chunks теряют контекст, а слишком большие размывают релевантность.52~53Наиболее распространённые стратегии:54- **Рекурсивное разбиение по символам**: Рекурсивно разбивает текст, используя разделители типа `\n\n`, `\n`, `. `, соблюдая структуру документа.55- **Семантическое разбиение**: Использует embeddings для нахождения естественных точек разрыва в тексте.56- **Перекрытие chunks**: Включает перекрытие между последовательными chunks для сохранения контекста на границах.57~58#### 3. Embedding59~60Каждый chunk преобразуется в **числовой вектор** (embedding) с помощью модели embedding (например, `text-embedding-3-small` от OpenAI). Эти векторы захватывают семантическое значение текста: предложения с похожим смыслом будут иметь близкие векторы в многомерном пространстве.61~62#### 4. Vector Store63~64Векторы сохраняются в **Vector Store** (или векторной базе данных), такой как ChromaDB, Pinecone, Weaviate или FAISS. Эта база данных оптимизирована для **поиска по сходству**: по заданному запросу она находит наиболее похожие векторы (и, следовательно, наиболее релевантные текстовые chunks).65~66### Фаза 2: Извлечение + Генерация67~68Когда пользователь задаёт вопрос:69~701. Вопрос преобразуется в embedding с использованием той же модели embedding.712. Vector Store находит наиболее похожие chunks через **поиск по сходству** (обычно косинусное сходство или евклидово расстояние).723. Извлечённые chunks вставляются в промпт в качестве контекста.734. LLM генерирует ответ на основе предоставленного контекста.74~75## Построение RAG-пайплайна с LangChain76~77**LangChain** - самый популярный фреймворк на Python (и JavaScript) для создания приложений на основе LLM. Он предоставляет высокоуровневые абстракции для каждого компонента RAG-пайплайна.78~79### Установка80~81```bash82pip install langchain langchain-openai langchain-community chromadb83```84~85### Шаг 1: Загрузка документов86~87LangChain предоставляет десятки Document Loaders для различных источников данных.88~89```python90from langchain_community.document_loaders import (91 PyPDFLoader,92 WebBaseLoader,93 DirectoryLoader,94 TextLoader,95)96~97# Load a PDF98pdf_loader = PyPDFLoader("docs/manual.pdf")99pdf_docs = pdf_loader.load()100~101# Load a web page102web_loader = WebBaseLoader("https://docs.example.com/guide")103web_docs = web_loader.load()104~105# Load all .md files from a directory106dir_loader = DirectoryLoader("./knowledge_base", glob="**/*.md", loader_cls=TextLoader)107md_docs = dir_loader.load()108~109all_docs = pdf_docs + web_docs + md_docs110```111~112### Шаг 2: Разбиение документов на chunks113~114```python115from langchain.text_splitter import RecursiveCharacterTextSplitter116~117text_splitter = RecursiveCharacterTextSplitter(118 chunk_size=1000,119 chunk_overlap=200,120 separators=["\n\n", "\n", ". ", " ", ""],121)122~123chunks = text_splitter.split_documents(all_docs)124print(f"Original documents: {len(all_docs)}, Chunks: {len(chunks)}")125```126~127Параметр `chunk_overlap` критически важен: он создаёт перекрытие между последовательными chunks, чтобы контекст не терялся на границах.128~129### Шаг 3: Создание Embeddings и Vector Store130~131```python132from langchain_openai import OpenAIEmbeddings133from langchain_community.vectorstores import Chroma134~135embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")136~137vectorstore = Chroma.from_documents(138 documents=chunks,139 embedding=embedding_model,140 persist_directory="./chroma_db",141)142```143~144### Шаг 4: Создание Retriever145~146Retriever - это компонент, который по заданному запросу извлекает наиболее релевантные chunks из vector store.147~148```python149retriever = vectorstore.as_retriever(150 search_type="similarity",151 search_kwargs={"k": 4},152)153~154relevant_docs = retriever.invoke("How does authentication work?")155for doc in relevant_docs:156 print(doc.page_content[:200])157 print("---")158```159~160### Шаг 5: Построение RAG-цепочки161~162Теперь соединим всё вместе с LLM и шаблоном промпта.163~164```python165from langchain_openai import ChatOpenAI166from langchain_core.prompts import ChatPromptTemplate167from langchain_core.runnables import RunnablePassthrough168from langchain_core.output_parsers import StrOutputParser169~170llm = ChatOpenAI(model="gpt-4o", temperature=0)171~172prompt = ChatPromptTemplate.from_template("""173Answer the question based only on the provided context.174If the context does not contain enough information, say you don't know.175~176Context:177{context}178~179Question: {question}180~181Answer:182""")183~184def format_docs(docs):185 return "\n\n".join(doc.page_content for doc in docs)186~187rag_chain = (188 {"context": retriever | format_docs, "question": RunnablePassthrough()}189 | prompt190 | llm191 | StrOutputParser()192)193~194response = rag_chain.invoke("How does authentication work in the system?")195print(response)196```197~198## Продвинутые техники RAG199~200Базовый пайплайн работает хорошо, но существует несколько техник для значительного улучшения качества ответов.201~202### Мульти-запросное извлечение203~204Иногда запрос пользователя неоднозначен или не совпадает с языком, используемым в документах. **Multi-Query Retriever** автоматически генерирует варианты исходного вопроса для захвата множества перспектив.205~206```python207from langchain.retrievers import MultiQueryRetriever208~209multi_retriever = MultiQueryRetriever.from_llm(210 retriever=vectorstore.as_retriever(),211 llm=llm,212)213~214docs = multi_retriever.invoke("What are the security best practices?")215```216~217### Контекстуальное сжатие218~219Не весь контент в chunk релевантен запросу. **Contextual Compression Retriever** использует LLM для извлечения только релевантных частей из каждого полученного chunk.220~221```python222from langchain.retrievers import ContextualCompressionRetriever223from langchain.retrievers.document_compressors import LLMChainExtractor224~225compressor = LLMChainExtractor.from_llm(llm)226compression_retriever = ContextualCompressionRetriever(227 base_compressor=compressor,228 base_retriever=retriever,229)230```231~232### Гибридный поиск233~234Чисто семантический поиск не всегда оптимален. **Гибридный поиск** сочетает семантический поиск (embeddings) с лексическим поиском (BM25, сопоставление ключевых слов) для достижения лучших результатов.235~236```python237from langchain.retrievers import EnsembleRetriever238from langchain_community.retrievers import BM25Retriever239~240bm25_retriever = BM25Retriever.from_documents(chunks)241bm25_retriever.k = 4242~243semantic_retriever = vectorstore.as_retriever(search_kwargs={"k": 4})244~245hybrid_retriever = EnsembleRetriever(246 retrievers=[bm25_retriever, semantic_retriever],247 weights=[0.4, 0.6],248)249```250~251### Разговорный RAG (с памятью)252~253Чтобы построить RAG-чатбот, который помнит контекст разговора, необходимо добавить память, которая переформулирует вопросы пользователя с учётом истории разговора.254~255```python256from langchain.chains import create_history_aware_retriever257from langchain_core.prompts import MessagesPlaceholder258~259contextualize_prompt = ChatPromptTemplate.from_messages([260 ("system", "Given the chat history and the user's latest question, "261 "reformulate the question so it is understandable without the history."),262 MessagesPlaceholder("chat_history"),263 ("human", "{input}"),264])265~266history_aware_retriever = create_history_aware_retriever(267 llm, retriever, contextualize_prompt268)269```270~271## Лучшие практики272~2731. **Выбирайте правильный размер chunk**: Экспериментируйте с различными размерами (500-1500 токенов). Меньшие chunks для точных ответов, большие для более широкого контекста.2742. **Используйте метаданные документов**: Добавляйте источник, дату и категорию в качестве метаданных к chunks. Это позволяет фильтровать результаты при извлечении.2753. **Оценивайте качество**: Используйте фреймворки вроде [RAGAS](https://docs.ragas.io/) для измерения таких метрик, как *faithfulness*, *relevancy* и *context precision*.2764. **Управляйте обновлениями документов**: Реализуйте пайплайн повторной загрузки для поддержания синхронизации vector store с вашими источниками данных.2775. **Добавьте re-ranker**: После первоначального извлечения используйте модель re-ranking (например, Cohere Rerank) для переупорядочивания результатов на основе реальной релевантности.278~279## Заключение280~281RAG стал стандартной архитектурой для создания AI-приложений, которым нужен доступ к конкретным, актуальным знаниям. LangChain значительно упрощает реализацию, предоставляя абстракции для каждого компонента пайплайна.282~283**Следующие шаги:**284- **Экспериментируйте локально**: Начните с ChromaDB и нескольких документов, чтобы ознакомиться с пайплайном.285- **Изучите LangSmith**: Используйте [LangSmith](https://smith.langchain.com/) для мониторинга и отладки ваших цепочек в продакшене.286- **Попробуйте разные модели embedding**: Сравните модели `text-embedding-3-small`, `text-embedding-3-large` и open-source модели от Sentence Transformers.287- **Ознакомьтесь с документацией**: [Документация LangChain](https://python.langchain.com/docs/) - отличный и постоянно обновляемый ресурс.288~
NORMAL · rag-langchain-deep-dive.md [readonly]288 lines · :q to close