·

Chain-of-thought (CoT — chuỗi suy luận) là kỹ thuật giúp LLM (large language model) giải quyết các bài toán nhiều bước bằng cách "nghĩ thành tiếng" trước khi đưa ra câu trả lời cuối cùng. Vấn đề là phần "nghĩ thành tiếng" đó luôn được tính là output token, và output token thường đắt hơn input token 3-5 lần tùy nhà cung cấp. Nếu áp dụng CoT một cách vô tội vạ — bật cho mọi request, không giới hạn độ dài — chi phí vận hành agent có thể tăng gấp nhiều lần mà chất lượng câu trả lời không cải thiện tương ứng. Bài này sẽ đi từ việc định lượng chi phí thật của CoT, đến các kỹ thuật kiểm soát nó trong hệ thống production.

What Chain-of-Thought Actually Costs

Để hiểu vì sao CoT là một trong những nguồn "rò token" lớn nhất trong agentic system, cần nhìn vào cách nó hoạt động ở tầng token. Khi bạn thêm câu "Hãy suy nghĩ từng bước" (think step by step) vào prompt, model không chỉ trả về đáp án — nó sinh ra toàn bộ quá trình lập luận trung gian, và mọi từ trong quá trình đó đều là token phải trả tiền.

Lấy một ví dụ cụ thể: một câu hỏi phân loại đơn giản như "Email này là spam hay không spam?" nếu trả lời trực tiếp chỉ tốn khoảng 5-10 output token ("Không spam."). Nhưng nếu bật CoT, model có thể sinh ra một đoạn lập luận dài 150-300 token kiểu "Trước tiên, tôi sẽ xem xét địa chỉ người gửi... Tiếp theo, tôi phân tích nội dung email để tìm các dấu hiệu điển hình của spam như liên kết lạ, yêu cầu cung cấp thông tin cá nhân... Cuối cùng, dựa trên các yếu tố trên, tôi kết luận..." — gấp 20-30 lần lượng token của câu trả lời trực tiếp, cho một bài toán mà bản thân model đã đủ khả năng trả lời đúng ngay lần đầu.

Nhân con số này lên quy mô production: một agent xử lý 100.000 request/ngày, mỗi request có CoT dư ra trung bình 200 token, với đơn giá output token giả định 15 USD/triệu token — riêng phần "suy nghĩ dư" đã tốn thêm khoảng 300 USD/ngày, tương đương hơn 9.000 USD/tháng, chỉ để giải quyết những bài toán mà phần lớn không cần suy luận nhiều bước. Đây chính là hiện tượng "CoT tax" — cái giá phải trả cho việc bật suy luận mặc định thay vì có chọn lọc.

Cái khó hơn nữa là độ dài CoT không cố định và khó dự đoán. Model có thể lặp lại ý, tự kiểm tra lại nhiều lần, hoặc rơi vào vòng lặp suy luận dài dằng dặc cho một câu hỏi tưởng chừng đơn giản — đặc biệt khi prompt không giới hạn rõ ràng. Điều này khiến chi phí theo request có phương sai (variance) rất lớn, gây khó khăn cho việc dự toán ngân sách vận hành agent.

Mẹo: Trước khi tối ưu, hãy đo lường thực tế bằng cách log riêng số token của phần reasoning và phần answer cho ít nhất 100-200 request điển hình — con số trung bình đó sẽ cho bạn biết "CoT tax" hiện tại của hệ thống là bao nhiêu, thay vì đoán mò.

Targeted CoT: Reasoning Only Where It Matters

Không phải mọi request đều cần suy luận nhiều bước. Nguyên tắc cốt lõi của targeted CoT (CoT có định hướng) là: chỉ bật chuỗi suy luận cho những request thực sự cần — các bài toán có nhiều bước logic, tính toán số học, lập kế hoạch (planning), hoặc yêu cầu tổng hợp thông tin từ nhiều nguồn. Còn với các tác vụ như tra cứu, phân loại đơn giản, trích xuất thông tin có cấu trúc rõ ràng — CoT thường không cải thiện độ chính xác, chỉ làm tăng token và độ trễ (latency).

