@xdev-asia/xdev-knowledge-mcp 1.0.57 → 1.0.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (17) hide show
  1. package/content/blog/ai/minimax-danh-gia-chi-tiet-nen-tang-ai-full-stack-trung-quoc.md +450 -0
  2. package/content/blog/ai/nvidia-dli-generative-ai-chung-chi-va-lo-trinh-hoc.md +894 -0
  3. package/content/metadata/authors/duy-tran.md +2 -0
  4. package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/01-deep-learning-foundations/lessons/01-bai-1-pytorch-neural-network-fundamentals.md +790 -0
  5. package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/01-deep-learning-foundations/lessons/02-bai-2-transformer-architecture-attention.md +984 -0
  6. package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/02-diffusion-models/lessons/01-bai-3-unet-architecture-denoising.md +1111 -0
  7. package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/02-diffusion-models/lessons/02-bai-4-ddpm-forward-reverse-diffusion.md +1007 -0
  8. package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/02-diffusion-models/lessons/03-bai-5-clip-text-to-image-pipeline.md +1037 -0
  9. package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/03-llm-applications-rag/lessons/01-bai-6-llm-inference-pipeline-design.md +929 -0
  10. package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/03-llm-applications-rag/lessons/02-bai-7-rag-retrieval-augmented-generation.md +1099 -0
  11. package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/03-llm-applications-rag/lessons/03-bai-8-rag-agent-build-evaluate.md +1249 -0
  12. package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/04-agentic-ai-customization/lessons/01-bai-9-agentic-ai-multi-agent-systems.md +1357 -0
  13. package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/04-agentic-ai-customization/lessons/02-bai-10-llm-evaluation-lora-fine-tuning.md +1867 -0
  14. package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/index.md +237 -0
  15. package/data/quizzes/nvidia-dli-generative-ai.json +350 -0
  16. package/data/quizzes.json +14 -0
  17. package/package.json +1 -1
