# Thực hành: bắt mô hình trích nguồn cho từng ý và dùng code chặn câu trả lời bịa

> Bản demo RAG nào cũng chạy ổn, nhưng hệ thống chỉ đáng tin khi có một lớp kiểm tra chặn được câu bịa trước khi nó tới tay người dùng của khách.

Bản gốc: https://fdetimes.net/vi/bach-khoa/thuc-hanh-phat-hien-va-giam-hallucination/

Một câu trả lời sai mà nghe rất hợp lý gây hại nhiều hơn một câu "không biết". IBM định nghĩa AI hallucination đúng như thế: những đầu ra nghe hợp lý nhưng sai sự thật.

Anthropic cũng thừa nhận trong tài liệu chính thức rằng ngay cả mô hình tiên tiến như Claude đôi khi vẫn sinh ra nội dung sai hoặc không khớp với ngữ cảnh đã cung cấp.

Với một FDE, chuyện này gắn trực tiếp với độ tin cậy của sản phẩm. Chỉ cần nhân viên của khách bắt được một câu bịa về điều khoản hợp đồng là họ ngừng tin cả hệ thống. Bài này hướng dẫn dựng một lớp chặn: bắt mô hình trích nguồn cho từng ý, rồi dùng code để kiểm tra trước khi câu trả lời được gửi đi.

## Sẽ dựng gì, cần gì?

Bạn sẽ dựng một pipeline năm bước. Mô hình được phép nói không biết, phải trích nguyên văn trước khi trả lời, phải gắn trích dẫn cho từng ý, sau đó một verifier bằng code đối chiếu trích dẫn với tài liệu gốc. Ý nào không qua được kiểm tra thì bị rút lại hoặc chuyển cho người duyệt.

Cần chuẩn bị Python 3, một tài liệu văn bản thuần và quyền gọi một LLM bất kỳ. Ví dụ xuyên suốt là giả định: một công ty bảo hiểm muốn có trợ lý trả lời nhân viên dựa trên quy tắc sản phẩm dài vài chục trang.

Lớp kiểm tra này cần thiết vì, như IBM giải thích, generative AI không "biết" điều gì đúng hay sai, nên đưa đúng tài liệu vào vẫn chưa bảo đảm mô hình bám vào tài liệu đó.

## Bước 1: cho phép nói không biết, bắt trích dẫn trước

Hướng dẫn giảm hallucination của Anthropic nêu một kỹ thuật cơ bản: cho mô hình được phép thừa nhận mình không chắc. Tài liệu gọi đây là cách đơn giản nhưng có thể giảm mạnh thông tin sai. Với tài liệu dài hơn 20k token, Anthropic khuyên yêu cầu mô hình trích nguyên văn các đoạn liên quan trước, rồi mới làm nhiệm vụ chính.

```text
Chỉ trả lời dựa trên tài liệu trong thẻ .
Nếu tài liệu không đủ để trả lời, ghi "Không tìm thấy trong tài liệu".
Bước 1: trích NGUYÊN VĂN các đoạn liên quan vào thẻ .
Bước 2: trả lời. Mỗi ý phải kèm một trích dẫn nguyên văn từ bước 1.
Nếu không tìm được trích dẫn cho một ý, hãy rút lại ý đó.
Trả về JSON: {"claims": [{"text": "...", "quote": "..."}], "final": "..."}
```

Câu cuối của prompt dựa trên mẫu kiểm tra mà Anthropic mô tả: mỗi ý phải tìm được trích dẫn hỗ trợ, nếu không tìm được thì phải rút lại.

**Kiểm tra:** hỏi một câu mà tài liệu chắc chắn không có câu trả lời. Nếu mô hình vẫn đưa ra con số, prompt chưa đủ chặt hoặc tài liệu đưa vào bị lẫn nội dung khác.

## Bước 2: viết verifier đối chiếu trích dẫn với tài liệu gốc

Trong prompt ở Bước 1, chính mô hình vừa viết câu trả lời vừa tự chép trích dẫn. Nó hoàn toàn có thể "trích" một câu không hề có trong tài liệu, hoặc trả về một chuỗi JSON hỏng. Vì thế việc kiểm tra phải do code làm.

