spinny:~/writing $ vim rag-langchain-deep-dive.md
1~2Các Mô hình Ngôn ngữ Lớn (LLM) như GPT-4 và Claude cực kỳ mạnh mẽ, nhưng chúng có một hạn chế cơ bản: kiến thức của chúng bị đóng băng tại thời điểm huấn luyện. Chúng không thể truy cập tài liệu nội bộ, cơ sở dữ liệu hay thông tin thời gian thực của bạn. **Sinh tăng cường bằng truy xuất (RAG)** giải quyết chính xác vấn đề này bằng cách kết hợp sức mạnh sinh của LLM với khả năng truy xuất thông tin từ các nguồn bên ngoài.3~4## Vấn đề: Hạn chế của LLM5~6Trước khi nói về RAG, điều quan trọng là phải hiểu tại sao chúng ta cần nó.7~81. **Kiến thức tĩnh**: Một LLM chỉ biết những gì nó đã thấy trong quá trình huấn luyện. Nếu bạn hỏi về một sự kiện xảy ra sau thời điểm cắt, nó không thể trả lời.92. **Ảo giác**: Khi một LLM không biết câu trả lời, nó có xu hướng bịa ra, tạo ra thông tin có vẻ hợp lý nhưng hoàn toàn sai.103. **Không truy cập được dữ liệu riêng tư**: Một LLM thông thường không có quyền truy cập vào tài liệu nội bộ, ticket hay mã nguồn của công ty bạn.11~12RAG giải quyết cả ba vấn đề này bằng cách cung cấp cho mô hình **ngữ cảnh liên quan** được truy xuất từ các nguồn bên ngoài tại thời điểm truy vấn.13~14## RAG là gì?15~16Sinh tăng cường bằng truy xuất là một kiến trúc làm giàu prompt gửi đến LLM bằng thông tin được truy xuất từ cơ sở tri thức bên ngoài. Thay vì chỉ dựa vào kiến thức tham số của mô hình, RAG **tìm kiếm** thông tin liên quan trước rồi **chèn** vào prompt, cho phép mô hình tạo ra các phản hồi chính xác, có căn cứ.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 hoạt động chi tiết như thế nào28~29Kiến trúc RAG bao gồm hai giai đoạn chính: **Lập chỉ mục** (ngoại tuyến) và **Truy xuất + Sinh** (trực tuyến).30~31### Giai đoạn 1: Lập chỉ mục (Thu thập tài liệu)32~33Giai đoạn lập chỉ mục chuẩn bị tài liệu của bạn cho tìm kiếm ngữ nghĩa. Nó gồm bốn bước.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. Tải tài liệu46~47Tài liệu có thể đến từ bất kỳ nguồn nào: tệp PDF, trang web, cơ sở dữ liệu, tệp Markdown, API. **Document Loader** đọc các tài liệu này và chuyển đổi chúng thành văn bản có cấu trúc.48~49#### 2. Phân tách văn bản (Chunking)50~51LLM có cửa sổ ngữ cảnh giới hạn và tài liệu có thể rất dài. **Text Splitter** chia tài liệu thành các đoạn nhỏ hơn gọi là *chunks*. Chất lượng chunking rất quan trọng: chunks quá nhỏ mất ngữ cảnh, trong khi chunks quá lớn làm loãng mức độ liên quan.52~53Các chiến lược phổ biến nhất là:54- **Phân tách ký tự đệ quy**: Phân tách văn bản đệ quy sử dụng các dấu phân cách như `\n\n`, `\n`, `. `, tôn trọng cấu trúc tài liệu.55- **Phân tách ngữ nghĩa**: Sử dụng embeddings để tìm các điểm ngắt tự nhiên trong văn bản.56- **Chunk chồng lấp**: Bao gồm sự chồng lấp giữa các chunks liên tiếp để bảo toàn ngữ cảnh tại ranh giới.57~58#### 3. Embedding59~60Mỗi chunk được chuyển đổi thành một **vector số** (embedding) thông qua mô hình embedding (như `text-embedding-3-small` của OpenAI). Các vector này nắm bắt ý nghĩa ngữ nghĩa của văn bản: các câu có nghĩa tương tự sẽ có các vector gần nhau trong không gian đa chiều.61~62#### 4. Vector Store63~64Các vector được lưu trong **Vector Store** (hoặc cơ sở dữ liệu vector), như ChromaDB, Pinecone, Weaviate hoặc FAISS. Cơ sở dữ liệu này được tối ưu hóa cho **tìm kiếm tương tự**: với một truy vấn, nó tìm các vector tương tự nhất (và do đó là các chunks văn bản liên quan nhất).65~66### Giai đoạn 2: Truy xuất + Sinh67~68Khi người dùng đặt câu hỏi:69~701. Câu hỏi được chuyển đổi thành embedding sử dụng cùng mô hình embedding.712. Vector Store tìm các chunks tương tự nhất qua **tìm kiếm tương tự** (thường là cosine similarity hoặc khoảng cách Euclidean).723. Các chunks được truy xuất được chèn vào prompt làm ngữ cảnh.734. LLM tạo phản hồi dựa trên ngữ cảnh được cung cấp.74~75## Xây dựng Pipeline RAG với LangChain76~77**LangChain** là framework Python (và JavaScript) phổ biến nhất để xây dựng ứng dụng dựa trên LLM. Nó cung cấp các abstraction cấp cao cho mọi thành phần của pipeline RAG.78~79### Cài đặt80~81```bash82pip install langchain langchain-openai langchain-community chromadb83```84~85### Bước 1: Tải tài liệu86~87LangChain cung cấp hàng chục Document Loader cho các nguồn dữ liệu khác nhau.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### Bước 2: Phân tách tài liệu thành 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~127Tham số `chunk_overlap` rất quan trọng: nó tạo sự chồng lấp giữa các chunks liên tiếp để ngữ cảnh không bị mất tại ranh giới.128~129### Bước 3: Tạo Embeddings và 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### Bước 4: Tạo Retriever145~146Retriever là thành phần mà, với một truy vấn, lấy các chunks liên quan nhất từ 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### Bước 5: Xây dựng RAG Chain161~162Bây giờ hãy kết hợp tất cả với một LLM và một template prompt.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## Kỹ thuật RAG nâng cao199~200Pipeline cơ bản hoạt động tốt, nhưng có một số kỹ thuật để cải thiện đáng kể chất lượng phản hồi.201~202### Truy xuất đa truy vấn203~204Đôi khi truy vấn của người dùng mơ hồ hoặc không phù hợp với ngôn ngữ sử dụng trong tài liệu. **Multi-Query Retriever** tự động tạo các biến thể của câu hỏi gốc để nắm bắt nhiều góc nhìn.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### Nén ngữ cảnh218~219Không phải tất cả nội dung trong một chunk đều liên quan đến truy vấn. **Contextual Compression Retriever** sử dụng LLM để trích xuất chỉ các phần liên quan từ mỗi chunk được truy xuất.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### Tìm kiếm kết hợp233~234Tìm kiếm ngữ nghĩa thuần túy không phải lúc nào cũng tối ưu. **Tìm kiếm kết hợp** kết hợp tìm kiếm ngữ nghĩa (embeddings) với tìm kiếm từ vựng (BM25, khớp từ khóa) để đạt kết quả tốt hơn.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 hội thoại (có bộ nhớ)252~253Để xây dựng chatbot RAG nhớ ngữ cảnh hội thoại, bạn cần thêm bộ nhớ để tái cấu trúc câu hỏi của người dùng dựa trên lịch sử hội thoại.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## Các phương pháp tốt nhất272~2731. **Chọn kích thước chunk phù hợp**: Thử nghiệm với các kích thước khác nhau (500-1500 token). Chunks nhỏ hơn cho câu trả lời chính xác, lớn hơn cho ngữ cảnh rộng hơn.2742. **Sử dụng metadata tài liệu**: Thêm nguồn, ngày và danh mục làm metadata cho chunks. Điều này cho phép lọc kết quả trong quá trình truy xuất.2753. **Đánh giá chất lượng**: Sử dụng các framework như [RAGAS](https://docs.ragas.io/) để đo các chỉ số như *faithfulness*, *relevancy* và *context precision*.2764. **Quản lý cập nhật tài liệu**: Triển khai pipeline tái thu thập để giữ vector store đồng bộ với nguồn dữ liệu của bạn.2775. **Thêm re-ranker**: Sau khi truy xuất ban đầu, sử dụng mô hình re-ranking (như Cohere Rerank) để sắp xếp lại kết quả dựa trên mức độ liên quan thực tế.278~279## Kết luận280~281RAG đã trở thành kiến trúc tiêu chuẩn để xây dựng ứng dụng AI cần truy cập kiến thức cụ thể, cập nhật. LangChain đơn giản hóa đáng kể việc triển khai, cung cấp các abstraction cho mọi thành phần của pipeline.282~283**Các bước tiếp theo:**284- **Thử nghiệm cục bộ**: Bắt đầu với ChromaDB và một vài tài liệu để làm quen với pipeline.285- **Khám phá LangSmith**: Sử dụng [LangSmith](https://smith.langchain.com/) để giám sát và gỡ lỗi chain của bạn trong môi trường production.286- **Thử các mô hình embedding khác nhau**: So sánh các mô hình như `text-embedding-3-small`, `text-embedding-3-large` và các mô hình mã nguồn mở từ Sentence Transformers.287- **Xem tài liệu**: [Tài liệu LangChain](https://python.langchain.com/docs/) là nguồn tài nguyên tuyệt vời và được cập nhật liên tục.288~
NORMAL · rag-langchain-deep-dive.md [readonly]288 lines · :q to close