# Bốn cách bắt model tự phản biện trước quyết định lớn của khách

> Lấy nhiều mẫu, dùng nhiều prompt, lùi lại tìm nguyên tắc rồi tìm kiếm theo cây: bốn lớp này giúp bạn biết lúc nào nên tin model và lúc nào cần chuyển cho người duyệt.

Bản gốc: https://fdetimes.net/vi/bach-khoa/thuc-hanh-self-consistency-ensembling-tree-of-thoughts/

Trên bộ đề toán GSM8K, chỉ cần hỏi model nhiều lần rồi lấy đáp án được chọn nhiều nhất, độ chính xác đã tăng 17,9% so với chain-of-thought chạy greedy decoding. Trên trò Game of 24, GPT-4 dùng chain-of-thought chỉ giải được 4% số bài, trong khi tree of thoughts đạt 74%.

Hai con số đó đến từ benchmark chứ không đến từ hệ thống của khách hàng. Dù vậy, chúng cho thấy một điều mà FDE nào cũng sẽ gặp: với cùng một model, cách bạn tổ chức các lần gọi có thể quan trọng ngang với bản thân prompt.

Ở site khách, nhiều quyết định không được phép sai: duyệt hay từ chối một hồ sơ, chọn nhà cung cấp nào, có cần escalate một sự cố hay không. Bài này hướng dẫn bạn dựng một bộ khung nhỏ gồm bốn lớp, đồng thời chỉ ra lớp nào đáng tiền và lớp nào chỉ làm tăng hóa đơn API.

## Bạn sẽ dựng gì, và cần chuẩn bị gì?

Thử hình dung một tình huống giả định: khách hàng là công ty bảo hiểm, cần model đọc hồ sơ bồi thường và điều khoản hợp đồng rồi kết luận CHI_TRẢ hoặc TỪ_CHỐI. Một lần gọi model cho bạn một câu trả lời nhưng không cho biết model chắc chắn đến đâu. Mục tiêu là đổi một lần đoán thành một cuộc bỏ phiếu có số liệu đi kèm.

Bạn cần Python 3 và SDK của nhà cung cấp model mà khách đang dùng. Code bên dưới đã được **đơn giản hóa**: hàm `call_llm` chỉ là chỗ để bạn cắm SDK thật vào, còn tên tham số và cách xử lý lỗi sẽ khác tùy nhà cung cấp.

```python
from collections import Counter

def call_llm(prompt: str, temperature: float = 0.7) -> str:
# Giả định: thay bằng lời gọi SDK thật của bạn
raise NotImplementedError

def extract_answer(text: str) -> str:
# Quy ước: model kết thúc bằng dòng "KẾT LUẬN: "
for line in reversed(text.splitlines()):
if line.startswith("KẾT LUẬN:"):
return line.split(":", 1)[1].strip().upper()
return "KHÔNG_RÕ"
```

Đừng xem nhẹ dòng KẾT LUẬN. Nếu không ép được định dạng đầu ra, bạn không thể đếm phiếu, và mọi bước phía sau đều sụp theo.

## Bước 1: hỏi một câu năm lần, đếm phiếu

Thay vì để chain-of-thought chạy với greedy decoding, self-consistency lấy mẫu nhiều đường lập luận khác nhau, rồi chọn đáp án nhất quán nhất giữa các đường đó. Khi làm thực tế, bạn chỉ cần gửi cùng một prompt nhiều lần với temperature lớn hơn 0 và lấy đáp án chiếm đa số.

```python
def self_consistency(prompt: str, n: int = 5):
answers = [extract_answer(call_llm(prompt, temperature=0.7))
for _ in range(n)]
votes = Counter(answers)
label, count = votes.most_common(1)[0]
return label, count / n, votes
```

**Kiểm tra:** chạy thử một hồ sơ. Nếu kết quả là 3 phiếu TỪ_CHỐI và 2 phiếu CHI_TRẢ, nhãn cuối cùng là TỪ_CHỐI với tỷ lệ đồng thuận 0,6.

Một hồ sơ đạt 5/5 phiếu hoàn toàn có thể là câu trả lời chắc chắn thật, nhất là với những ca dễ. Chỉ khi mọi hồ sơ trong tập thử đều 5/5 thì mới nên xem lại temperature, vì có thể bạn chưa thật sự lấy mẫu.

Ở site khách, hãy trả về cả tỷ lệ đồng thuận chứ đừng chỉ trả về nhãn. Một hồ sơ được 5/5 phiếu và một hồ sơ chỉ được 3/5 phiếu không nên đi chung một luồng xử lý.