@@ -0,0 +1,1249 @@
1
+ ---
2
+ id: 019c9619-nv01-p3-l08
3
+ title: 'Bài 8: RAG Agent — Build & Evaluate'
4
+ slug: bai-8-rag-agent-build-evaluate
5
+ description: >-
6
+ Build RAG Agent: kết hợp retrieval + tools + reasoning.
7
+ Multi-turn conversational RAG.
8
+ Evaluation metrics: faithfulness, relevance, accuracy.
9
+ LLM-as-judge evaluation pattern.
10
+ Assessment prep cho DLI S-FX-15.
11
+ duration_minutes: 90
12
+ is_free: true
13
+ video_url: null
14
+ sort_order: 8
15
+ section_title: "Part 3: LLM Applications & RAG"
16
+ course:
17
+ id: 019c9619-nv01-7001-c001-nv0100000001
18
+ title: 'Luyện thi NVIDIA DLI — Generative AI with Diffusion Models & LLMs'
19
+ slug: luyen-thi-nvidia-dli-generative-ai
20
+ ---
21
+
22
+ <h2 id="1-from-rag-pipeline-to-rag-agent">1. From RAG Pipeline to RAG Agent</h2>
23
+
24
+ <h3 id="1-1-han-che-cua-static-rag">1.1. Hạn chế của Static RAG</h3>
25
+
26
+ <p>Bài 7 đã xây dựng <strong>static RAG pipeline</strong> — retrieve K docs → stuff vào prompt → LLM trả lời. Pipeline này hoạt động tốt cho câu hỏi đơn giản, nhưng có ba hạn chế nghiêm trọng:</p>
27
+
28
+ <ul>
29
+ <li><strong>No reasoning</strong> — pipeline luôn retrieve rồi generate, không biết "nên retrieve không?" hay "cần thêm bước nào?"</li>
30
+ <li><strong>No tool use</strong> — chỉ có một nguồn dữ liệu (vector store). Không thể gọi API, tính toán, hay web search.</li>
31
+ <li><strong>Single retrieval</strong> — retrieve đúng 1 lần. Nếu kết quả không đủ → không biết retrieve lại với query khác.</li>
32
+ <li><strong>No memory</strong> — mỗi câu hỏi xử lý độc lập. Không nhớ context từ câu hỏi trước.</li>
33
+ </ul>
34
+
35
+ <h3 id="1-2-rag-agent-retrieval-as-a-tool">1.2. RAG Agent — Retrieval as a Tool</h3>
36
+
37
+ <p><strong>RAG Agent</strong> nâng cấp static RAG bằng cách biến retrieval thành <em>một trong nhiều tools</em> mà LLM có thể chọn sử dụng. Agent có khả năng:</p>
38
+
39
+ <ul>
40
+ <li><strong>Decide WHEN to retrieve</strong> — nếu đã biết câu trả lời → không cần retrieve</li>
41
+ <li><strong>Choose WHAT tool</strong> — retriever cho tài liệu nội bộ, web search cho tin tức, calculator cho tính toán</li>
42
+ <li><strong>Iterative reasoning</strong> — retrieve → nhận ra thiếu info → retrieve lại với query khác</li>
43
+ <li><strong>Synthesize from multiple sources</strong> — kết hợp kết quả từ nhiều tools</li>
44
+ </ul>
45
+
46
+ <h3 id="1-3-react-pattern">1.3. ReAct Pattern — Thought → Action → Observation</h3>
47
+
48
+ <p><strong>ReAct (Reasoning + Acting)</strong> là pattern phổ biến nhất cho LLM agents. LLM thực hiện loop: suy nghĩ (Thought) → chọn hành động (Action) → quan sát kết quả (Observation) → lặp lại cho đến khi có Final Answer.</p>
49
+
50
+ <pre><code class="language-text">
51
+ ReAct Agent Loop — RAG Agent Decision Flow
52
+ ══════════════════════════════════════════════════════════════════
53
+
54
+ User: "So sánh doanh thu Q3 của công ty với trung bình ngành"
55
+
56
+
57
+ ┌─────────────────────────────────────────────────────────┐
58
+ │ THOUGHT: Cần 2 thông tin — doanh thu nội bộ + TB ngành│
59
+ │ ACTION: retriever_tool("doanh thu Q3 công ty") │
60
+ └────────────────────────┬────────────────────────────────┘
61
+
62
+
63
+ ┌─────────────────────────────────────────────────────────┐
64
+ │ OBSERVATION: "Doanh thu Q3: 150 tỷ VND" │
65
+ │ THOUGHT: Có doanh thu nội bộ rồi. Cần TB ngành → web │
66
+ │ ACTION: web_search_tool("average revenue Q3 2025 │
67
+ │ tech industry Vietnam") │
68
+ └────────────────────────┬────────────────────────────────┘
69
+
70
+
71
+ ┌─────────────────────────────────────────────────────────┐
72
+ │ OBSERVATION: "TB ngành tech VN Q3: 120 tỷ" │
73
+ │ THOUGHT: Có đủ data. Cần so sánh → tính toán │
74
+ │ ACTION: calculator_tool("(150 - 120) / 120 * 100") │
75
+ └────────────────────────┬────────────────────────────────┘
76
+
77
+
78
+ ┌─────────────────────────────────────────────────────────┐
79
+ │ OBSERVATION: "25.0" │
80
+ │ THOUGHT: Đủ data rồi. Tổng hợp câu trả lời. │
81
+ │ FINAL ANSWER: "Doanh thu Q3 công ty (150 tỷ) cao hơn │
82
+ │ trung bình ngành (120 tỷ) khoảng 25%." │
83
+ └─────────────────────────────────────────────────────────┘
84
+ </code></pre>
85
+
86
+ <blockquote><p><strong>Exam tip:</strong> "LLM tự quyết định khi nào retrieve, khi nào dùng tool khác" → đáp án là <strong>Agent</strong> (không phải static RAG chain). "Pattern nào cho phép LLM reasoning + acting iteratively?" → <strong>ReAct</strong>. Phân biệt: <strong>Chain</strong> = fixed sequence, <strong>Agent</strong> = dynamic decision.</p></blockquote>
87
+
88
+ <table>
89
+ <thead>
90
+ <tr><th>Feature</th><th>Static RAG Chain</th><th>RAG Agent</th></tr>
91
+ </thead>
92
+ <tbody>
93
+ <tr><td>Execution flow</td><td>Fixed: Retrieve → Generate</td><td>Dynamic: LLM decides each step</td></tr>
94
+ <tr><td>Tools</td><td>1 retriever only</td><td>Multiple: retriever, search, calc, API...</td></tr>
95
+ <tr><td>Reasoning</td><td>None — always retrieves</td><td>ReAct loop: Thought → Action → Observation</td></tr>
96
+ <tr><td>Multi-step</td><td>Single retrieval</td><td>Can retrieve multiple times with different queries</td></tr>
97
+ <tr><td>Memory</td><td>Stateless</td><td>Can maintain conversation history</td></tr>
98
+ <tr><td>Complexity</td><td>Simple, predictable</td><td>Powerful but harder to debug</td></tr>
99
+ <tr><td>Latency</td><td>Thấp (1 LLM call)</td><td>Cao hơn (nhiều LLM calls)</td></tr>
100
+ <tr><td>Use case</td><td>Simple Q&A over docs</td><td>Complex tasks needing multiple sources</td></tr>
101
+ </tbody>
102
+ </table>
103
+
104
+ <figure><img src="/storage/uploads/2026/04/nvidia-dli-bai8-rag-agent-evaluation.png" alt="RAG Agent với Evaluation — Agent Loop, Tools, LLM-as-Judge Metrics" loading="lazy" /><figcaption>RAG Agent với Evaluation — Agent Loop, Tools, LLM-as-Judge Metrics</figcaption></figure>
105
+
106
+ <h2 id="2-build-rag-agent-langchain">2. Build a RAG Agent với LangChain</h2>
107
+
108
+ <h3 id="2-1-define-tools">2.1. Define Tools</h3>
109
+
110
+ <p>Bước đầu tiên: định nghĩa các <strong>tools</strong> mà agent có thể sử dụng. Mỗi tool có <strong>name</strong>, <strong>description</strong> (LLM đọc description để quyết định dùng tool nào), và <strong>function</strong>.</p>
111
+
112
+ <pre><code class="language-python">
113
+ from langchain_nvidia_ai_endpoints import ChatNVIDIA, NVIDIAEmbeddings
114
+ from langchain_community.vectorstores import FAISS
115
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
116
+ from langchain_community.document_loaders import PyPDFLoader
117
+ from langchain.tools.retriever import create_retriever_tool
118
+ from langchain_community.tools.tavily_search import TavilySearchResults
119
+ from langchain_core.tools import tool
120
+
121
+ # === Tool 1: Document Retriever ===
122
+ loader = PyPDFLoader("company_docs.pdf")
123
+ docs = loader.load()
124
+ splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
125
+ chunks = splitter.split_documents(docs)
126
+ embeddings = NVIDIAEmbeddings(model="NV-Embed-QA")
127
+ vectorstore = FAISS.from_documents(chunks, embeddings)
128
+ retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
129
+
130
+ retriever_tool = create_retriever_tool(
131
+ retriever,
132
+ name="company_docs_search",
133
+ description="Tìm kiếm thông tin trong tài liệu nội bộ công ty. "
134
+ "Dùng khi user hỏi về chính sách, quy trình, nhân sự."
135
+ )
136
+
137
+ # === Tool 2: Web Search ===
138
+ web_search_tool = TavilySearchResults(
139
+ max_results=3,
140
+ description="Tìm kiếm thông tin trên internet. "
141
+ "Dùng khi cần tin tức mới, dữ liệu thị trường, "
142
+ "hoặc thông tin không có trong tài liệu nội bộ."
143
+ )
144
+
145
+ # === Tool 3: Calculator ===
146
+ @tool
147
+ def calculator_tool(expression: str) -> str:
148
+ """Tính toán biểu thức toán học. Dùng khi cần tính phần trăm,
149
+ so sánh số liệu, hoặc các phép tính số học."""
150
+ try:
151
+ result = eval(expression) # Production: dùng numexpr hoặc sympy
152
+ return str(result)
153
+ except Exception as e:
154
+ return f"Lỗi tính toán: {e}"
155
+
156
+ # Danh sách tools
157
+ tools = [retriever_tool, web_search_tool, calculator_tool]
158
+ </code></pre>
159
+
160
+ <blockquote><p><strong>Exam tip:</strong> <strong>Tool description</strong> cực kỳ quan trọng — LLM đọc description để quyết định dùng tool nào. Description mơ hồ → agent chọn sai tool. Đề có thể hỏi: "Agent chọn sai tool, nguyên nhân?" → kiểm tra <strong>tool description</strong>.</p></blockquote>
161
+
162
+ <h3 id="2-2-create-agent">2.2. Create Agent — Tool Calling Agent</h3>
163
+
164
+ <p>LangChain cung cấp hai cách tạo agent: <strong>create_react_agent</strong> (ReAct prompt-based) và <strong>create_tool_calling_agent</strong> (dùng native tool calling API). Với NVIDIA NIM / OpenAI-compatible models, nên dùng <strong>tool calling agent</strong>.</p>
165
+
166
+ <pre><code class="language-python">
167
+ from langchain.agents import create_tool_calling_agent, AgentExecutor
168
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
169
+
170
+ # LLM với tool calling support
171
+ llm = ChatNVIDIA(
172
+ model="meta/llama-3.1-70b-instruct",
173
+ temperature=0.1
174
+ )
175
+
176
+ # Agent prompt — PHẢI có {agent_scratchpad}
177
+ prompt = ChatPromptTemplate.from_messages([
178
+ ("system", """Bạn là trợ lý thông minh của công ty. Sử dụng các tools
179
+ để tìm thông tin chính xác. Luôn trích dẫn nguồn.
180
+ Nếu không tìm được → nói rõ "Không tìm thấy thông tin."
181
+ Không bao giờ bịa thông tin."""),
182
+ MessagesPlaceholder(variable_name="chat_history", optional=True),
183
+ ("human", "{input}"),
184
+ MessagesPlaceholder(variable_name="agent_scratchpad"),
185
+ ])
186
+
187
+ # Tạo agent
188
+ agent = create_tool_calling_agent(llm, tools, prompt)
189
+
190
+ # AgentExecutor: chạy agent loop
191
+ agent_executor = AgentExecutor(
192
+ agent=agent,
193
+ tools=tools,
194
+ verbose=True, # Hiện ReAct loop
195
+ max_iterations=5, # Giới hạn iterations (tránh infinite loop!)
196
+ handle_parsing_errors=True,
197
+ return_intermediate_steps=True # Debug: xem agent đã dùng tools nào
198
+ )
199
+ </code></pre>
200
+
201
+ <h3 id="2-3-agent-executor-run">2.3. Run Agent</h3>
202
+
203
+ <pre><code class="language-python">
204
+ # === Query 1: Cần retrieval ===
205
+ result = agent_executor.invoke({
206
+ "input": "Chính sách nghỉ phép của công ty là gì?"
207
+ })
208
+ print(result["output"])
209
+ # Agent sẽ: Thought → dùng company_docs_search → trả lời
210
+
211
+ # === Query 2: Cần web search ===
212
+ result = agent_executor.invoke({
213
+ "input": "NVIDIA stock price hôm nay bao nhiêu?"
214
+ })
215
+ # Agent sẽ: Thought → dùng web_search → trả lời
216
+
217
+ # === Query 3: Multi-tool ===
218
+ result = agent_executor.invoke({
219
+ "input": "So sánh doanh thu Q3 công ty với trung bình ngành tech VN"
220
+ })
221
+ # Agent sẽ: retriever → web_search → calculator → tổng hợp
222
+
223
+ # === Xem intermediate steps (debug) ===
224
+ for step in result["intermediate_steps"]:
225
+ action, observation = step
226
+ print(f"Tool: {action.tool}")
227
+ print(f"Input: {action.tool_input}")
228
+ print(f"Output: {observation[:100]}...")
229
+ print("---")
230
+ </code></pre>
231
+
232
+ <h3 id="2-4-tool-selection-logic">2.4. Tool Selection Logic</h3>
233
+
234
+ <p>LLM chọn tool dựa trên <strong>semantic matching</strong> giữa user query và tool description. Quá trình:</p>
235
+
236
+ <pre><code class="language-text">
237
+ Tool Selection — How LLM Chooses Tools
238
+ ═══════════════════════════════════════════════════════
239
+
240
+ User Query: "Doanh thu Q3 là bao nhiêu?"
241
+
242
+
243
+ ┌──────────────────────────────────────────────────┐
244
+ │ LLM reads tool descriptions: │
245
+ │ │
246
+ │ 1. company_docs_search: │
247
+ │ "Tìm thông tin trong tài liệu nội bộ. │
248
+ │ Dùng khi hỏi chính sách, quy trình..." │
249
+ │ → Relevance: HIGH ✅ (nội bộ + số liệu) │
250
+ │ │
251
+ │ 2. web_search: │
252
+ │ "Tìm trên internet. Dùng khi cần tin tức, │
253
+ │ dữ liệu thị trường..." │
254
+ │ → Relevance: MEDIUM (có thể cần nếu không │
255
+ │ tìm được trong docs) │
256
+ │ │
257
+ │ 3. calculator: │
258
+ │ "Tính toán biểu thức toán học..." │
259
+ │ → Relevance: LOW (chưa cần tính toán) │
260
+ └──────────────────────┬───────────────────────────┘
261
+
262
+
263
+ Selected: company_docs_search ✅
264
+ </code></pre>
265
+
266
+ <blockquote><p><strong>Exam tip:</strong> "Agent gọi tool không phù hợp" → kiểm tra <strong>tool description có rõ ràng không</strong>. "Agent gọi tool quá nhiều lần (infinite loop)" → set <strong>max_iterations</strong>. Hai parameter quan trọng nhất của AgentExecutor: <strong>max_iterations</strong> (default 15, nên giới hạn 5-10) và <strong>handle_parsing_errors=True</strong>.</p></blockquote>
267
+
268
+ <h2 id="3-multi-turn-conversational-rag">3. Multi-turn Conversational RAG</h2>
269
+
270
+ <h3 id="3-1-van-de-khong-co-memory">3.1. Vấn đề: Không có Memory</h3>
271
+
272
+ <p>Static RAG pipeline xử lý mỗi query độc lập. Khi user hỏi follow-up, pipeline không hiểu context:</p>
273
+
274
+ <pre><code class="language-text">
275
+ Vấn đề khi không có Chat History
276
+ ═══════════════════════════════════════════════
277
+
278
+ User: "Chính sách nghỉ phép là gì?"
279
+ Bot: "Nhân viên được nghỉ 12 ngày/năm..." ✅
280
+
281
+ User: "Còn nghỉ bệnh thì sao?" ← follow-up
282
+ Bot: ??? ← "nghỉ bệnh" không rõ context
283
+ Retriever search "nghỉ bệnh"
284
+ → có thể miss relevant docs
285
+
286
+ User: "Có tính lương không?" ← "có" và "tính lương" → gì?
287
+ Bot: ??? ← Hoàn toàn mất context
288
+ </code></pre>
289
+
290
+ <h3 id="3-2-contextualize-question">3.2. Contextualize Question — Rewrite dựa theo History</h3>
291
+
292
+ <p>Giải pháp: trước khi retrieve, <strong>rewrite câu hỏi</strong> để bao gồm context từ conversation history. "Còn nghỉ bệnh thì sao?" → "Chính sách nghỉ bệnh của công ty là gì? Có được trả lương không?"</p>
293
+
294
+ <pre><code class="language-python">
295
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
296
+ from langchain_core.output_parsers import StrOutputParser
297
+ from langchain.chains import create_history_aware_retriever
298
+
299
+ # Prompt rewrite câu hỏi dựa trên chat history
300
+ contextualize_q_prompt = ChatPromptTemplate.from_messages([
301
+ ("system", """Dựa vào lịch sử hội thoại và câu hỏi mới nhất,
302
+ hãy viết lại câu hỏi thành một câu độc lập (standalone question)
303
+ mà không cần context trước đó cũng hiểu được.
304
+ KHÔNG trả lời câu hỏi — chỉ viết lại nếu cần, hoặc giữ nguyên."""),
305
+ MessagesPlaceholder("chat_history"),
306
+ ("human", "{input}"),
307
+ ])
308
+
309
+ llm = ChatNVIDIA(model="meta/llama-3.1-70b-instruct", temperature=0.1)
310
+
311
+ # History-aware retriever: rewrite query → retrieve
312
+ history_aware_retriever = create_history_aware_retriever(
313
+ llm, retriever, contextualize_q_prompt
314
+ )
315
+ </code></pre>
316
+
317
+ <h3 id="3-3-conversational-rag-chain">3.3. Full Conversational RAG Chain</h3>
318
+
319
+ <pre><code class="language-python">
320
+ from langchain.chains import create_retrieval_chain
321
+ from langchain.chains.combine_documents import create_stuff_documents_chain
322
+ from langchain_core.messages import HumanMessage, AIMessage
323
+
324
+ # QA prompt
325
+ qa_prompt = ChatPromptTemplate.from_messages([
326
+ ("system", """Bạn là trợ lý AI. Trả lời dựa trên context được cung cấp.
327
+ Nếu không tìm thấy → nói "Không tìm thấy trong tài liệu."
328
+
329
+ Context:
330
+ {context}"""),
331
+ MessagesPlaceholder("chat_history"),
332
+ ("human", "{input}"),
333
+ ])
334
+
335
+ # Chain: stuff documents
336
+ question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)
337
+
338
+ # Full conversational RAG chain
339
+ rag_chain = create_retrieval_chain(
340
+ history_aware_retriever, question_answer_chain
341
+ )
342
+
343
+ # === Multi-turn conversation ===
344
+ chat_history = []
345
+
346
+ # Turn 1
347
+ response1 = rag_chain.invoke({
348
+ "input": "Chính sách nghỉ phép là gì?",
349
+ "chat_history": chat_history
350
+ })
351
+ print(response1["answer"])
352
+ # → "Nhân viên được nghỉ 12 ngày phép/năm..."
353
+
354
+ chat_history.extend([
355
+ HumanMessage(content="Chính sách nghỉ phép là gì?"),
356
+ AIMessage(content=response1["answer"])
357
+ ])
358
+
359
+ # Turn 2 — follow-up
360
+ response2 = rag_chain.invoke({
361
+ "input": "Còn nghỉ bệnh thì sao?",
362
+ "chat_history": chat_history
363
+ })
364
+ print(response2["answer"])
365
+ # Câu hỏi được rewrite thành: "Chính sách nghỉ bệnh của công ty là gì?"
366
+ # → Retrieve chính xác hơn!
367
+ </code></pre>
368
+
369
+ <h3 id="3-4-runnablewithmessagehistory">3.4. Auto-manage History: RunnableWithMessageHistory</h3>
370
+
371
+ <pre><code class="language-python">
372
+ from langchain_community.chat_message_histories import ChatMessageHistory
373
+ from langchain_core.runnables.history import RunnableWithMessageHistory
374
+
375
+ # Store session histories
376
+ session_store = {}
377
+
378
+ def get_session_history(session_id: str):
379
+ if session_id not in session_store:
380
+ session_store[session_id] = ChatMessageHistory()
381
+ return session_store[session_id]
382
+
383
+ # Wrap chain với message history management
384
+ conversational_rag = RunnableWithMessageHistory(
385
+ rag_chain,
386
+ get_session_history,
387
+ input_messages_key="input",
388
+ history_messages_key="chat_history",
389
+ output_messages_key="answer",
390
+ )
391
+
392
+ # Sử dụng — history được quản lý tự động theo session_id
393
+ config = {"configurable": {"session_id": "user-123"}}
394
+
395
+ r1 = conversational_rag.invoke(
396
+ {"input": "Chính sách nghỉ phép?"},
397
+ config=config
398
+ )
399
+ # Tự động lưu history
400
+
401
+ r2 = conversational_rag.invoke(
402
+ {"input": "Còn nghỉ bệnh?"}, # sẽ tự rewrite dựa trên history
403
+ config=config
404
+ )
405
+ </code></pre>
406
+
407
+ <pre><code class="language-text">
408
+ Multi-turn Conversational RAG Flow
409
+ ════════════════════════════════════════════════════════════════════
410
+
411
+ User: "Chính sách nghỉ phép?" session_id: "user-123"
412
+
413
+
414
+ ┌──────────────────┐ History: [] (empty)
415
+ │ Contextualize Q │───► Standalone: "Chính sách nghỉ phép?"
416
+ └────────┬─────────┘ (giữ nguyên vì không cần rewrite)
417
+
418
+
419
+ ┌──────────────────┐
420
+ │ Retriever │───► 4 chunks về nghỉ phép
421
+ └────────┬─────────┘
422
+
423
+
424
+ ┌──────────────────┐
425
+ │ LLM Generate │───► "Nhân viên được 12 ngày/năm..."
426
+ └────────┬─────────┘
427
+
428
+
429
+ Save to History: [Human: "Chính sách...", AI: "Nhân viên..."]
430
+
431
+ ─────────────────────────────────────────────────────────────
432
+
433
+ User: "Còn nghỉ bệnh?" session_id: "user-123"
434
+
435
+
436
+ ┌──────────────────┐ History: [nghỉ phép Q&A]
437
+ │ Contextualize Q │───► Rewrite: "Chính sách nghỉ bệnh
438
+ └────────┬─────────┘ của công ty là gì?"
439
+
440
+
441
+ ┌──────────────────┐
442
+ │ Retriever │───► Tìm với rewritten query → chính xác hơn!
443
+ └────────┬─────────┘
444
+
445
+
446
+ ┌──────────────────┐
447
+ │ LLM Generate │───► "Nghỉ bệnh: tối đa 30 ngày/năm..."
448
+ └──────────────────┘
449
+ </code></pre>
450
+
451
+ <blockquote><p><strong>Exam tip:</strong> "User hỏi follow-up nhưng retriever trả sai kết quả" → thiếu <strong>history-aware retriever</strong> (cần contextualize question trước khi retrieve). "Quản lý multi-session chat history" → <strong>RunnableWithMessageHistory</strong> + session_id. Đề DLI có thể hỏi vai trò của <strong>contextualize prompt</strong> — luôn nhấn mạnh: rewrite thành standalone question, KHÔNG trả lời.</p></blockquote>
452
+
453
+ <h2 id="4-evaluation-metrics-rag">4. Evaluation Metrics cho RAG</h2>
454
+
455
+ <h3 id="4-1-tai-sao-can-evaluation">4.1. Tại sao cần Evaluation?</h3>
456
+
457
+ <p>"Nhìn thấy kết quả ổn" không đủ cho production. Cần <strong>systematic evaluation</strong> để đo lường chất lượng RAG pipeline và so sánh giữa các configurations (chunk size, embedding model, retriever type...).</p>
458
+
459
+ <h3 id="4-2-bon-metrics-chinh">4.2. Bốn Metrics Chính</h3>
460
+
461
+ <table>
462
+ <thead>
463
+ <tr><th>Metric</th><th>Đo cái gì?</th><th>Cách tính</th><th>Threshold chấp nhận</th></tr>
464
+ </thead>
465
+ <tbody>
466
+ <tr><td><strong>Faithfulness</strong></td><td>Answer có "bịa" không? Mọi claim trong answer đều có trong context?</td><td>Chia answer thành claims → check từng claim against context</td><td>≥ 0.85</td></tr>
467
+ <tr><td><strong>Answer Relevance</strong></td><td>Answer có thực sự trả lời câu hỏi không?</td><td>Generate câu hỏi từ answer → so cosine similarity với original question</td><td>≥ 0.80</td></tr>
468
+ <tr><td><strong>Context Precision</strong></td><td>Docs retrieved có relevant không? (precision)</td><td>Bao nhiêu docs retrieved thực sự liên quan / tổng số docs retrieved</td><td>≥ 0.75</td></tr>
469
+ <tr><td><strong>Context Recall</strong></td><td>Đã retrieve đủ docs cần thiết chưa? (recall)</td><td>Bao nhiêu claims trong ground truth có thể traced back to retrieved docs</td><td>≥ 0.80</td></tr>
470
+ </tbody>
471
+ </table>
472
+
473
+ <pre><code class="language-text">
474
+ RAG Evaluation — What Each Metric Measures
475
+ ════════════════════════════════════════════════════════════════
476
+
477
+ Question: "Chính sách hoàn tiền?"
478
+
479
+ Retrieved Context (3 docs):
480
+ ┌─────────────────────────────────────────────────────────┐
481
+ │ Doc 1: "Hoàn tiền trong 30 ngày nếu có hóa đơn" ✅ │
482
+ │ Doc 2: "Sản phẩm phải còn nguyên seal" ✅ │
483
+ │ Doc 3: "Menu canteen tuần này" ❌ │
484
+ └─────────────────────────────────────────────────────────┘
485
+ Context Precision = 2/3 = 0.67 ← Doc 3 irrelevant!
486
+
487
+ Ground Truth: "Hoàn tiền 30 ngày, cần hóa đơn, nguyên seal,
488
+ liên hệ CS qua email"
489
+ Retrieved covers: hoàn tiền ✅, hóa đơn ✅, seal ✅, email ❌
490
+ Context Recall = 3/4 = 0.75 ← Thiếu info về email
491
+
492
+ Generated Answer: "Hoàn tiền trong 30 ngày nếu có hóa đơn
493
+ và sản phẩm nguyên seal."
494
+ Claims: [30 ngày ✅, hóa đơn ✅, nguyên seal ✅]
495
+ Faithfulness = 3/3 = 1.0 ← Mọi claim đều grounded!
496
+
497
+ Does answer address the question? → Có, nhưng chưa đầy đủ
498
+ Answer Relevance ≈ 0.85 ← Relevant nhưng thiếu chi tiết email
499
+ </code></pre>
500
+
501
+ <h3 id="4-3-ragas-framework">4.3. RAGAS Framework</h3>
502
+
503
+ <p><strong>RAGAS (Retrieval Augmented Generation Assessment)</strong> là framework open-source phổ biến nhất để evaluate RAG. RAGAS tự động tính 4 metrics ở trên mà không cần human labels (dùng LLM để evaluate).</p>
504
+
505
+ <pre><code class="language-python">
506
+ from ragas import evaluate
507
+ from ragas.metrics import (
508
+ faithfulness,
509
+ answer_relevancy,
510
+ context_precision,
511
+ context_recall,
512
+ )
513
+ from datasets import Dataset
514
+
515
+ # Chuẩn bị evaluation dataset
516
+ eval_data = {
517
+ "question": [
518
+ "Chính sách hoàn tiền là gì?",
519
+ "Nghỉ phép bao nhiêu ngày?"
520
+ ],
521
+ "answer": [
522
+ "Hoàn tiền trong 30 ngày nếu có hóa đơn.",
523
+ "Nhân viên được 12 ngày phép mỗi năm."
524
+ ],
525
+ "contexts": [
526
+ ["Hoàn tiền trong 30 ngày khi có hóa đơn gốc.", "Sản phẩm nguyên seal."],
527
+ ["Nhân viên chính thức: 12 ngày phép/năm.", "Thử việc: 1 ngày/tháng."]
528
+ ],
529
+ "ground_truth": [
530
+ "Khách hàng được hoàn tiền trong 30 ngày với hóa đơn gốc và sản phẩm nguyên seal.",
531
+ "Nhân viên chính thức được nghỉ 12 ngày phép/năm, thử việc 1 ngày/tháng."
532
+ ]
533
+ }
534
+
535
+ dataset = Dataset.from_dict(eval_data)
536
+
537
+ # Evaluate!
538
+ results = evaluate(
539
+ dataset,
540
+ metrics=[faithfulness, answer_relevancy, context_precision, context_recall],
541
+ )
542
+
543
+ print(results)
544
+ # {'faithfulness': 0.95, 'answer_relevancy': 0.88,
545
+ # 'context_precision': 0.83, 'context_recall': 0.75}
546
+
547
+ # Convert to pandas để phân tích chi tiết
548
+ df = results.to_pandas()
549
+ print(df)
550
+ </code></pre>
551
+
552
+ <blockquote><p><strong>Exam tip:</strong> "Answer chứa thông tin không có trong retrieved context" → <strong>Faithfulness thấp</strong>. "Retrieved docs không liên quan đến câu hỏi" → <strong>Context Precision thấp</strong>. "Answer đúng nhưng không trả lời đúng câu hỏi" → <strong>Answer Relevance thấp</strong>. "Thiếu docs quan trọng" → <strong>Context Recall thấp</strong>. Framework evaluate RAG phổ biến → <strong>RAGAS</strong>.</p></blockquote>
553
+
554
+ <h2 id="5-llm-as-judge">5. LLM-as-Judge Evaluation</h2>
555
+
556
+ <h3 id="5-1-tai-sao-llm-as-judge">5.1. Tại sao cần LLM-as-Judge?</h3>
557
+
558
+ <p>Manual evaluation (con người đánh giá) chính xác nhưng <strong>không scale</strong>: 1000 câu trả lời × 3 annotators = 3000 lượt review. <strong>LLM-as-Judge</strong> dùng một LLM mạnh hơn (hoặc cùng tier) để tự động đánh giá output của LLM khác.</p>
559
+
560
+ <table>
561
+ <thead>
562
+ <tr><th>Evaluation Method</th><th>Pros</th><th>Cons</th></tr>
563
+ </thead>
564
+ <tbody>
565
+ <tr><td><strong>Human evaluation</strong></td><td>Gold standard, nuanced</td><td>Expensive, slow, không scalable</td></tr>
566
+ <tr><td><strong>Automatic metrics</strong> (BLEU, ROUGE)</td><td>Fast, cheap, reproducible</td><td>Không capture semantic quality</td></tr>
567
+ <tr><td><strong>LLM-as-Judge</strong></td><td>Scalable, captures semantics</td><td>Bias, cost of judge LLM, không hoàn hảo</td></tr>
568
+ <tr><td><strong>RAGAS (LLM-based)</strong></td><td>Automated, multi-metric</td><td>Phụ thuộc quality của judge LLM</td></tr>
569
+ </tbody>
570
+ </table>
571
+
572
+ <h3 id="5-2-evaluation-prompt-template">5.2. Evaluation Prompt Template</h3>
573
+
574
+ <pre><code class="language-python">
575
+ from langchain_core.prompts import ChatPromptTemplate
576
+ from langchain_core.output_parsers import JsonOutputParser
577
+
578
+ # Faithfulness evaluator prompt
579
+ faithfulness_eval_prompt = ChatPromptTemplate.from_template("""
580
+ You are an impartial judge evaluating the faithfulness of an AI answer.
581
+
582
+ **Faithfulness** means every claim in the answer must be supported by
583
+ the provided context. The answer should NOT contain information
584
+ that cannot be traced back to the context.
585
+
586
+ **Context:**
587
+ {context}
588
+
589
+ **Question:**
590
+ {question}
591
+
592
+ **Answer to evaluate:**
593
+ {answer}
594
+
595
+ Evaluate step by step:
596
+ 1. List all claims made in the answer.
597
+ 2. For each claim, check if it is supported by the context.
598
+ 3. Count supported claims vs total claims.
599
+
600
+ Respond in JSON format:
601
+ {{
602
+ "claims": [
603
+ {{"claim": "...", "supported": true/false, "evidence": "..."}}
604
+ ],
605
+ "faithfulness_score": <float 0.0 to 1.0>,
606
+ "reasoning": "..."
607
+ }}
608
+ """)
609
+
610
+ # Judge LLM — nên dùng model mạnh nhất available
611
+ judge_llm = ChatNVIDIA(
612
+ model="meta/llama-3.1-70b-instruct",
613
+ temperature=0.0 # temperature=0 cho consistent evaluation
614
+ )
615
+
616
+ faithfulness_chain = faithfulness_eval_prompt | judge_llm | JsonOutputParser()
617
+
618
+ # Evaluate một câu trả lời
619
+ eval_result = faithfulness_chain.invoke({
620
+ "context": "Công ty hoàn tiền trong 30 ngày nếu có hóa đơn gốc.",
621
+ "question": "Chính sách hoàn tiền?",
622
+ "answer": "Hoàn tiền trong 30 ngày với hóa đơn. Liên hệ email CS."
623
+ })
624
+
625
+ print(eval_result)
626
+ # {
627
+ # "claims": [
628
+ # {"claim": "Hoàn tiền trong 30 ngày", "supported": true, ...},
629
+ # {"claim": "cần hóa đơn", "supported": true, ...},
630
+ # {"claim": "Liên hệ email CS", "supported": false, ...} ← hallucination!
631
+ # ],
632
+ # "faithfulness_score": 0.67,
633
+ # "reasoning": "2/3 claims supported. 'Liên hệ email CS' not in context."
634
+ # }
635
+ </code></pre>
636
+
637
+ <h3 id="5-3-pairwise-comparison">5.3. Pairwise Comparison — So sánh A vs B</h3>
638
+
639
+ <p>Thay vì chấm điểm tuyệt đối, <strong>pairwise</strong> so sánh hai output và chọn cái tốt hơn. Method này ít bị bias hơn absolute scoring.</p>
640
+
641
+ <pre><code class="language-python">
642
+ pairwise_prompt = ChatPromptTemplate.from_template("""
643
+ You are comparing two AI responses to the same question.
644
+
645
+ **Question:** {question}
646
+ **Context:** {context}
647
+
648
+ **Response A:**
649
+ {response_a}
650
+
651
+ **Response B:**
652
+ {response_b}
653
+
654
+ Compare on these criteria:
655
+ 1. Faithfulness: grounded in context?
656
+ 2. Completeness: covers all relevant info?
657
+ 3. Clarity: well-structured and easy to understand?
658
+
659
+ Choose the better response. Respond in JSON:
660
+ {{
661
+ "winner": "A" or "B" or "TIE",
662
+ "criteria_scores": {{
663
+ "faithfulness": {{"A": <1-5>, "B": <1-5>}},
664
+ "completeness": {{"A": <1-5>, "B": <1-5>}},
665
+ "clarity": {{"A": <1-5>, "B": <1-5>}}
666
+ }},
667
+ "reasoning": "..."
668
+ }}
669
+ """)
670
+
671
+ pairwise_chain = pairwise_prompt | judge_llm | JsonOutputParser()
672
+ </code></pre>
673
+
674
+ <h3 id="5-4-limitations-llm-judge">5.4. Limitations của LLM-as-Judge</h3>
675
+
676
+ <ul>
677
+ <li><strong>Verbosity bias</strong> — LLM judge có xu hướng đánh giá cao output dài hơn, even if shorter answer is better</li>
678
+ <li><strong>Positional bias</strong> — trong pairwise, prefer output ở vị trí đầu (A > B). Giải pháp: đánh giá 2 lần swap vị trí A↔B</li>
679
+ <li><strong>Self-enhancement bias</strong> — LLM judge ưu tiên output của chính mình. Dùng model khác làm judge</li>
680
+ <li><strong>Limited reasoning</strong> — judge có thể miss subtle errors in specialized domains (medical, legal)</li>
681
+ </ul>
682
+
683
+ <pre><code class="language-text">
684
+ Mitigate LLM-as-Judge Bias
685
+ ══════════════════════════════════════════
686
+
687
+ Positional Bias Fix:
688
+ ┌──────────────────────────┐
689
+ │ Round 1: A first, B sec │──► Winner round 1: A
690
+ │ Round 2: B first, A sec │──► Winner round 2: A
691
+ │ Final: Consistent → A │
692
+ │ (If inconsistent → TIE) │
693
+ └──────────────────────────┘
694
+
695
+ Verbosity Bias Fix:
696
+ ┌──────────────────────────┐
697
+ │ Prompt: "Evaluate based │
698
+ │ on accuracy and concise-│
699
+ │ ness. Longer ≠ better." │
700
+ └──────────────────────────┘
701
+ </code></pre>
702
+
703
+ <blockquote><p><strong>Exam tip:</strong> "Evaluate LLM output at scale" → <strong>LLM-as-Judge</strong>. "LLM judge prefer longer answers" → <strong>verbosity bias</strong>. "LLM judge prefer first option in pair" → <strong>positional bias</strong>. Fix positional bias → <strong>swap positions and average</strong>. Đề DLI thường hỏi: "Which evaluation method scales best?" → LLM-as-Judge (not human evaluation).</p></blockquote>
704
+
705
+ <h2 id="6-assessment-prep-dli-sfx15">6. Assessment Prep — DLI S-FX-15</h2>
706
+
707
+ <h3 id="6-1-sfx15-overview">6.1. S-FX-15 Assessment Overview</h3>
708
+
709
+ <p>Course <strong>S-FX-15: "Generative AI with Diffusion Models and Large Language Models"</strong> kết thúc bằng một <strong>hands-on assessment</strong> trong Jupyter notebook. Bạn cần hoàn thành các coding tasks trong thời gian giới hạn.</p>
710
+
711
+ <table>
712
+ <thead>
713
+ <tr><th>Aspect</th><th>Detail</th></tr>
714
+ </thead>
715
+ <tbody>
716
+ <tr><td><strong>Format</strong></td><td>Jupyter notebook — fill in code cells, run tests</td></tr>
717
+ <tr><td><strong>Duration</strong></td><td>~2 giờ (trong lab session)</td></tr>
718
+ <tr><td><strong>Passing</strong></td><td>Hoàn thành tất cả required cells + output chính xác</td></tr>
719
+ <tr><td><strong>Tools available</strong></td><td>Course notebooks, NVIDIA docs (trong DLI environment)</td></tr>
720
+ <tr><td><strong>Retake</strong></td><td>Có thể retake nếu fail (theo policy DLI)</td></tr>
721
+ </tbody>
722
+ </table>
723
+
724
+ <h3 id="6-2-key-areas">6.2. Key Areas Covered</h3>
725
+
726
+ <p>Assessment S-FX-15 bao gồm các lĩnh vực chính từ cả ba phần đã học:</p>
727
+
728
+ <table>
729
+ <thead>
730
+ <tr><th>Part</th><th>Key Topics</th><th>Likely Assessment Tasks</th></tr>
731
+ </thead>
732
+ <tbody>
733
+ <tr><td><strong>Part 1</strong>: Generative AI Fundamentals</td><td>Diffusion models, VAE, GAN</td><td>Configure diffusion pipeline, generate images</td></tr>
734
+ <tr><td><strong>Part 2</strong>: LLM Core</td><td>Transformer, tokenizer, PEFT, inference</td><td>Load model, tokenize, LoRA fine-tuning, inference params</td></tr>
735
+ <tr><td><strong>Part 3</strong>: RAG & Applications</td><td>RAG pipeline, agent, evaluation</td><td>Build RAG, implement evaluation, add guardrails</td></tr>
736
+ </tbody>
737
+ </table>
738
+
739
+ <h3 id="6-3-time-management">6.3. Time Management Strategy</h3>
740
+
741
+ <pre><code class="language-text">
742
+ S-FX-15 Time Management
743
+ ════════════════════════════════════════════
744
+
745
+ Total: ~120 minutes
746
+
747
+ ┌─────────────────────────────────────┐
748
+ │ 0-10 min: Đọc toàn bộ notebook │ ← KHÔNG code ngay!
749
+ │ Đánh dấu cells dễ/khó │
750
+ │ Xác định dependencies │
751
+ └─────────────────────────────────────┘
752
+ ┌─────────────────────────────────────┐
753
+ │ 10-50 min: Làm cells dễ trước │ ← Quick wins first
754
+ │ Import, setup, config │
755
+ │ Straightforward tasks │
756
+ └─────────────────────────────────────┘
757
+ ┌─────────────────────────────────────┐
758
+ │ 50-100 min: Cells khó │ ← RAG pipeline, eval
759
+ │ Multi-step tasks │
760
+ │ Debug if needed │
761
+ └─────────────────────────────────────┘
762
+ ┌─────────────────────────────────────┐
763
+ │ 100-120 min: Review & fix │ ← Run ALL cells top-down
764
+ │ Check outputs match │
765
+ │ Fix any errors │
766
+ └─────────────────────────────────────┘
767
+ </code></pre>
768
+
769
+ <h3 id="6-4-common-mistakes">6.4. Common Mistakes — Tránh những lỗi này</h3>
770
+
771
+ <table>
772
+ <thead>
773
+ <tr><th>Mistake</th><th>Consequence</th><th>Fix</th></tr>
774
+ </thead>
775
+ <tbody>
776
+ <tr><td>Quên <code>chunk_overlap</code> khi chunking</td><td>Context bị cắt ở ranh giới → answer kém</td><td>Luôn set overlap = 10-20% chunk_size</td></tr>
777
+ <tr><td>Dùng sai embedding model cho retriever vs ingestion</td><td>Dimension mismatch → crash</td><td>Cùng model cho cả embed và retrieve</td></tr>
778
+ <tr><td>Không set <code>temperature=0</code> cho evaluation</td><td>Kết quả evaluation không reproducible</td><td>Evaluation tasks: <code>temperature=0</code></td></tr>
779
+ <tr><td>Agent infinite loop</td><td>Timeout, fail cell</td><td>Set <code>max_iterations=5</code></td></tr>
780
+ <tr><td>Quên <code>handle_parsing_errors=True</code></td><td>Agent crash khi LLM trả format sai</td><td>Luôn bật flag này</td></tr>
781
+ <tr><td>Không format context đúng trong RAG prompt</td><td>LLM bỏ qua context → hallucinate</td><td>Chia rõ <code>{context}</code> trong prompt template</td></tr>
782
+ <tr><td>Run cells không theo thứ tự</td><td>Variable undefined errors</td><td>Run top-down, hoặc "Restart & Run All"</td></tr>
783
+ <tr><td>Quên install packages</td><td>Import errors</td><td>Chạy <code>!pip install</code> cell đầu tiên</td></tr>
784
+ </tbody>
785
+ </table>
786
+
787
+ <h3 id="6-5-tips-passing">6.5. Tips for Passing</h3>
788
+
789
+ <ul>
790
+ <li><strong>Đọc kỹ instructions</strong> — mỗi cell thường có comment chỉ rõ TODO. Đọc kỹ trước khi code.</li>
791
+ <li><strong>Course notebooks là tài liệu</strong> — assessment tasks thường là variations của exercises trong course. Tham khảo notebooks đã làm.</li>
792
+ <li><strong>NVIDIA API patterns</strong> — nhớ cách import và init: <code>ChatNVIDIA(model=...)</code>, <code>NVIDIAEmbeddings(model=...)</code>.</li>
793
+ <li><strong>Test từng cell</strong> — chạy cell ngay sau khi viết, đừng đợi viết xong hết rồi mới run.</li>
794
+ <li><strong>Output format matters</strong> — nếu đề yêu cầu return dict → return dict, không return string.</li>
795
+ </ul>
796
+
797
+ <blockquote><p><strong>Exam tip:</strong> Assessment S-FX-15 nặng về <strong>hands-on coding</strong>, không phải multiple choice. Ưu tiên ôn lại: <strong>RAG pipeline setup</strong> (đề thường có), <strong>PEFT/LoRA configuration</strong>, <strong>diffusion pipeline</strong>. Tham khảo course notebooks — assessment thường yêu cầu tương tự nhưng với data/model khác.</p></blockquote>
798
+
799
+ <h2 id="7-cheat-sheet">7. Cheat Sheet</h2>
800
+
801
+ <table>
802
+ <thead>
803
+ <tr><th>Concept</th><th>Key Point</th></tr>
804
+ </thead>
805
+ <tbody>
806
+ <tr><td>Static RAG vs Agent</td><td>Chain = fixed flow; Agent = dynamic, LLM decides</td></tr>
807
+ <tr><td>ReAct pattern</td><td>Thought → Action → Observation loop</td></tr>
808
+ <tr><td>Tool description</td><td>LLM chọn tool dựa trên description — phải rõ ràng!</td></tr>
809
+ <tr><td>create_tool_calling_agent</td><td>Dùng native tool calling API (ưu tiên cho NVIDIA NIM)</td></tr>
810
+ <tr><td>AgentExecutor max_iterations</td><td>Default 15, nên set 5-10 để tránh infinite loop</td></tr>
811
+ <tr><td>handle_parsing_errors</td><td>Luôn True — tránh crash khi LLM trả format sai</td></tr>
812
+ <tr><td>History-aware retriever</td><td>Rewrite follow-up query thành standalone trước khi retrieve</td></tr>
813
+ <tr><td>RunnableWithMessageHistory</td><td>Auto-manage chat history theo session_id</td></tr>
814
+ <tr><td>Faithfulness</td><td>Answer grounded in context? (≥ 0.85)</td></tr>
815
+ <tr><td>Answer Relevance</td><td>Answer trả lời đúng câu hỏi? (≥ 0.80)</td></tr>
816
+ <tr><td>Context Precision</td><td>Retrieved docs có relevant? (≥ 0.75)</td></tr>
817
+ <tr><td>Context Recall</td><td>Đã retrieve đủ docs? (≥ 0.80)</td></tr>
818
+ <tr><td>RAGAS</td><td>Framework đo 4 metrics trên, dùng LLM evaluate</td></tr>
819
+ <tr><td>LLM-as-Judge</td><td>Dùng LLM mạnh đánh giá output LLM khác — scalable</td></tr>
820
+ <tr><td>Verbosity bias</td><td>Judge prefer longer answers</td></tr>
821
+ <tr><td>Positional bias</td><td>Judge prefer first option → swap A↔B rồi average</td></tr>
822
+ <tr><td>S-FX-15 format</td><td>Hands-on Jupyter notebook, ~2h, coding-based</td></tr>
823
+ <tr><td>S-FX-15 strategy</td><td>Đọc hết → làm dễ trước → khó sau → review</td></tr>
824
+ </tbody>
825
+ </table>
826
+
827
+ <h2 id="8-practice-questions">8. Practice Questions — Coding</h2>
828
+
829
+ <p><strong>Q1: Build RAG Agent với Retriever + Web Search</strong></p>
830
+ <p>Xây dựng một RAG Agent có 2 tools: <code>retriever_tool</code> (tìm trong tài liệu nội bộ) và <code>web_search_tool</code> (tìm trên internet). Agent phải tự quyết định khi nào dùng tool nào. In ra intermediate steps để thấy tool selection logic.</p>
831
+
832
+ <details>
833
+ <summary>Xem đáp án Q1</summary>
834
+
835
+ <pre><code class="language-python">
836
+ from langchain_nvidia_ai_endpoints import ChatNVIDIA, NVIDIAEmbeddings
837
+ from langchain_community.vectorstores import FAISS
838
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
839
+ from langchain.tools.retriever import create_retriever_tool
840
+ from langchain_community.tools.tavily_search import TavilySearchResults
841
+ from langchain.agents import create_tool_calling_agent, AgentExecutor
842
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
843
+
844
+ # === Setup retriever ===
845
+ from langchain_core.documents import Document
846
+ docs = [
847
+ Document(page_content="Nhân viên được 12 ngày phép/năm. Thử việc: 1 ngày/tháng.",
848
+ metadata={"source": "hr_policy.pdf"}),
849
+ Document(page_content="Hoàn tiền trong 30 ngày với hóa đơn gốc. Sản phẩm nguyên seal.",
850
+ metadata={"source": "refund_policy.pdf"}),
851
+ ]
852
+
853
+ embeddings = NVIDIAEmbeddings(model="NV-Embed-QA")
854
+ vectorstore = FAISS.from_documents(docs, embeddings)
855
+ retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
856
+
857
+ # === Define tools ===
858
+ retriever_tool = create_retriever_tool(
859
+ retriever,
860
+ name="internal_docs_search",
861
+ description="Tìm thông tin trong tài liệu nội bộ công ty: chính sách nhân sự, "
862
+ "quy trình, hoàn tiền. Dùng cho câu hỏi về nội bộ công ty."
863
+ )
864
+
865
+ web_search_tool = TavilySearchResults(
866
+ max_results=3,
867
+ description="Tìm kiếm trên internet. Dùng khi cần thông tin bên ngoài: "
868
+ "tin tức, giá cổ phiếu, thông tin thị trường, dữ liệu public."
869
+ )
870
+
871
+ tools = [retriever_tool, web_search_tool]
872
+
873
+ # === Create agent ===
874
+ llm = ChatNVIDIA(model="meta/llama-3.1-70b-instruct", temperature=0.1)
875
+
876
+ prompt = ChatPromptTemplate.from_messages([
877
+ ("system", "Bạn là trợ lý AI. Dùng tools để tìm thông tin chính xác. "
878
+ "Trích dẫn nguồn khi trả lời."),
879
+ ("human", "{input}"),
880
+ MessagesPlaceholder(variable_name="agent_scratchpad"),
881
+ ])
882
+
883
+ agent = create_tool_calling_agent(llm, tools, prompt)
884
+ agent_executor = AgentExecutor(
885
+ agent=agent,
886
+ tools=tools,
887
+ verbose=True,
888
+ max_iterations=5,
889
+ handle_parsing_errors=True,
890
+ return_intermediate_steps=True,
891
+ )
892
+
893
+ # === Test: internal question → should use retriever ===
894
+ result1 = agent_executor.invoke({"input": "Chính sách nghỉ phép là gì?"})
895
+ print("Answer:", result1["output"])
896
+ print("\nTools used:")
897
+ for step in result1["intermediate_steps"]:
898
+ print(f" → {step[0].tool}: {step[0].tool_input}")
899
+
900
+ # === Test: external question → should use web search ===
901
+ result2 = agent_executor.invoke({"input": "NVIDIA stock price today?"})
902
+ print("Answer:", result2["output"])
903
+ print("\nTools used:")
904
+ for step in result2["intermediate_steps"]:
905
+ print(f" → {step[0].tool}: {step[0].tool_input}")
906
+ </code></pre>
907
+
908
+ <p><strong>Giải thích tool selection:</strong> Agent đọc tool descriptions. "Chính sách nghỉ phép" matches "tài liệu nội bộ công ty, chính sách nhân sự" → chọn <code>internal_docs_search</code>. "NVIDIA stock price" matches "tin tức, giá cổ phiếu, thông tin thị trường" → chọn <code>web_search</code>.</p>
909
+ </details>
910
+
911
+ <p><strong>Q2: Implement History-aware Retriever cho Multi-turn RAG</strong></p>
912
+ <p>Xây dựng conversational RAG pipeline có khả năng hiểu follow-up questions. Test với 3-turn conversation: câu hỏi gốc → follow-up → follow-up nữa.</p>
913
+
914
+ <details>
915
+ <summary>Xem đáp án Q2</summary>
916
+
917
+ <pre><code class="language-python">
918
+ from langchain_nvidia_ai_endpoints import ChatNVIDIA, NVIDIAEmbeddings
919
+ from langchain_community.vectorstores import FAISS
920
+ from langchain_core.documents import Document
921
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
922
+ from langchain_core.messages import HumanMessage, AIMessage
923
+ from langchain.chains import create_history_aware_retriever, create_retrieval_chain
924
+ from langchain.chains.combine_documents import create_stuff_documents_chain
925
+
926
+ # === Setup ===
927
+ docs = [
928
+ Document(page_content="Nhân viên chính thức: 12 ngày phép/năm. Tích lũy tối đa 5 ngày sang năm sau."),
929
+ Document(page_content="Nghỉ bệnh: tối đa 30 ngày/năm có lương. Cần giấy bác sĩ từ ngày thứ 3."),
930
+ Document(page_content="Nghỉ thai sản: 6 tháng cho nữ, 5 ngày cho nam. Theo luật lao động VN."),
931
+ ]
932
+
933
+ embeddings = NVIDIAEmbeddings(model="NV-Embed-QA")
934
+ vectorstore = FAISS.from_documents(docs, embeddings)
935
+ retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
936
+ llm = ChatNVIDIA(model="meta/llama-3.1-70b-instruct", temperature=0.1)
937
+
938
+ # === History-aware retriever ===
939
+ contextualize_prompt = ChatPromptTemplate.from_messages([
940
+ ("system", "Dựa vào lịch sử hội thoại, viết lại câu hỏi thành standalone question. "
941
+ "KHÔNG trả lời, chỉ rewrite."),
942
+ MessagesPlaceholder("chat_history"),
943
+ ("human", "{input}"),
944
+ ])
945
+
946
+ history_aware_retriever = create_history_aware_retriever(
947
+ llm, retriever, contextualize_prompt
948
+ )
949
+
950
+ # === QA chain ===
951
+ qa_prompt = ChatPromptTemplate.from_messages([
952
+ ("system", "Trả lời dựa trên context. Nếu không có → nói không tìm thấy.\n\n"
953
+ "Context:\n{context}"),
954
+ MessagesPlaceholder("chat_history"),
955
+ ("human", "{input}"),
956
+ ])
957
+
958
+ qa_chain = create_stuff_documents_chain(llm, qa_prompt)
959
+ rag_chain = create_retrieval_chain(history_aware_retriever, qa_chain)
960
+
961
+ # === 3-turn conversation ===
962
+ chat_history = []
963
+
964
+ # Turn 1
965
+ r1 = rag_chain.invoke({"input": "Chính sách nghỉ phép?", "chat_history": chat_history})
966
+ print(f"Turn 1: {r1['answer']}")
967
+ chat_history.extend([
968
+ HumanMessage(content="Chính sách nghỉ phép?"),
969
+ AIMessage(content=r1["answer"])
970
+ ])
971
+
972
+ # Turn 2 — follow-up
973
+ r2 = rag_chain.invoke({"input": "Còn nghỉ bệnh thì sao?", "chat_history": chat_history})
974
+ print(f"Turn 2: {r2['answer']}")
975
+ # "Còn nghỉ bệnh thì sao?" → rewrite: "Chính sách nghỉ bệnh của công ty?"
976
+ chat_history.extend([
977
+ HumanMessage(content="Còn nghỉ bệnh thì sao?"),
978
+ AIMessage(content=r2["answer"])
979
+ ])
980
+
981
+ # Turn 3 — another follow-up
982
+ r3 = rag_chain.invoke({"input": "Cần giấy tờ gì không?", "chat_history": chat_history})
983
+ print(f"Turn 3: {r3['answer']}")
984
+ # "Cần giấy tờ gì không?" → rewrite: "Nghỉ bệnh cần giấy tờ gì?"
985
+ </code></pre>
986
+ </details>
987
+
988
+ <p><strong>Q3: Calculate Faithfulness Score</strong></p>
989
+ <p>Implement hàm <code>calculate_faithfulness()</code> nhận vào <code>context</code> và <code>answer</code>, dùng LLM để chia answer thành claims, check từng claim against context, trả về faithfulness score [0.0 - 1.0].</p>
990
+
991
+ <details>
992
+ <summary>Xem đáp án Q3</summary>
993
+
994
+ <pre><code class="language-python">
995
+ from langchain_nvidia_ai_endpoints import ChatNVIDIA
996
+ from langchain_core.prompts import ChatPromptTemplate
997
+ from langchain_core.output_parsers import JsonOutputParser
998
+
999
+ def calculate_faithfulness(context: str, answer: str) -> dict:
1000
+ """
1001
+ Calculate faithfulness score: fraction of claims in answer
1002
+ that are supported by context.
1003
+ Returns: {"score": float, "claims": list, "reasoning": str}
1004
+ """
1005
+ llm = ChatNVIDIA(model="meta/llama-3.1-70b-instruct", temperature=0.0)
1006
+
1007
+ prompt = ChatPromptTemplate.from_template("""
1008
+ Analyze the faithfulness of the answer given the context.
1009
+
1010
+ Context:
1011
+ {context}
1012
+
1013
+ Answer:
1014
+ {answer}
1015
+
1016
+ Steps:
1017
+ 1. Break the answer into individual factual claims.
1018
+ 2. For each claim, determine if it is supported by the context.
1019
+ 3. Calculate: faithfulness_score = supported_claims / total_claims
1020
+
1021
+ Return JSON:
1022
+ {{
1023
+ "claims": [
1024
+ {{"text": "claim text", "supported": true, "evidence": "quote from context"}},
1025
+ {{"text": "claim text", "supported": false, "evidence": "not found"}}
1026
+ ],
1027
+ "supported_count": <int>,
1028
+ "total_count": <int>,
1029
+ "score": <float 0.0-1.0>,
1030
+ "reasoning": "summary"
1031
+ }}
1032
+ """)
1033
+
1034
+ chain = prompt | llm | JsonOutputParser()
1035
+ result = chain.invoke({"context": context, "answer": answer})
1036
+ return result
1037
+
1038
+
1039
+ # === Test ===
1040
+ context = (
1041
+ "Công ty ABC hoàn tiền trong 30 ngày kể từ ngày mua. "
1042
+ "Khách hàng cần xuất trình hóa đơn gốc. "
1043
+ "Sản phẩm phải còn nguyên seal và chưa sử dụng."
1044
+ )
1045
+ answer = (
1046
+ "Công ty ABC hoàn tiền trong 30 ngày nếu có hóa đơn. "
1047
+ "Sản phẩm phải nguyên seal. "
1048
+ "Liên hệ hotline 1900-xxxx để được hỗ trợ." # ← NOT in context!
1049
+ )
1050
+
1051
+ result = calculate_faithfulness(context, answer)
1052
+ print(f"Faithfulness Score: {result['score']}")
1053
+ # Expected: ~0.67 (2/3 claims supported)
1054
+ for claim in result["claims"]:
1055
+ status = "✅" if claim["supported"] else "❌"
1056
+ print(f" {status} {claim['text']}")
1057
+ </code></pre>
1058
+ </details>
1059
+
1060
+ <p><strong>Q4: Implement LLM-as-Judge Evaluator với Structured Rubric</strong></p>
1061
+ <p>Build một evaluator dùng LLM-as-Judge pattern với rubric có 3 criteria: <strong>Faithfulness</strong> (1-5), <strong>Completeness</strong> (1-5), <strong>Clarity</strong> (1-5). Evaluator nhận question, context, answer và trả về scores + reasoning. Implement cả <strong>pairwise comparison</strong> với swap positions để giảm positional bias.</p>
1062
+
1063
+ <details>
1064
+ <summary>Xem đáp án Q4</summary>
1065
+
1066
+ <pre><code class="language-python">
1067
+ from langchain_nvidia_ai_endpoints import ChatNVIDIA
1068
+ from langchain_core.prompts import ChatPromptTemplate
1069
+ from langchain_core.output_parsers import JsonOutputParser
1070
+
1071
+ judge_llm = ChatNVIDIA(model="meta/llama-3.1-70b-instruct", temperature=0.0)
1072
+
1073
+ # === Part A: Single response evaluation ===
1074
+ single_eval_prompt = ChatPromptTemplate.from_template("""
1075
+ You are an expert evaluator. Score the AI response on a 1-5 scale.
1076
+
1077
+ **Question:** {question}
1078
+ **Context:** {context}
1079
+ **Response:** {response}
1080
+
1081
+ **Rubric:**
1082
+ - Faithfulness (1-5): Is every claim supported by context? 5 = all claims grounded.
1083
+ - Completeness (1-5): Does it cover all relevant info from context? 5 = comprehensive.
1084
+ - Clarity (1-5): Well-structured and easy to understand? 5 = excellent.
1085
+
1086
+ Return JSON:
1087
+ {{
1088
+ "scores": {{
1089
+ "faithfulness": <1-5>,
1090
+ "completeness": <1-5>,
1091
+ "clarity": <1-5>
1092
+ }},
1093
+ "overall": <float, average of 3 scores>,
1094
+ "reasoning": "..."
1095
+ }}
1096
+ """)
1097
+
1098
+ single_eval_chain = single_eval_prompt | judge_llm | JsonOutputParser()
1099
+
1100
+ # === Part B: Pairwise with positional bias mitigation ===
1101
+ pairwise_prompt = ChatPromptTemplate.from_template("""
1102
+ Compare two responses. Which is better overall?
1103
+
1104
+ **Question:** {question}
1105
+ **Context:** {context}
1106
+
1107
+ **Response 1:**
1108
+ {response_1}
1109
+
1110
+ **Response 2:**
1111
+ {response_2}
1112
+
1113
+ Return JSON:
1114
+ {{
1115
+ "winner": "1" or "2" or "TIE",
1116
+ "scores_1": {{"faithfulness": <1-5>, "completeness": <1-5>, "clarity": <1-5>}},
1117
+ "scores_2": {{"faithfulness": <1-5>, "completeness": <1-5>, "clarity": <1-5>}},
1118
+ "reasoning": "..."
1119
+ }}
1120
+ """)
1121
+
1122
+ pairwise_chain = pairwise_prompt | judge_llm | JsonOutputParser()
1123
+
1124
+
1125
+ def pairwise_eval_debiased(question, context, resp_a, resp_b):
1126
+ """Pairwise eval with positional bias mitigation: evaluate twice, swap order."""
1127
+
1128
+ # Round 1: A first
1129
+ r1 = pairwise_chain.invoke({
1130
+ "question": question, "context": context,
1131
+ "response_1": resp_a, "response_2": resp_b
1132
+ })
1133
+
1134
+ # Round 2: B first (swapped)
1135
+ r2 = pairwise_chain.invoke({
1136
+ "question": question, "context": context,
1137
+ "response_1": resp_b, "response_2": resp_a
1138
+ })
1139
+
1140
+ # Normalize: map r2 winner back
1141
+ r2_winner_mapped = {"1": "2", "2": "1", "TIE": "TIE"}[r2["winner"]]
1142
+
1143
+ # Determine final winner
1144
+ if r1["winner"] == r2_winner_mapped:
1145
+ final_winner = r1["winner"] # Consistent → confident
1146
+ confidence = "HIGH"
1147
+ else:
1148
+ final_winner = "TIE" # Inconsistent → likely positional bias
1149
+ confidence = "LOW (positional bias detected)"
1150
+
1151
+ return {
1152
+ "final_winner": f"Response {'A' if final_winner == '1' else 'B' if final_winner == '2' else 'TIE'}",
1153
+ "confidence": confidence,
1154
+ "round1": r1,
1155
+ "round2_swapped": r2,
1156
+ }
1157
+
1158
+
1159
+ # === Test ===
1160
+ question = "Chính sách hoàn tiền?"
1161
+ context = "Hoàn tiền trong 30 ngày với hóa đơn. Sản phẩm nguyên seal."
1162
+ resp_a = "Hoàn tiền 30 ngày nếu có hóa đơn và sản phẩm còn seal."
1163
+ resp_b = "Công ty hỗ trợ hoàn tiền. Chi tiết liên hệ tổng đài."
1164
+
1165
+ # Single eval
1166
+ score_a = single_eval_chain.invoke({
1167
+ "question": question, "context": context, "response": resp_a
1168
+ })
1169
+ print(f"Response A overall: {score_a['overall']}")
1170
+
1171
+ # Pairwise (debiased)
1172
+ comparison = pairwise_eval_debiased(question, context, resp_a, resp_b)
1173
+ print(f"Winner: {comparison['final_winner']} ({comparison['confidence']})")
1174
+ </code></pre>
1175
+ </details>
1176
+
1177
+ <p><strong>Q5: Debug — Agent Infinite Loop</strong></p>
1178
+ <p>Code dưới đây có bug: agent liên tục gọi tool và không bao giờ dừng (infinite loop). Tìm nguyên nhân và sửa. Hint: kiểm tra <code>max_iterations</code> và <code>handle_parsing_errors</code>.</p>
1179
+
1180
+ <pre><code class="language-python">
1181
+ # BUG CODE — tìm và sửa lỗi
1182
+ from langchain.agents import create_tool_calling_agent, AgentExecutor
1183
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
1184
+
1185
+ llm = ChatNVIDIA(model="meta/llama-3.1-70b-instruct", temperature=0.9)
1186
+
1187
+ prompt = ChatPromptTemplate.from_messages([
1188
+ ("system", "You are a helpful assistant."),
1189
+ ("human", "{input}"),
1190
+ # BUG: thiếu agent_scratchpad!
1191
+ ])
1192
+
1193
+ agent = create_tool_calling_agent(llm, tools, prompt)
1194
+
1195
+ agent_executor = AgentExecutor(
1196
+ agent=agent,
1197
+ tools=tools,
1198
+ verbose=True,
1199
+ # BUG: không set max_iterations → default 15, quá cao
1200
+ # BUG: không set handle_parsing_errors → crash nếu parse fails
1201
+ )
1202
+
1203
+ result = agent_executor.invoke({"input": "So sánh doanh thu Q3 với ngành"})
1204
+ </code></pre>
1205
+
1206
+ <details>
1207
+ <summary>Xem đáp án Q5</summary>
1208
+
1209
+ <pre><code class="language-python">
1210
+ # FIXED CODE — 4 bugs đã sửa
1211
+
1212
+ llm = ChatNVIDIA(
1213
+ model="meta/llama-3.1-70b-instruct",
1214
+ temperature=0.1 # FIX 1: temperature thấp → output ổn định hơn
1215
+ # temperature=0.9 → agent "sáng tạo" quá → chọn tool lung tung
1216
+ )
1217
+
1218
+ prompt = ChatPromptTemplate.from_messages([
1219
+ ("system", "You are a helpful assistant. Use tools to find accurate information."),
1220
+ ("human", "{input}"),
1221
+ MessagesPlaceholder(variable_name="agent_scratchpad"),
1222
+ # FIX 2: PHẢI có agent_scratchpad!
1223
+ # Đây là nơi LangChain inject Thought/Action/Observation history
1224
+ # Thiếu → agent không thấy kết quả tool → gọi tool lại liên tục
1225
+ ])
1226
+
1227
+ agent = create_tool_calling_agent(llm, tools, prompt)
1228
+
1229
+ agent_executor = AgentExecutor(
1230
+ agent=agent,
1231
+ tools=tools,
1232
+ verbose=True,
1233
+ max_iterations=5, # FIX 3: giới hạn iterations
1234
+ handle_parsing_errors=True, # FIX 4: handle parse errors gracefully
1235
+ return_intermediate_steps=True,
1236
+ )
1237
+
1238
+ result = agent_executor.invoke({"input": "So sánh doanh thu Q3 với ngành"})
1239
+
1240
+ # === Tổng kết 4 bugs ===
1241
+ # 1. temperature=0.9 quá cao → agent không stable trong tool selection
1242
+ # 2. Thiếu MessagesPlaceholder("agent_scratchpad") → agent không thấy
1243
+ # observation từ tools → gọi tool lại vô hạn (root cause infinite loop!)
1244
+ # 3. Không set max_iterations → chạy mãi nếu agent không converge
1245
+ # 4. Không handle_parsing_errors → crash thay vì retry khi parse fails
1246
+ </code></pre>
1247
+
1248
+ <p><strong>Root cause:</strong> Thiếu <code>agent_scratchpad</code> là nguyên nhân chính. Đây là placeholder nơi LangChain inject lịch sử Thought/Action/Observation. Thiếu nó → agent không biết mình đã gọi tool → gọi lại liên tục. <code>max_iterations</code> là safety net, <code>temperature</code> thấp giúp agent ra quyết định ổn định hơn.</p>
1249
+ </details>