Bài 6: Agentic RAG¶
Tổng quan¶
RAG truyền thống là pipeline cố định: luôn retrieve → generate. Agentic RAG thêm vào khả năng quyết định: có cần retrieve không? Retrieved chunks có đủ tốt không? Câu trả lời có đủ tin cậy không? Nếu không - làm gì tiếp theo?
1. Giới hạn của RAG Truyền thống¶
| Vấn đề | Mô tả | Ví dụ |
|---|---|---|
| Always retrieves | Retrieve ngay cả khi không cần | "2 + 2 = ?" vẫn search vector DB |
| No quality check | Không kiểm tra chunks có relevant không | Trả về nonsense nếu không có thông tin |
| Single-hop | Không thể chain nhiều retrieval steps | Câu hỏi cần info từ 2 tài liệu khác nhau |
| No fallback | Không có plan B khi không tìm được | Hallucinate thay vì từ chối |
2. Self-RAG¶
Self-RAG thêm reflection tokens vào quá trình generation - model tự đánh giá ở mỗi bước.
Ba loại Reflection¶
graph LR
Q[Query] --> R1{Retrieve?}
R1 -->|Yes| RETRIEVE[Retrieve Chunks]
R1 -->|No| GENERATE[Generate Directly]
RETRIEVE --> R2{Chunk Relevant?}
R2 -->|Relevant| GENERATE
R2 -->|Irrelevant| SKIP[Skip chunk]
GENERATE --> R3{Answer Faithful?}
R3 -->|Faithful| OUTPUT[Final Answer]
R3 -->|Not faithful| RETRY[Retry or abstain]
Retrieve necessity - Có cần retrieve không?
Token: [Retrieve] / [No Retrieve]
Query: "Công ty TNHH có thể có bao nhiêu thành viên?" → [Retrieve]
Query: "Tổng của 5 và 3 là bao nhiêu?" → [No Retrieve]
Chunk relevance - Chunk có liên quan không?
Answer faithfulness - Câu trả lời có dựa trên tài liệu không?
3. Corrective RAG (CRAG)¶
CRAG là phiên bản practical hơn của Self-RAG với grading + fallback to web search.
Workflow CRAG¶
graph TD
Q[User Query] --> RETRIEVE[Retrieve from Vector DB]
RETRIEVE --> GRADE[Grade Each Chunk]
GRADE --> DECISION{Quality?}
DECISION -->|All relevant| GENERATE[Generate Answer]
DECISION -->|Mixed / Ambiguous| REFINE[Refine Knowledge<br/>+ Web Search]
DECISION -->|All irrelevant| WEB[Web Search Fallback]
REFINE --> GENERATE
WEB --> GENERATE
GENERATE --> OUTPUT[Final Answer]
Implementation với LangGraph¶
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from langchain_community.tools import TavilySearchResults
# ====== STATE ======
class GraphState(TypedDict):
question: str
documents: list
generation: str
web_search_needed: bool
# ====== NODES ======
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
web_search = TavilySearchResults(max_results=3)
def retrieve(state: GraphState) -> GraphState:
"""Retrieve từ vector DB"""
docs = retriever.invoke(state["question"])
return {"documents": docs}
def grade_documents(state: GraphState) -> GraphState:
"""Grade relevance của mỗi document"""
grade_prompt = """Đánh giá xem TÀI LIỆU có LIÊN QUAN đến CÂU HỎI không.
Trả về JSON: {{"relevant": true/false, "reason": "..."}}
Câu hỏi: {question}
Tài liệu: {document}"""
relevant_docs = []
web_search_needed = False
for doc in state["documents"]:
result = llm.invoke(grade_prompt.format(
question=state["question"],
document=doc.page_content
))
# Parse result...
if is_relevant(result):
relevant_docs.append(doc)
else:
web_search_needed = True # Có ít nhất 1 doc không relevant
if not relevant_docs:
web_search_needed = True # Không có doc nào relevant
return {
"documents": relevant_docs,
"web_search_needed": web_search_needed,
}
def web_search_node(state: GraphState) -> GraphState:
"""Fallback: tìm kiếm web"""
search_results = web_search.invoke(state["question"])
# Convert to Document format và append
web_docs = [Document(page_content=r["content"]) for r in search_results]
return {"documents": state["documents"] + web_docs}
def generate(state: GraphState) -> GraphState:
"""Generate final answer"""
context = "\n\n".join([d.page_content for d in state["documents"]])
answer = rag_chain.invoke({"context": context, "question": state["question"]})
return {"generation": answer}
# ====== EDGES ======
def decide_web_search(state: GraphState) -> str:
if state["web_search_needed"]:
return "web_search"
return "generate"
# ====== BUILD GRAPH ======
workflow = StateGraph(GraphState)
workflow.add_node("retrieve", retrieve)
workflow.add_node("grade_documents", grade_documents)
workflow.add_node("web_search", web_search_node)
workflow.add_node("generate", generate)
workflow.set_entry_point("retrieve")
workflow.add_edge("retrieve", "grade_documents")
workflow.add_conditional_edges(
"grade_documents",
decide_web_search,
{"web_search": "web_search", "generate": "generate"}
)
workflow.add_edge("web_search", "generate")
workflow.add_edge("generate", END)
app = workflow.compile()
# Chạy
result = app.invoke({"question": "Luật doanh nghiệp 2020 quy định gì về công ty TNHH?"})
print(result["generation"])
4. Query Decomposition & Multi-hop¶
Khi nào cần?¶
Câu hỏi phức tạp cần thông tin từ nhiều nguồn hoặc nhiều bước suy luận:
"So sánh điều kiện thành lập công ty TNHH và công ty cổ phần theo luật 2020,
và cho biết loại nào phù hợp hơn cho startup công nghệ?"
Cần decompose thành:
Sub-query 1: "Điều kiện thành lập công ty TNHH theo luật 2020"
Sub-query 2: "Điều kiện thành lập công ty cổ phần theo luật 2020"
Sub-query 3: [Synthesis] So sánh và đưa ra khuyến nghị
Implementation¶
decompose_prompt = """Chia câu hỏi phức tạp sau thành 2-4 sub-questions đơn giản hơn.
Mỗi sub-question phải trả lời độc lập được.
Câu hỏi: {question}
Trả về JSON: {{"sub_questions": ["câu 1", "câu 2", ...]}}"""
def decompose_query(question: str) -> list[str]:
result = llm.invoke(decompose_prompt.format(question=question))
return parse_json(result)["sub_questions"]
def multi_hop_rag(question: str) -> str:
sub_questions = decompose_query(question)
# Retrieve cho từng sub-question
all_contexts = []
for sub_q in sub_questions:
docs = retriever.invoke(sub_q)
all_contexts.extend(docs)
# Deduplicate
unique_contexts = list({d.page_content: d for d in all_contexts}.values())
# Generate final answer với tất cả context
return rag_chain.invoke({
"context": format_context(unique_contexts),
"question": question
})
5. LangGraph Basics¶
LangGraph cho phép xây dựng stateful multi-step workflows cho LLM - phù hợp cho Agentic RAG.
Các khái niệm cốt lõi¶
State: TypedDict chứa toàn bộ thông tin của workflow
class AgentState(TypedDict):
messages: list # Conversation history
documents: list # Retrieved documents
iterations: int # Số lần retry
final_answer: str # Kết quả cuối
Nodes: Functions nhận state → trả về partial state update
Edges: Kết nối các nodes
# Static edge: luôn đi từ A sang B
graph.add_edge("node_a", "node_b")
# Conditional edge: quyết định dựa trên state
graph.add_conditional_edges(
"node_a",
router_function, # Trả về tên node tiếp theo
{
"option_1": "node_b",
"option_2": "node_c",
}
)
Ví dụ: RAG với Retry Loop¶
MAX_RETRIES = 3
def check_answer_quality(state: GraphState) -> str:
"""Router: tiếp tục hay retry?"""
if state["iterations"] >= MAX_RETRIES:
return "end"
if is_answer_good(state["generation"]):
return "end"
return "retrieve" # Retry với rewritten query
workflow.add_conditional_edges(
"generate",
check_answer_quality,
{"end": END, "retrieve": "retrieve"}
)
Tóm tắt: Chọn RAG Pattern¶
| Pattern | Phù hợp khi | Độ phức tạp |
|---|---|---|
| Basic RAG | Query đơn giản, corpus nhỏ | ⭐ |
| Self-RAG | Cần kiểm soát retrieval necessity | ⭐⭐⭐ |
| CRAG | Corpus không đầy đủ, cần fallback | ⭐⭐ |
| Query Decomposition | Câu hỏi phức tạp, multi-aspect | ⭐⭐ |
| Multi-hop | Câu hỏi cần chain nhiều retrieval | ⭐⭐⭐ |
Bắt đầu đơn giản
Luôn bắt đầu với Basic RAG, đo lường chất lượng với RAGAS, sau đó thêm complexity khi cần. CRAG là điểm nâng cấp phổ biến nhất khi Basic RAG không đủ tốt.