```python
import json, re

def norm(s):
return re.sub(r"\s+", " ", s).strip().lower()

def numbers(s):
return set(re.findall(r"\d+(?:[.,/]\d+)*", s))

def verify(answer_json, document):
try:
claims = json.loads(answer_json)["claims"]
except (json.JSONDecodeError, KeyError, TypeError):
return [], [{"error": "invalid_json", "raw": answer_json}]
doc = norm(document)
kept, retracted = [], []
for c in claims:
q = norm(c.get("quote", ""))
text = c.get("text", "")
if q and q in doc and numbers(text) <= numbers(q):
kept.append(c)
else:
retracted.append(c)
return kept, retracted
```

Đây là phiên bản đơn giản hoá. Hàm chỉ so khớp chuỗi sau khi chuẩn hoá khoảng trắng và chữ hoa, chưa xử lý trường hợp mô hình đổi dấu câu hay ký tự Unicode. Khi JSON hỏng, hàm không cố đoán mà trả về một mục lỗi để Bước 3 xử lý.

Cần tỉnh táo với chính verifier này. Trích dẫn có thật trong tài liệu chưa chắc đã ủng hộ ý đi kèm: mô hình có thể chép đúng một câu về thời gian chờ rồi gắn nó cho một ý chứa con số khác. Vì thế hàm có thêm một kiểm tra rẻ: mọi con số và ngày tháng trong ý phải xuất hiện trong trích dẫn.

Kiểm tra số vẫn không hiểu nghĩa câu, nó chỉ bắt được loại lỗi lệch số. Ý không chứa con số nào sẽ lọt qua bước này, nên với nhóm câu hỏi quan trọng bạn vẫn cần thêm cách kiểm tra khác hoặc người duyệt.

Áp vào ví dụ bảo hiểm (giả định): nhân viên hỏi thời gian chờ cho bệnh có sẵn và mô hình trả về ba ý. Hai ý có trích dẫn khớp nguyên văn với quy tắc. Ý thứ ba nói thêm một con số mà trích dẫn đi kèm không có trong tài liệu, nên verifier rút lại ý này và người dùng chỉ nhận hai ý có nguồn.

Giả sử thêm một biến thể: trích dẫn của ý thứ ba có thật trong quy tắc, nhưng con số trong ý không nằm trong đoạn trích. Phép so chuỗi đơn thuần sẽ cho qua, còn kiểm tra số sẽ rút ý đó lại.

**Kiểm tra:** cố tình sửa một trích dẫn trong JSON mẫu, rồi đổi một con số trong phần `text` của một ý khác, và chạy lại. Cả hai ý phải nằm trong `retracted`. Thử thêm một chuỗi JSON bị cắt cụt: hàm phải trả về mục lỗi chứ không văng exception.

## Bước 3: quyết định khi có ý bị rút lại

Verifier chỉ cho biết ý nào không có chỗ dựa. Còn xử lý thế nào là quyết định sản phẩm mà bạn cần chốt với khách.

```python
kept, retracted = verify(raw_answer, document)
if not kept:
log_for_review(retracted)
reply = "Không tìm thấy thông tin này trong tài liệu."
elif retracted:
log_for_review(retracted)   # hàm ghi log của bạn
reply = render(kept)        # chỉ hiển thị ý có nguồn
else:
reply = render(kept)
```

Hai hàm `log_for_review` và `render` là hàm của bạn, không thuộc thư viện nào. Khi JSON hỏng, nhánh đầu tiên sẽ chạy; bạn có thể chọn gọi lại mô hình một lần trước khi rơi về câu "không tìm thấy", nhưng đừng bao giờ hiển thị chuỗi thô cho người dùng.

Log các ý bị rút lại đáng được đọc kỹ. Một ý bị rút có thể do retrieval không đưa đúng đoạn tài liệu vào, cũng có thể do mô hình tự bịa dù tài liệu đã đủ; khi xem log, hãy tách hai loại này ra vì cách sửa của chúng khác nhau.

**Điểm mấu chốt:** Câu trả lời nào không chỉ ra được nguồn thì chưa được gửi tới người dùng.

## Bước 4: chạy nhiều lần để bắt chỗ mô hình dao động

Anthropic gợi ý một kỹ thuật nâng cao là best-of-N: chạy cùng một prompt nhiều lần rồi so sánh, vì kết quả không nhất quán giữa các lần chạy có thể là dấu hiệu hallucination.

```python
def consistent(answers):
finals = {norm(a["final"]) for a in answers}
return len(finals) == 1
```