Cách triển khai thực tế là xây một bộ router (bộ định tuyến) đứng trước model chính, có nhiệm vụ phân loại độ phức tạp của request và quyết định có cần CoT hay không. Router này có thể là:

  1. Một model nhỏ, rẻ (ví dụ Claude Haiku hoặc GPT-4o-mini) chạy bước phân loại trước.
  2. Một tập rule dựa trên heuristic (độ dài câu hỏi, số lượng ràng buộc, có chứa từ khóa như "tính toán", "so sánh", "lập kế hoạch" hay không).
  3. Kết hợp cả hai — rule lọc nhanh các case rõ ràng, model nhỏ xử lý case mơ hồ.

Ví dụ một hàm router đơn giản bằng rule kết hợp gọi model phân loại khi cần:

import re

SIMPLE_PATTERNS = [
    r"^(what|who|when|where) is\b",
    r"^(list|show|get)\b",
    r"^(is|are|does|do)\b.*\?$",
]

COMPLEX_KEYWORDS = [
    "calculate", "compare", "plan", "optimize",
    "step by step", "why", "trade-off", "debug"
]

def needs_cot(user_query: str, word_count_threshold: int = 25) -> bool:
    query_lower = user_query.lower().strip()

    # Rule 1: obviously simple lookup patterns -> no CoT
    for pattern in SIMPLE_PATTERNS:
        if re.match(pattern, query_lower):
            return False

    # Rule 2: contains reasoning-heavy keywords -> needs CoT
    if any(keyword in query_lower for keyword in COMPLEX_KEYWORDS):
        return True

    # Rule 3: long, multi-clause queries likely need reasoning
    word_count = len(query_lower.split())
    has_multiple_clauses = query_lower.count(",") >= 2 or " and " in query_lower

    if word_count > word_count_threshold and has_multiple_clauses:
        return True

    return False


def build_prompt(user_query: str) -> str:
    if needs_cot(user_query):
        return (
            f"{user_query}\n\n"
            "Think through this step by step inside <thinking></thinking> tags, "
            "then give the final answer inside <answer></answer> tags."
        )
    return (
        f"{user_query}\n\n"
        "Answer directly and concisely. No need to show reasoning."
    )

Với cách này, một hệ thống hỗ trợ khách hàng của một sản phẩm SaaS thực tế có thể phân loại 70-80% request là câu hỏi đơn giản (tra cứu chính sách, trạng thái đơn hàng), chỉ 20-30% còn lại — các câu hỏi liên quan đến so sánh gói dịch vụ, tính toán hoàn tiền theo điều khoản phức tạp — mới cần CoT. Việc này giúp giảm tổng token tiêu thụ trung bình trên toàn hệ thống 40-60% so với việc bật CoT cho tất cả.

Một biến thể nâng cao hơn là CoT theo mức độ tin cậy (confidence-based): cho model trả lời trực tiếp trước, nếu độ tin cậy thấp (ví dụ model tự đánh giá hoặc câu trả lời có dấu hiệu mâu thuẫn) mới gọi lại với CoT. Cách này tốn thêm một lượt gọi cho các case khó nhưng tiết kiệm đáng kể cho phần lớn case dễ.

Mẹo: Log lại tỷ lệ request được router gắn cờ "cần CoT" theo thời gian — nếu tỷ lệ này tăng dần bất thường, có thể router đang bị "trôi" (drift) do phân bố câu hỏi thay đổi, cần retrain hoặc điều chỉnh lại rule.

Structured CoT: Constraining the Reasoning Format

Ngay cả khi đã xác định một request cần CoT, không có nghĩa là để model tự do suy luận bao lâu tùy ý. Structured CoT (CoT có cấu trúc) là kỹ thuật ràng buộc hình thức và độ dài của phần suy luận bằng chỉ dẫn rõ ràng trong prompt, thay vì chỉ nói "think step by step" rồi mặc kệ.

