Bỏ qua

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?

Token: [Relevant] / [Irrelevant]

Đánh giá: query ↔ chunk_i → [Relevant/Irrelevant]

Answer faithfulness - Câu trả lời có dựa trên tài liệu không?

Token: [Fully supported] / [Partially supported] / [No support]

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

def my_node(state: AgentState) -> dict:
    # Process...
    return {"field_to_update": new_value}

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.