Đây cũng là bản đơn giản hoá: so chuỗi y hệt nên quá khắt khe với câu trả lời dài. Trên thực tế bạn nên so phần dữ kiện then chốt như con số hay ngày tháng, tái dùng hàm `numbers` ở Bước 2. Kỹ thuật này tốn gấp N lần chi phí gọi mô hình, nên chỉ dùng cho nhóm câu hỏi rủi ro cao.

## Bước 5: khi nào nên chuyển sang Citations API?

Tự viết trích dẫn trong prompt có điểm yếu ở Bước 2 đã nói. Citations API của Anthropic xử lý phần này ở phía API: nó trả về đúng đoạn văn bản hỗ trợ từng ý, để bạn kiểm chứng và hiển thị nguồn cho người dùng cuối.

Tài liệu của Anthropic cho biết API tự phân tích và trích `cited_text`, nên trích dẫn được đảm bảo trỏ hợp lệ vào tài liệu đã cung cấp.

Giới hạn ở Bước 2 vẫn còn nguyên. "Trỏ hợp lệ" nghĩa là đoạn trích có thật, chứ chưa chắc đoạn đó thực sự ủng hộ ý mà mô hình viết. Khi dùng Citations API, bạn bỏ được phép so chuỗi, nhưng nên giữ kiểm tra con số và ngày tháng giữa ý và `cited_text`.

## Những lỗi hay gặp

Lỗi đầu tiên là chỉ dặn trong prompt mà không có code kiểm tra. Lỗi thứ hai là chuẩn hoá văn bản quá tay, ví dụ bỏ hết chữ số, khiến trích dẫn sai con số vẫn lọt qua. Lỗi thứ ba là coi pipeline này là lời bảo đảm tuyệt đối.

Anthropic viết rõ các kỹ thuật trên giảm đáng kể hallucination nhưng không xoá hẳn, và thông tin quan trọng vẫn phải được xác thực, nhất là khi quyết định có rủi ro cao.

IBM cũng nhấn mạnh con người phải giám sát ở những nơi hallucination có thể gây thiệt hại lớn. Với khách hàng bảo hiểm trong ví dụ, câu trả lời về mức chi trả nên đi qua người duyệt, dù verifier đã cho qua.

## Ở chỗ khách, log rút lại là thước đo niềm tin

LlamaIndex mô tả bước evaluation trong RAG là cách đo khách quan độ chính xác, độ trung thực và tốc độ của câu trả lời. Ở chỗ khách, log từ Bước 3 chính là bộ đo đó. Thay vì nói "chatbot khá chính xác", bạn có thể báo bao nhiêu phần trăm ý bị rút lại trên mỗi nhóm câu hỏi.

Khi đọc JD vị trí FDE hay AI engineer, hãy để ý các cụm như "evaluation", "guardrails", "grounding", "citations". Nếu chúng xuất hiện, nên chuẩn bị sẵn một ví dụ cụ thể về lớp kiểm tra bạn từng dựng để kể trong buổi phỏng vấn.

Trong CV, một dòng như "dựng verifier trích dẫn, giảm tỉ lệ ý không có nguồn từ X% xuống Y%" với số liệu thật của bạn có sức nặng hơn nhiều so với "xây chatbot RAG".

Lần tới khi khách hỏi chatbot có bịa không, đừng trả lời bằng cảm giác. Hãy mở log các ý bị rút lại và cho họ xem.

**Thử ngay tuần này:**

- Lấy một tài liệu thật (quy chế, hợp đồng mẫu), viết 10 câu hỏi, trong đó 3 câu tài liệu không trả lời được, rồi chạy prompt ở Bước 1 để xem mô hình có chịu nói không biết không
- Thêm hàm verify vào một dự án RAG sẵn có và ghi log tỉ lệ ý bị rút lại trên 50 câu hỏi
- Trong CV, viết một dòng mô tả lớp kiểm tra grounding bạn đã làm kèm con số đo được, thay vì chỉ ghi 'xây chatbot RAG'

## Nguồn

- [Reduce hallucinations](https://platform.claude.com/docs/en/test-and-evaluate/strengthen-guardrails/reduce-hallucinations)

- [Citations](https://platform.claude.com/docs/en/build-with-claude/citations)

- [Citations (Anthropic docs)](https://docs.anthropic.com/en/docs/build-with-claude/citations)

- [What Are AI Hallucinations? | IBM](https://www.ibm.com/think/topics/ai-hallucinations)

- [Introduction to RAG](https://developers.llamaindex.ai/python/framework/understanding/rag/)