## Bước 2: đổi góc nhìn bằng prompt ensembling

Self-consistency lấy mẫu từ một prompt duy nhất, nên nếu prompt đó lệch thì cả năm phiếu có thể lệch theo cùng một hướng. Prompt ensembling khắc phục điểm này: dùng nhiều prompt cho cùng một bài toán rồi gộp các câu trả lời thành kết quả cuối.

```python
PROMPTS = [
"Bạn là chuyên viên bồi thường. Hồ sơ: {case}\n"
"Kết thúc bằng 'KẾT LUẬN: CHI_TRẢ' hoặc 'KẾT LUẬN: TỪ_CHỐI'.",
"Liệt kê mọi điều khoản loại trừ liên quan tới hồ sơ: {case}\n"
"Sau đó kết thúc bằng dòng KẾT LUẬN như trên.",
"Đóng vai người phản biện, tìm lý do hồ sơ này bị xử lý sai: {case}\n"
"Sau đó kết thúc bằng dòng KẾT LUẬN như trên.",
]

def ensemble(case: str, n_each: int = 3):
all_votes = Counter()
for tpl in PROMPTS:
_, _, votes = self_consistency(tpl.format(case=case), n=n_each)
all_votes.update(votes)
label, count = all_votes.most_common(1)[0]
return label, count / sum(all_votes.values()), all_votes
```

Ba prompt nhân ba mẫu cho 9 lần gọi. Giả sử kết quả là 6 phiếu TỪ_CHỐI và 3 phiếu CHI_TRẢ, tỷ lệ đồng thuận là khoảng 0,67. Nên đặt một ngưỡng, chẳng hạn 0,7, và chuyển mọi hồ sơ dưới ngưỡng cho người duyệt. Ngưỡng cụ thể phải hiệu chỉnh trên dữ liệu của khách chứ không nên chọn theo cảm tính.

**Lỗi hay gặp:** viết ba prompt chỉ khác nhau vài chữ. Lúc đó bạn chỉ có self-consistency tốn gấp ba. Các prompt cần khác nhau về góc nhìn, ví dụ góc người xử lý, góc điều khoản và góc phản biện.

## Bước 3: lùi một bước trước khi phán

Step-back prompting gồm hai bước. Đầu tiên model được yêu cầu tập trung vào một khái niệm hoặc nguyên tắc tổng quát hơn liên quan đến câu hỏi, sau đó mới dùng nguyên tắc ấy để lập luận.

Bài báo gốc ghi nhận kỹ thuật này giúp PaLM-2L tăng 7% ở phần Vật lý và 11% ở phần Hóa học của MMLU. Learn Prompting tổng hợp rằng mức vượt chain-of-thought dao động từ 7% đến 27% tùy tác vụ.

```python
def step_back(question: str, case: str) -> str:
principle = call_llm(
"Chưa trả lời vội. Nguyên tắc chung nào quyết định "
f"loại câu hỏi này?\nCâu hỏi: {question}", temperature=0)
return call_llm(
f"Nguyên tắc: {principle}\nHồ sơ: {case}\n"
"Áp dụng nguyên tắc vào hồ sơ. Kết thúc bằng dòng KẾT LUẬN.",
temperature=0)
```

**Kiểm tra:** in biến `principle` ra và đưa cho chuyên gia nghiệp vụ của khách đọc. Đây là lợi ích phụ nhưng rất đáng giá ở site khách, vì nguyên tắc sai bị phát hiện ngay từ bước một, trước khi nó kéo theo cả kết luận.

## Bước 4: tree of thoughts khi bài toán có nhiều nhánh

Có những quyết định không chỉ có hai nhãn, chẳng hạn lập phương án điều phối gồm nhiều bước phụ thuộc nhau. Tree of thoughts xử lý loại này bằng cách cho model sinh các ý tưởng, tự chấm chúng, rồi kết hợp với thuật toán tìm kiếm như BFS hoặc DFS. Phiên bản dưới đây là BFS rút gọn, **đã đơn giản hóa nhiều** so với bài báo gốc.

```python
def score(path: str) -> float:
s = call_llm("Chấm 1-10 mức khả thi của hướng lập luận sau. "
f"Chỉ trả về một số.\n{path}", temperature=0)
try:
return float(s.strip())
except ValueError:
return 0.0

def tot_bfs(problem: str, depth=3, breadth=3, keep=2) -> str:
frontier = [""]
for _ in range(depth):
candidates = [
path + "\n" + call_llm(
f"Vấn đề: {problem}\nCác bước đã có:{path}\n"
"Đề xuất MỘT bước tiếp theo.")
for path in frontier for _ in range(breadth)]
ranked = sorted(candidates, key=score, reverse=True)
frontier = ranked[:keep]
return frontier[0]
```

