Nếu prompt engineering ở phần trước tập trung vào việc bạn "hỏi" LLM như thế nào, thì phần này tập trung vào việc kiểm soát LLM "trả lời" như thế nào — bao nhiêu chữ, theo cấu trúc gì, và với mức độ chi tiết ra sao. Đây là nhóm kỹ thuật thường bị bỏ qua nhất trong các team mới làm AI agent, nhưng lại là nơi mang lại ROI (tỷ suất lợi nhuận trên chi phí) rõ ràng nhất, vì output token luôn đắt hơn input token và verbosity dư thừa sẽ nhân chi phí lên theo từng lượt gọi API. Bài viết này sẽ đi qua từng kỹ thuật cụ thể — từ ràng buộc độ dài, ép format có cấu trúc, kiểm soát persona/style, đến streaming — kèm ví dụ prompt và code thực tế để bạn áp dụng ngay vào agent hoặc feature AI của mình.
The Economics of Output Tokens (Kinh tế học của Output Token)
Điều đầu tiên mọi kỹ sư cần khắc cốt ghi tâm: output token luôn đắt hơn input token, thường là 3-5 lần, tùy model. Lý do kỹ thuật nằm ở cách LLM sinh văn bản — quá trình decode diễn ra tuần tự (autoregressive), mỗi token mới phải chạy qua toàn bộ forward pass của model, trong khi input token có thể được xử lý song song (prefill) và tận dụng cơ chế prompt caching. Vì vậy các nhà cung cấp định giá output cao hơn hẳn để phản ánh chi phí compute thực tế.
Bảng dưới đây minh họa mức giá phổ biến (đơn vị: USD / 1 triệu token, số liệu mang tính tham khảo, có thể thay đổi theo thời điểm và cần kiểm tra lại trên trang pricing chính thức):
| Model | Input ($/1M token) | Output ($/1M token) | Hệ số output/input |
|---|---|---|---|
| Claude Opus (tier cao cấp) | 15.00 | 75.00 | 5x |
| Claude Sonnet (tier trung) | 3.00 | 15.00 | 5x |
| GPT-4o | 2.50 | 10.00 | 4x |
| GPT-4o-mini | 0.15 | 0.60 | 4x |
| Gemini 1.5 Pro | 1.25 | 5.00 | 4x |
Nhìn vào bảng này, bạn sẽ thấy một sự thật quan trọng: nếu agent của bạn xử lý 1 triệu request/tháng, và mỗi response dư ra 200 token verbosity không cần thiết (một đoạn giải thích thừa, một câu tóm tắt lại câu hỏi, một lời chào mở đầu), thì với model tier Sonnet, con số đó tương đương 200 triệu token output dư thừa × $15/1M = $3,000/tháng chỉ để trả tiền cho những chữ không ai đọc. Ở quy mô enterprise, số này có thể lên tới hàng chục nghìn USD/tháng.
Một điểm nữa cần lưu ý: chi phí output không chỉ là tiền, còn là latency (độ trễ). Vì decode là tuần tự, mỗi token sinh ra tốn thêm vài chục milliseconds. Response dài hơn không chỉ đắt hơn mà còn chậm hơn — điều này đặc biệt quan trọng với các agent chạy real-time (chatbot, coding assistant, voice agent).
Mẹo: Trước khi tối ưu bất kỳ prompt nào, hãy đo baseline: log lại số output token trung bình trên một mẫu 50-100 request thực tế, rồi nhân với đơn giá output của model bạn đang dùng để có con số "chi phí verbosity" cụ thể — con số này sẽ là động lực thuyết phục để cả team ưu tiên tối ưu output.
Direct Length Constraints (Ràng buộc Độ dài Trực tiếp)
Cách đơn giản và trực quan nhất để giảm output token là yêu cầu model giới hạn độ dài ngay trong prompt. Có ba lớp kỹ thuật ở đây: ràng buộc mềm (soft constraint) bằng ngôn ngữ tự nhiên, ràng buộc cứng (hard constraint) bằng tham số API, và ràng buộc kết hợp.
Ràng buộc mềm qua instruction. Đây là cách phổ biến nhất: chèn trực tiếp yêu cầu độ dài vào system prompt hoặc user prompt.
Answer in no more than 3 sentences.
Summarize this in exactly 5 bullet points, each under 15 words.
Respond in under 100 words total.
Vấn đề là các LLM — đặc biệt là các model đời cũ hoặc khi prompt có nhiều instruction cạnh tranh nhau — thường "lờ" giới hạn mềm này. Model có thể đếm sai số câu, viết 8 bullet thay vì 5, hoặc vượt quá 100 từ vì "cảm thấy" chưa trả lời đủ ý. Đây là hạn chế cố hữu vì LLM không đếm token/từ một cách chính xác tuyệt đối khi generate — nó dự đoán token tiếp theo dựa trên xác suất, không chạy một bộ đếm thực sự.
Ràng buộc cứng qua max_tokens. Đây là tham số API cắt cứng số token output, bất kể model có muốn viết tiếp hay không.
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=150, # hard cutoff, response bị cắt ngang nếu vượt
messages=[{"role": "user", "content": "Explain what a race condition is."}]
)
Rủi ro lớn nhất của max_tokens là truncation (cắt cụt) — response bị dừng giữa câu, giữa một đoạn JSON, hoặc giữa một code block, khiến output không dùng được và bạn vẫn mất tiền cho phần token đã sinh ra trước khi bị cắt. Vì vậy, max_tokens nên được set dựa trên ước lượng thực tế (đo từ dữ liệu lịch sử) cộng thêm buffer an toàn, không nên set tùy tiện.
Kỹ thuật kết hợp — khuyến nghị thực tế. Cách hiệu quả nhất trong sản xuất là dùng cả hai: instruction mềm để định hướng model viết ngắn tự nhiên (giảm khả năng bị cắt cụt), và max_tokens như một lưới an toàn (safety net) để chặn trường hợp model "nói nhiều" bất thường.
System: You are a technical support agent. Keep every response under 80 words.
If the answer needs more detail, say "Ask me to elaborate" instead of writing more.
Kết hợp với max_tokens=200 (rộng hơn giới hạn mềm một chút để tránh cắt cụt oan) sẽ cho kết quả ổn định hơn là chỉ dùng một trong hai.
Mẹo: Đừng chỉ ra lệnh "trả lời ngắn gọn" — hãy cho model một đơn vị đo cụ thể (số câu, số từ, số bullet) vì model tuân thủ ràng buộc có đơn vị đo tốt hơn nhiều so với các từ mơ hồ như "ngắn gọn" hay "súc tích".
Format Shaping: Structured Outputs Over Prose (Ép Output có Cấu trúc thay vì Văn xuôi Tự do)
Văn xuôi tự do (free-form prose) là dạng output "tốn token" nhất vì model phải sinh ra các từ nối, câu chuyển ý, câu mở đầu/kết luận để văn bản đọc "tự nhiên". Khi bạn ép model xuất ra dữ liệu có cấu trúc — JSON, bảng, hoặc template cố định — bạn loại bỏ hoàn toàn phần "đệm ngôn ngữ" đó, đồng thời giúp output dễ parse bằng code hơn.
Dùng JSON Schema / structured output. Các API hiện đại (OpenAI structured outputs, Claude tool use/function calling) cho phép bạn định nghĩa schema và buộc model trả về đúng cấu trúc đó, không thêm lời dẫn hay giải thích ngoài lề.
import json
schema = {
"type": "object",
"properties": {
"bug_severity": {"type": "string", "enum": ["low", "medium", "high", "critical"]},
"affected_module": {"type": "string"},
"root_cause": {"type": "string", "maxLength": 200},
"fix_suggested": {"type": "boolean"}
},
"required": ["bug_severity", "affected_module", "root_cause", "fix_suggested"]
}
prose_response = """
Based on my analysis, this bug appears to be of high severity. It affects
the payment processing module. The root cause seems to be a race condition
when two requests hit the database simultaneously. I would suggest adding
a database-level lock to fix this issue.
""" # ~55 tokens
json_response = json.dumps({
"bug_severity": "high",
"affected_module": "payment_processing",
"root_cause": "Race condition on concurrent DB writes",
"fix_suggested": True
}) # ~30 tokens, and directly parseable — no regex/NLP needed downstream
Cùng một lượng thông tin, JSON response tiết kiệm gần 40-45% token so với văn xuôi, và quan trọng hơn: bạn không cần viết logic parse ngôn ngữ tự nhiên để trích xuất bug_severity từ câu "bug này có mức độ nghiêm trọng cao" — đây là lợi ích kép cho các agent pipeline nối nhiều bước.
Dùng bảng (table) cho dữ liệu so sánh. Khi cần trình bày nhiều item với nhiều thuộc tính (so sánh 3 giải pháp kỹ thuật, list lỗi tìm thấy trong code review), bảng Markdown nén thông tin hiệu quả hơn hẳn so với văn xuôi liệt kê từng ý.
Instead of:
"The first approach uses caching which is fast but has a memory overhead of about 200MB.
The second approach uses lazy loading which is slower but uses only 50MB of memory..."
Use:
| Approach | Speed | Memory |
|---|---|---|
| Caching | Fast | ~200MB |
| Lazy loading | Slower | ~50MB |
Dùng template cố định cho các use case lặp lại. Nếu agent của bạn luôn cần trả về cùng một dạng thông tin (ví dụ: tóm tắt PR, báo cáo test case), hãy định nghĩa template và yêu cầu model điền vào đúng khung, không thêm phần mở/kết.
Fill in this exact template, no additional text before or after:
## Summary
[one sentence]
## Changes
- [bullet per file changed, max 10 words each]
## Risk
[low/medium/high] — [reason in under 15 words]
Mẹo: Khi dùng function calling / tool calling để ép structured output, hãy đặt maxLength hoặc giới hạn item cho các field dạng string/array trong schema — nếu không, model vẫn có thể viết một đoạn root_cause dài 500 từ vì schema chỉ ép format, không tự động ép độ dài.
Verbosity Control via Persona and Style Instructions (Kiểm soát Verbosity qua Persona và Phong cách Diễn đạt)
Một trong những nguồn verbosity "vô hình" nhất là do model được huấn luyện để nghe có vẻ hữu ích, lịch sự và cẩn trọng — dẫn đến các câu như "Chắc chắn rồi, tôi rất vui được giúp bạn với vấn đề này" hoặc "Hy vọng điều này hữu ích! Nếu bạn cần thêm thông tin, đừng ngần ngại hỏi." Những câu này không sai, nhưng chúng tốn token mà không mang giá trị thông tin nào cho một agent tự động hoặc một API backend.
Persona framing để loại bỏ filler (những câu đệm không mang thông tin). Gán cho model một persona "chuyên gia bận rộn, nói thẳng vào vấn đề" (terse expert) làm giảm đáng kể các câu đệm xã giao này.
System: You are a senior staff engineer reviewing code in a fast-paced startup.
You have limited time. You never use greetings, apologies, or pleasantries.
You state findings directly, using technical vocabulary without hedging.
So sánh hai response cho cùng một câu hỏi "review đoạn code này":
Without persona framing (~120 tokens):
"Thank you for sharing this code! I've taken a look and I think overall
it's pretty solid. However, I did notice a few things that might be worth
considering. First, it seems like there might be a potential null pointer
issue on line 12, though I could be wrong. Also, you might want to think
about adding some error handling around the API call..."
With persona framing (~35 tokens):
"Line 12: null pointer risk, `user` not checked before `.name` access.
Line 24: API call missing error handling — wrap in try/catch."
Giảm hơn 70% token cho cùng nội dung hữu ích, chỉ bằng cách đổi persona.
Style rule rõ ràng thay vì mơ hồ. "Hãy súc tích" là một chỉ dẫn mơ hồ mà model diễn giải khác nhau tùy ngữ cảnh. Style rule cụ thể — liệt kê chính xác điều nên/không nên làm — hiệu quả hơn nhiều.
Style rules:
- No greetings ("Sure!", "Great question!", "Certainly!")
- No hedging language ("might", "could possibly", "it seems like")
- No closing remarks ("Let me know if you need anything else!")
- State facts and findings directly as declarative sentences
- Use technical terms without defining them (audience is senior engineers)
Điều chỉnh persona theo đối tượng (audience), không chỉ theo độ ngắn. Với các persona đa dạng trong team (QA, PM, dev), bạn có thể tùy biến mức độ formal/casual để giảm phần giải thích lại các khái niệm cơ bản mà đối tượng đó đã biết — ví dụ persona cho PM không cần giải thích thuật ngữ kỹ thuật, nhưng persona cho dev junior thì có thể cần giữ lại một phần giải thích (trade-off — sự đánh đổi — giữa token và độ rõ ràng, xem thêm phần sau).
Mẹo: Đưa ra 2-3 ví dụ cụ thể "không nên viết như thế này" (few-shot negative example) ngay trong system prompt — model học theo pattern rất nhanh khi thấy ví dụ cụ thể, hiệu quả hơn việc chỉ mô tả bằng tính từ như "ngắn gọn", "chuyên nghiệp".
Controlling Reasoning and Explanation Depth (Kiểm soát Độ sâu Reasoning và Giải thích)
Không phải lúc nào giải thích chi tiết cũng lãng phí — vấn đề là biết khi nào giá trị của việc giải thích đáng với chi phí token, và khi nào không. Đây là nơi nhiều team mắc lỗi theo hai hướng cực đoan: luôn để model giải thích dài dòng (tốn token vô ích khi câu trả lời chỉ cần một giá trị đơn), hoặc luôn tắt giải thích (mất khả năng debug khi agent trả lời sai và không ai hiểu vì sao).
Answer-only mode cho các tác vụ có kết quả rõ ràng. Khi câu trả lời là một giá trị, một lựa chọn, hoặc một hành động cụ thể — không cần lý giải trong response, vì lý giải (nếu cần) nên nằm ở log riêng, không nằm trong output chính.
Classify the sentiment of this review as: positive, negative, or neutral.
Respond with ONLY the single word, no explanation, no punctuation.
Given this test failure log, output ONLY the file path most likely
containing the root cause. No reasoning, no alternative suggestions.
Giữ lại reasoning khi cần calibration hoặc audit. Với các quyết định có rủi ro cao — ví dụ agent tự động quyết định merge một PR, hoặc phân loại mức độ nghiêm trọng của một incident — việc bỏ hoàn toàn reasoning khiến con người không thể kiểm tra logic của model, và khiến việc debug khi model sai trở nên gần như không thể. Trong các trường hợp này, trade-off đúng là: giữ reasoning nhưng nén nó lại, không loại bỏ.
Decide if this PR is safe to auto-merge. Respond in this exact format:
DECISION: [approve/reject]
REASON: [one sentence, max 20 words]
Tách "final answer" khỏi "reasoning trace" bằng structure. Nếu bạn cần cả câu trả lời và reasoning (ví dụ để log lại cho việc audit sau này), hãy tách chúng thành hai field riêng trong structured output, và chỉ hiển thị field "answer" cho end-user, còn field "reasoning" được lưu log ở backend, không hiển thị lên UI — điều này không giảm token đã sinh ra, nhưng đảm bảo bạn không phải trả token hai lần cho việc "giải thích lại" ở downstream call.
{
"answer": "reject",
"reasoning_summary": "3 unit tests fail on the modified auth middleware"
}
Cân nhắc đặc biệt với reasoning model. Với các model có chế độ "extended thinking" hoặc "reasoning" riêng (khác với response chính), phần suy luận nội bộ (internal reasoning token) thường được tính phí như output token dù người dùng không thấy nó trực tiếp. Với các tác vụ đơn giản (phân loại, trích xuất field), việc bật reasoning mode mặc định có thể gây tốn token oan — nên chỉ bật reasoning mode/effort cao cho các tác vụ thực sự cần suy luận nhiều bước (multi-step reasoning), và dùng mode nhanh/effort thấp cho các tác vụ đơn giản, lặp lại.
Mẹo: Với các pipeline agent nhiều bước, hãy tách rõ hai loại call: "reasoning call" (cần giải thích, chấp nhận tốn token) chỉ chạy ở bước ra quyết định quan trọng, và "extraction call" (answer-only, không giải thích) cho các bước phụ như trích xuất field, format lại dữ liệu — đừng dùng cùng một cấu hình verbosity cho toàn bộ pipeline.
Negative Constraints and What Not to Generate (Ràng buộc Phủ định và Những Gì Không Nên Generate)
Bên cạnh việc nói cho model biết phải làm gì, việc nói rõ không được làm gì cũng là một công cụ mạnh để cắt giảm verbosity — đặc biệt hiệu quả với các pattern verbosity "mặc định" mà model được train để luôn thêm vào, trừ khi có instruction rõ ràng ngăn lại.
Các negative constraint phổ biến và hiệu quả:
- Do not apologize or use phrases like "I apologize" / "Sorry for the confusion."
- Do not restate or repeat the user's question before answering.
- Do not use Markdown formatting (bold, headers, bullet lists) unless explicitly asked.
- Do not add a summary or recap after the main answer.
- Do not include disclaimers about being an AI or about limitations
unless directly relevant to the answer's accuracy.
- Do not offer to help further at the end of the response.
Áp dụng cùng lúc nhiều negative constraint trong system prompt cho một API backend (không phải chat UI) thường giảm 15-30% token so với response mặc định, vì bạn loại bỏ toàn bộ các câu "meta" không mang thông tin nghiệp vụ.
Giới hạn và điểm cần lưu ý:
Negative constraint có ba hạn chế cần biết để dùng đúng cách:
- Hiệu lực suy giảm theo độ dài conversation. Trong các cuộc hội thoại nhiều lượt (multi-turn), model có xu hướng "quên" các negative constraint đặt ở đầu system prompt sau vài chục lượt trao đổi — nếu agent của bạn chạy hội thoại dài, cần lặp lại constraint quan trọng ở message gần nhất, hoặc dùng cơ chế nhắc lại định kỳ.
- Negative constraint chung (generic) yếu hơn ví dụ cụ thể. "Không viết dài dòng" yếu hơn nhiều so với việc chỉ ra chính xác pattern không mong muốn kèm ví dụ, như đã nói ở phần persona.
- Constraint chồng chéo có thể gây nhiễu. Nếu bạn có 10+ negative constraint trong một system prompt, model có thể bắt đầu tuân thủ không đều — hãy ưu tiên 3-5 constraint quan trọng nhất theo mức độ tốn token thực tế đo được, thay vì liệt kê tất cả những gì bạn "không muốn thấy".
Test A/B để đo hiệu quả thực tế. Vì hiệu lực của negative constraint không đồng nhất giữa các model và version, cách chắc chắn nhất để biết constraint có tác dụng là chạy cùng một batch input qua hai phiên bản prompt (có/không có constraint) và so sánh token count trung bình, không chỉ dựa vào cảm nhận chủ quan.
Mẹo: Đặt negative constraint càng gần vị trí model bắt đầu generate càng hiệu quả — với nhiều API, constraint đặt ở cuối system prompt hoặc lặp lại ngay trước câu hỏi trong user message thường được tuân thủ tốt hơn so với đặt ở đầu một system prompt dài.
Streaming and Progressive Disclosure (Streaming và Bộc lộ Thông tin Dần dần)
Ngay cả khi đã áp dụng mọi kỹ thuật trên, có những trường hợp bạn không thể biết trước độ dài cần thiết của câu trả lời trước khi model bắt đầu generate — và đây là lúc streaming kết hợp với progressive disclosure (bộc lộ thông tin theo từng lớp, theo yêu cầu) phát huy giá trị.
Streaming để cắt sớm khi đã đủ thông tin. Với API hỗ trợ streaming, bạn nhận token ngay khi model sinh ra, thay vì chờ toàn bộ response hoàn tất. Điều này cho phép bạn implement logic "đủ rồi, dừng lại" — hủy request giữa chừng khi đã thu được thông tin cần, tiết kiệm toàn bộ phần token còn lại mà lẽ ra bạn phải trả tiền.
import anthropic
client = anthropic.Anthropic()
def stream_with_budget(prompt: str, token_budget: int = 120) -> str:
"""
Stream a response but stop consuming (and paying for) tokens once
we cross the budget AND have reached a natural stopping point
(end of sentence), avoiding a mid-word cutoff.
"""
collected = ""
token_count = 0
with client.messages.stream(
model="claude-sonnet-5",
max_tokens=500, # hard upper bound as a safety net
messages=[{"role": "user", "content": prompt}],
) as stream:
for text_chunk in stream.text_stream:
collected += text_chunk
token_count += len(text_chunk.split()) # rough estimate
# Stop early once budget is hit and we're at a sentence boundary
if token_count >= token_budget and collected.rstrip().endswith((".", "!", "?")):
stream.close() # closes the connection, no further billing for new tokens
break
return collected.strip()
short_answer = stream_with_budget(
"Explain what database indexing is and why it matters for performance.",
token_budget=60,
)
print(short_answer)
Kỹ thuật stream.close() ngay khi đạt ngưỡng và gặp điểm dừng tự nhiên (cuối câu) giúp tránh cả hai vấn đề: trả tiền cho token dư thừa, và cắt cụt giữa câu gây khó chịu cho người đọc.
Progressive disclosure — trả lời ngắn trước, mở rộng khi được yêu cầu. Đây là pattern UX kết hợp với tối ưu chi phí: thay vì luôn generate một response đầy đủ chi tiết (mà phần lớn người dùng không đọc hết), bạn thiết kế agent trả lời ngắn ở lượt đầu, và chỉ gọi thêm một lượt API (tốn thêm token) khi người dùng thực sự yêu cầu "giải thích thêm" hoặc "chi tiết hơn".
First-pass system prompt:
"Give a 2-3 sentence direct answer. End with exactly this line if there's
more depth available: '(Ask for more detail if needed.)'
Do not preemptively include the extra detail."
Follow-up prompt (only triggered if user explicitly asks):
"Expand on your previous answer about {topic} with full technical detail,
including edge cases and examples."
Với một FAQ bot hoặc coding assistant, dữ liệu thực tế từ nhiều team cho thấy phần lớn người dùng (thường 60-80% tùy use case) hài lòng với câu trả lời ngắn ở lượt đầu và không bao giờ yêu cầu mở rộng — nghĩa là bạn tiết kiệm được toàn bộ phần token "chi tiết" cho đa số các lượt tương tác, chỉ trả thêm chi phí đó cho nhóm nhỏ thực sự cần.
Kết hợp streaming với UI để cải thiện cảm nhận latency. Ngoài lợi ích tiết kiệm token, streaming còn giúp giảm "perceived latency" (độ trễ cảm nhận) — người dùng thấy chữ xuất hiện ngay lập tức thay vì chờ toàn bộ response, dù tổng thời gian xử lý không đổi. Đây là lợi ích UX đi kèm, không phải lợi ích chi phí trực tiếp, nhưng thường là lý do chính khiến các team quyết định đầu tư implement streaming.
Mẹo: Khi implement early-stop streaming trong production, luôn set một max_tokens cứng làm lưới an toàn song song với logic dừng sớm theo budget — logic đếm token thủ công (như ước lượng theo số từ) có thể lệch so với tokenizer thực tế, nên đừng dựa 100% vào nó để đảm bảo không vượt ngân sách.