Các kỹ thuật ràng buộc phổ biến, xếp theo mức độ chặt:

1. Giới hạn số bước. Thay vì để model tự quyết định cần bao nhiêu bước, yêu cầu cụ thể:

Reason through this in at most 3 numbered steps, each step no more than
one sentence. Then give the final answer.

1. ...
2. ...
3. ...

Answer: ...

2. Giới hạn độ dài "scratchpad" (khoảng nháp). Đặt ngân sách token rõ ràng cho phần suy luận:

Think through this problem in a scratchpad of no more than 80 words.
Do not repeat the question. Do not restate given information —
only reason about what is not yet known.

3. Bắt buộc format cô đọng thay vì văn xuôi. Yêu cầu model dùng cụm từ ngắn, không viết câu hoàn chỉnh trong phần suy luận:

Use terse fragments in your reasoning (e.g. "revenue up 12% -> above target"),
not full sentences. Full sentences only in the final answer.

4. Kỹ thuật "step budget" động theo độ phức tạp — với router đã xây ở phần trước, có thể gán số bước tối đa khác nhau theo độ phức tạp ước lượng: câu hỏi trung bình cho phép 3 bước, câu hỏi phức tạp cho phép 5-6 bước, nhưng không bao giờ để mở.

So sánh thực tế: một bài toán lập kế hoạch di chuyển "làm sao để đi từ điểm A đến điểm B qua 3 điểm dừng với ràng buộc thời gian" — nếu để CoT tự do, model có thể sinh 400-600 token suy luận, lặp lại giả định, tự kiểm tra lại nhiều lần. Với structured CoT giới hạn "tối đa 4 bước, mỗi bước một câu", cùng bài toán chỉ tốn 80-120 token suy luận mà kết quả cuối vẫn chính xác tương đương trong phần lớn trường hợp — vì bản chất bài toán chỉ cần 4 bước logic thật, phần token dư ra trước đó chỉ là model "nói vòng vo" chứ không thêm giá trị suy luận.

Cần lưu ý: ràng buộc quá chặt (ví dụ giới hạn 1 bước cho bài toán vốn cần 5 bước logic) có thể làm giảm độ chính xác. Nguyên tắc là ước lượng số bước logic tối thiểu cần thiết cho loại bài toán đó (qua thử nghiệm/eval thực tế), rồi đặt giới hạn nhích hơn một chút — không đặt tùy tiện.

Mẹo: Chạy eval so sánh độ chính xác giữa CoT tự do và structured CoT trên cùng một bộ test case trước khi đưa vào production — nếu độ chính xác không giảm đáng kể (thường ngưỡng chấp nhận là dưới 2-3 điểm % chênh lệch) mà token giảm 50-70%, structured CoT là lựa chọn rõ ràng có lợi hơn.

Separating Thinking Tokens from Answer Tokens in Production Systems

Trong hệ thống production, một sai lầm phổ biến là trả nguyên văn cả phần suy luận lẫn câu trả lời cuối cùng cho người dùng, hoặc — tệ hơn — không tách biệt hai phần này trong logging, khiến việc theo dõi chi phí và debug trở nên mù mờ. Kiến trúc đúng là tách rõ thinking tokens (token dùng cho suy luận nội bộ) khỏi answer tokens (token của câu trả lời cuối cùng hiển thị cho người dùng), bằng cách bọc phần suy luận trong delimiter hoặc tag rõ ràng (ví dụ XML tag <thinking>/<answer>), rồi parse và lọc ở tầng application trước khi trả về client.

Lợi ích của việc tách biệt này gồm: (1) người dùng cuối không bị "ngộp" bởi văn bản suy luận dài dòng không liên quan đến câu hỏi của họ; (2) team vận hành có thể log riêng số token của từng phần để theo dõi chi phí theo cấu phần (cost attribution); (3) có thể strip hoàn toàn phần thinking trước khi lưu vào lịch sử hội thoại (conversation history) gửi lại cho model ở turn sau, tránh tích lũy token suy luận cũ không còn cần thiết qua nhiều lượt hội thoại.