Hãy tính số lần gọi trước khi chạy. Ở tầng một có 1 nhánh nhân 3, tức 3 lần sinh và 3 lần chấm. Tầng hai và tầng ba mỗi tầng có 2 nhánh nhân 3, tức 6 lần sinh và 6 lần chấm. Tổng cộng là 30 lần gọi cho một câu hỏi, gấp 30 lần một prompt đơn.

**Điểm mấu chốt:** Đo mức cải thiện trên dữ liệu của khách trước khi trả giá gấp 9–30 lần cho mỗi quyết định.

## Khi nào không nên dùng bộ khung này?

Prompt Engineering Guide nhắc rằng reasoning model đắt hơn đáng kể so với chat LLM thông thường và chậm hơn, nên chỉ nên dùng cho những thành phần thật sự cần suy luận.

Chạy ensembling 9 lần trên một reasoning model là lấy 9 nhân với một đơn giá mỗi lần gọi vốn đã cao hơn hẳn. Hãy thử trước trên model rẻ, đo xem cải thiện có thật hay không, rồi mới nâng cấp.

Cũng theo hướng dẫn đó, với reasoning model bạn nên tránh ghi chỉ dẫn chain-of-thought kiểu "suy nghĩ từng bước" vào prompt. Lỗi hay gặp là bê nguyên các prompt ở bước 2 sang reasoning model. Hãy giữ phần ngữ cảnh và định dạng KẾT LUẬN, bỏ phần chỉ dẫn từng bước, rồi đo lại.

## Kỹ năng này xuất hiện ở site khách như thế nào?

Khách hàng hiếm khi hỏi bạn về self-consistency. Họ sẽ hỏi: "Model sai thì sao?" Câu trả lời tốt nhất là một luồng xử lý cụ thể: hồ sơ có đồng thuận cao đi thẳng, hồ sơ đồng thuận thấp chuyển cho người duyệt, và bạn có bảng số liệu cho thấy tỷ lệ mỗi nhóm.

Trong CV, đừng viết "thành thạo prompt engineering". Hãy mô tả việc bạn đã làm: dùng ensembling ba prompt kèm ngưỡng đồng thuận để chuyển một phần hồ sơ khó cho người duyệt, đo trên một tập dữ liệu đã gán nhãn, và ghi số liệu thật của bạn chứ không mượn số của benchmark.

Bốn kỹ thuật này không làm model thông minh hơn. Chúng cho model nhiều cơ hội để không đồng ý với chính nó, và chính những lần bất đồng đó cho bạn biết chỗ nào cần một người kiểm tra lại.

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

- Chọn một tác vụ phân loại có hai nhãn trong dự án của bạn, chạy self-consistency với n=5 trên 20 ca và ghi lại tỷ lệ đồng thuận của từng ca
- Viết ba prompt khác góc nhìn cho cùng tác vụ đó, chạy ensembling và đối chiếu những ca có đồng thuận thấp với đáp án do người kiểm tra
- Tính trước số lần gọi model của một cấu hình tree of thoughts rồi quyết định nó có đáng dùng cho tác vụ đó hay không

## Nguồn

- [Self-Consistency | Prompt Engineering Guide](https://www.promptingguide.ai/techniques/consistency)

- [Self-Consistency Prompting: Enhancing AI Accuracy](https://learnprompting.org/docs/intermediate/self_consistency)

- [Self-Consistency Improves Chain of Thought Reasoning in Language Models](https://arxiv.org/abs/2203.11171)

- [Introduction to Ensembling Prompting Techniques for LLMs](https://learnprompting.org/docs/advanced/ensembling/introduction)

- [Step-Back Prompting](https://learnprompting.org/docs/advanced/thought_generation/step_back_prompting)

- [Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models](https://arxiv.org/abs/2310.06117)

- [Tree of Thoughts (ToT) | Prompt Engineering Guide](https://www.promptingguide.ai/techniques/tot)

- [Tree of Thoughts: Deliberate Problem Solving with Large Language Models](https://arxiv.org/abs/2305.10601)

- [Reasoning LLMs | Prompt Engineering Guide](https://www.promptingguide.ai/guides/reasoning-llms)