Dưới đây là ví dụ đầy đủ về pattern này bằng Python, dùng tag <thinking><answer>, kèm parser và logic tính chi phí riêng từng phần:

import re
from dataclasses import dataclass


@dataclass
class ParsedResponse:
    thinking: str
    answer: str
    thinking_token_count: int
    answer_token_count: int


def estimate_tokens(text: str) -> int:
    # Rough heuristic: ~4 characters per token for English text.
    # Replace with a real tokenizer (e.g. tiktoken) for production accuracy.
    return max(1, len(text) // 4)


def parse_structured_response(raw_output: str) -> ParsedResponse:
    thinking_match = re.search(
        r"<thinking>(.*?)</thinking>", raw_output, re.DOTALL
    )
    answer_match = re.search(
        r"<answer>(.*?)</answer>", raw_output, re.DOTALL
    )

    thinking_text = thinking_match.group(1).strip() if thinking_match else ""
    answer_text = answer_match.group(1).strip() if answer_match else raw_output.strip()

    return ParsedResponse(
        thinking=thinking_text,
        answer=answer_text,
        thinking_token_count=estimate_tokens(thinking_text),
        answer_token_count=estimate_tokens(answer_text),
    )


def handle_llm_response(raw_output: str, request_id: str) -> str:
    parsed = parse_structured_response(raw_output)

    # Log the split for cost monitoring / observability dashboards.
    log_token_split(
        request_id=request_id,
        thinking_tokens=parsed.thinking_token_count,
        answer_tokens=parsed.answer_token_count,
    )

    # Only the answer goes back to the end user.
    return parsed.answer


def log_token_split(request_id: str, thinking_tokens: int, answer_tokens: int) -> None:
    total = thinking_tokens + answer_tokens
    ratio = thinking_tokens / total if total else 0
    print(
        f"[token-monitor] request={request_id} "
        f"thinking={thinking_tokens} answer={answer_tokens} "
        f"thinking_ratio={ratio:.2%}"
    )
    # In production: push these metrics to your observability stack
    # (e.g. Datadog, Prometheus, or a custom BigQuery table) so you can
    # alert when thinking_ratio spikes across a batch of requests.

Với các model có API "extended thinking" / reasoning-token native (ví dụ Claude với thinking blocks, OpenAI reasoning models trả riêng reasoning_tokens trong usage object) — provider đã tách sẵn hai loại token này ở tầng API, bạn không cần tự parse bằng regex mà đọc trực tiếp từ response object. Nhưng nếu bạn đang dùng structured CoT thủ công qua prompt (tag tự định nghĩa) trên model không có tính năng này, pattern parser ở trên là cách chuẩn để đạt hiệu quả tương đương.

Một điểm quan trọng khi thiết kế hệ thống nhiều lượt hội thoại (multi-turn): khi gửi lại lịch sử hội thoại cho model ở turn sau, chỉ nên gửi lại phần <answer>, không gửi lại <thinking> của các turn trước — vì phần suy luận cũ hầu như không còn giá trị tham chiếu cho turn hiện tại, nhưng vẫn tốn input token nếu giữ lại.

Mẹo: Thiết lập alert khi thinking_ratio (tỷ lệ token suy luận / tổng token) của một request hoặc một batch vượt ngưỡng bất thường (ví dụ trên 70-80%) — đây thường là dấu hiệu model đang "loop" suy luận không cần thiết hoặc prompt structured CoT của bạn chưa ràng buộc đủ chặt.

Reasoning Models: Extended Thinking and When to Use It

Ngoài việc tự viết prompt CoT trên model tiêu chuẩn, một hướng khác là dùng các reasoning model (model suy luận chuyên biệt) — như dòng OpenAI o-series, Claude với extended thinking (suy luận mở rộng), hoặc Gemini thinking mode. Các model này được huấn luyện đặc biệt để tự sinh chuỗi suy luận nội bộ dài, có cơ chế billing/budget riêng cho reasoning token (thường tách biệt với token câu trả lời trong hóa đơn), và cho phép bạn cấu hình mức "ngân sách suy nghĩ" (thinking budget) tường minh qua tham số API thay vì chỉ dựa vào prompt.

Điểm khác biệt cốt lõi so với việc tự viết CoT prompt trên model chuẩn: reasoning model có khả năng tự sửa lỗi ngay trong quá trình suy luận nội bộ (self-correction) hiệu quả hơn nhiều, đặc biệt với các bài toán đòi hỏi suy luận nhiều bước sâu, toán học phức tạp, hoặc lập trình có logic nhiều lớp. Nhưng đi kèm với đó là chi phí: reasoning token trên các model này thường được tính giá cao hơn hẳn (có thể tương đương hoặc cao hơn giá output token thông thường), và với "extended thinking budget" ở mức cao, một request đơn lẻ có thể tiêu tốn hàng nghìn đến hàng chục nghìn token chỉ cho phần suy luận nội bộ mà người dùng không nhìn thấy.

Bảng so sánh chi phí ước lượng cho một bài toán lập trình trung bình (fix một bug logic có 2-3 lớp điều kiện lồng nhau):

Phương án Token suy luận (ước lượng) Token câu trả lời Ghi chú
Model chuẩn, không CoT 0 80-150 Thường sai với bug logic nhiều lớp
Model chuẩn, CoT tự do qua prompt 300-600 100-200 Độ chính xác khá, chi phí trung bình
Model chuẩn, structured CoT 100-200 100-200 Độ chính xác gần tương đương CoT tự do, rẻ hơn
Reasoning model, thinking budget thấp 500-1.500 100-200 Độ chính xác cao hơn hẳn
Reasoning model, thinking budget cao 3.000-10.000+ 100-200 Độ chính xác cao nhất, chi phí có thể gấp 10-30 lần

Từ bảng trên, nguyên tắc chọn lựa thực tế là: reasoning model chỉ đáng dùng khi structured CoT trên model chuẩn đã "hết cửa" — nghĩa là bạn đã thử ràng buộc CoT thủ công, đã đo eval, và độ chính xác vẫn không đạt yêu cầu cho những bài toán đặc biệt khó (toán học đa bước, suy luận logic hình thức, debug code với nhiều tầng phụ thuộc, các bài toán lập kế hoạch có ràng buộc chồng chéo). Với các tác vụ agentic thông thường trong sản phẩm — trả lời câu hỏi, tóm tắt, phân loại, viết code đơn giản đến trung bình — structured CoT trên model chuẩn thường đã đủ, và rẻ hơn đáng kể.

Một chiến lược lai (hybrid) hiệu quả trong thực tế: dùng router (đã xây ở phần Targeted CoT) để phân loại 3 tầng — (1) không cần CoT, (2) structured CoT trên model chuẩn, (3) reasoning model — và chỉ escalate lên tầng 3 khi tầng 2 thất bại hoặc bài toán được gắn cờ độ khó cao ngay từ đầu (ví dụ qua từ khóa "prove", "optimal", "multi-constraint", hoặc lịch sử agent đã retry nhiều lần không thành công).

Mẹo: Khi dùng reasoning model, luôn set giới hạn thinking budget rõ ràng qua tham số API (không để mặc định "unlimited") — nhiều nhà cung cấp cho phép cấu hình mức token tối đa cho reasoning riêng biệt với max_tokens của câu trả lời, dùng nó để tránh trường hợp một request đơn lẻ "ăn" ngân sách token của cả một batch.

Practical CoT Decision Framework

Để áp dụng nhất quán tất cả những gì đã phân tích ở trên vào một agent thực tế, dưới đây là khung quyết định (decision framework) theo dạng cây quyết định, giúp engineer trả lời nhanh câu hỏi "request này nên xử lý theo cách nào?" mà không cần suy nghĩ lại từ đầu mỗi lần.

Bước 1 — Phân loại độ phức tạp bài toán. Dùng router (rule-based hoặc model nhỏ) để gắn nhãn request vào một trong ba nhóm:
- Nhóm A — Tra cứu/phân loại đơn giản: câu hỏi có đáp án trực tiếp, không cần suy luận trung gian (tra cứu trạng thái, phân loại nhị phân rõ ràng, trích xuất trường dữ liệu có cấu trúc).
- Nhóm B — Suy luận trung bình: cần 2-5 bước logic (so sánh nhiều lựa chọn, tính toán đơn giản, tổng hợp từ 2-3 nguồn thông tin).
- Nhóm C — Suy luận sâu/nhiều lớp: toán học phức tạp, lập kế hoạch nhiều ràng buộc chồng chéo, debug logic nhiều tầng, các bài toán mà structured CoT thông thường đã từng thất bại trong eval.

Bước 2 — Ánh xạ nhóm sang chiến lược xử lý:

Nhóm Chiến lược Ngân sách token suy luận gợi ý
A Không CoT — trả lời trực tiếp 0
B Structured CoT trên model chuẩn (giới hạn 3-5 bước) 80-200 token
C Reasoning model với thinking budget được cấu hình, hoặc structured CoT với ngân sách cao hơn (300-500 token) nếu chưa cần escalate hẳn lên reasoning model 300-2.000+ token tùy độ khó

Bước 3 — Thiết lập cơ chế escalation (leo thang) có kiểm soát. Không escalate ngay lên reasoning model cho mọi case nhóm C nếu chưa thử structured CoT trước — chỉ escalate khi: (a) structured CoT trả lời sai hoặc mâu thuẫn theo cơ chế self-check, (b) bài toán được đánh dấu độ khó cao từ đầu qua từ khóa/heuristic, hoặc (c) đây là domain đã biết trước cần reasoning model (ví dụ hệ thống chuyên xử lý bài toán toán học, chứng minh logic).

Bước 4 — Giám sát liên tục bằng metric đã tách ở phần trước. Theo dõi thinking_ratio trung bình theo từng nhóm A/B/C, theo dõi tỷ lệ request được router phân vào từng nhóm theo thời gian, và theo dõi chi phí theo cấu phần (thinking token vs answer token) trên dashboard. Nếu nhóm A tăng bất thường sang nhóm B/C, có thể router đang quá "nhạy", hoặc ngược lại nếu nhóm C giảm sai độ chính xác, có thể router đang bỏ sót case khó.

Bước 5 — Review định kỳ ngân sách và ngưỡng. Vì phân bố loại câu hỏi thực tế của người dùng thay đổi theo thời gian (sản phẩm ra tính năng mới, người dùng đặt câu hỏi phức tạp hơn khi đã quen thuộc hệ thống), các ngưỡng phân loại và ngân sách token ở bước 1-2 cần được review lại theo chu kỳ (ví dụ hàng tháng hoặc khi phát hành tính năng lớn), dựa trên dữ liệu eval và log thực tế, không nên "set once and forget."

Áp dụng đúng framework này thường giúp giảm 50-70% tổng chi phí token liên quan đến reasoning so với việc bật CoT tự do cho mọi request, mà không đánh đổi đáng kể về độ chính xác — vì phần lớn giá trị của CoT nằm ở việc áp dụng đúng chỗ, đúng mức, chứ không nằm ở việc để model "nghĩ" càng nhiều càng tốt.

Mẹo: Lưu lại framework này dưới dạng một bảng cấu hình (config) tách biệt khỏi code — router threshold, token budget theo nhóm, điều kiện escalation — để team có thể điều chỉnh nhanh qua review dữ liệu thực tế mà không cần deploy lại code mỗi lần.