·

Câu hỏi mà đội ngũ nào cũng hỏi khi bill token tăng vọt: "Tại sao agent của mình lại tốn nhiều token đến vậy cho một task đơn giản?" Câu trả lời gần như luôn nằm ở cùng một chỗ — toàn bộ context được nạp sẵn (preload) một lần, cho mọi task, bất kể task đó có cần đến 90% những gì được nạp hay không.

Context layering (phân tầng context) là kỹ thuật giải quyết đúng vấn đề đó. Thay vì coi context là một khối duy nhất — nạp hết hoặc không nạp gì — bạn thiết kế nó thành nhiều tầng (layer), mỗi tầng có thời điểm kích hoạt riêng, nội dung riêng, và vòng đời riêng. Một tầng có thể tồn tại suốt phiên làm việc, một tầng khác chỉ xuất hiện khi có tín hiệu phù hợp, và tầng thứ ba chỉ được model chủ động lấy về khi nó thực sự cần.

Bài này đi sâu vào ba chiến lược lắp ghép context — static (tĩnh), dynamic (động), và on-demand (theo yêu cầu) — cách thiết kế từng tầng cho đúng, và cách kết hợp cả ba thành một kiến trúc mạch lạc giúp giảm token cost mà không đánh đổi chất lượng output. Nội dung phù hợp cho kỹ sư backend/frontend xây agent, QA engineer thiết kế context cho automation, và cả product manager cần hiểu vì sao chi phí vận hành agent lại biến động theo từng loại task.


The Three Layering Strategies Defined

Ba chiến lược này khác nhau ở một câu hỏi duy nhất: context được đưa vào conversation ở thời điểm nào, và ai quyết định điều đó?

Static context — nạp sẵn, không đổi trong suốt phiên

Static context là context được lắp ráp một lần và giữ nguyên trong toàn bộ session, toàn bộ vòng đời của agent, hoặc toàn bộ một lần deploy. Nó không đổi theo task, không đổi theo trạng thái conversation, không đổi theo vai trò người dùng. Ví dụ điển hình nhất là system prompt, nhưng file kiểu CLAUDE.md, các tool definition cố định, và các block config được nạp sẵn cũng thuộc dạng này.

Ưu điểm của static context là tính dự đoán được: bạn tính chính xác số token nó chiếm, không có overhead tính toán lúc runtime — nó "cứng" ngay từ đầu. Nhược điểm chính là sự cứng nhắc: bạn trả cùng một khoản token đó cho mọi lần gọi, bất kể thông tin trong đó có liên quan đến task hiện tại hay không.

Dynamic context — lắp ghép theo tín hiệu tại runtime

Dynamic context là context được chọn và lắp ghép tại thời điểm runtime, dựa trên các tín hiệu quan sát được: loại task, trạng thái hiện tại của conversation, danh tính người dùng, loại file đang xử lý, hoặc kết quả của các bước trước đó. Dynamic context thay đổi từ lượt gọi này sang lượt gọi khác — nó thích nghi để đưa đúng thông tin cần thiết vào đúng lúc, và giữ lại thông tin không cần thiết.

Để làm được điều này, bạn cần một tầng lắp ghép (assembly layer) — code hoặc config đánh giá tín hiệu rồi dựng context tương ứng. Điều này tăng độ phức tạp kỹ thuật, nhưng đổi lại tiết kiệm token đáng kể vì context giờ tỷ lệ thuận với nhu cầu thực tế, không còn là "một cỡ cho tất cả".

On-demand context — model tự chủ động lấy khi cần

On-demand context là context không hề được nạp sẵn vào conversation — nó được model tự lấy về, thông qua tool calling (gọi tool), ngay tại lúc model xác định nó cần thêm thông tin. Các lượt tra cứu RAG (Retrieval-Augmented Generation — sinh nội dung có hỗ trợ truy xuất), tool đọc file, query database, hay gọi API đều là các dạng on-demand context. Model phải trả một khoản overhead nhỏ (token cho tool definition + token cho function call), nhưng đổi lại tránh phải trả cho context có thể sẽ không bao giờ dùng đến.

On-demand context có tiềm năng tiết kiệm token tốt nhất trong ba chiến lược, vì model chỉ lấy đúng thứ nó thực sự cần, đúng lúc nó cần. Nhược điểm là độ trễ (mỗi tool call thêm một round-trip — một vòng gọi-nhận) và độ tin cậy (model phải phán đoán đúng khi nào nó cần thêm context, phán đoán sai sẽ dẫn tới việc gọi tool dư thừa hoặc bỏ sót).

Bảng so sánh nhanh

Tiêu chí Static Dynamic On-demand
Ai quyết định nội dung Người thiết kế agent, cố định trước Assembly layer, dựa trên signal Model, dựa trên phán đoán tại runtime
Chi phí token Cố định mỗi lần gọi Biến động theo signal Biến động theo số tool call thực tế
Độ trễ thêm Không Rất nhỏ (thời gian classify signal) Có (mỗi tool call là một round-trip)
Rủi ro lớn nhất Phình to, chứa thứ không liên quan Assembly logic sai, chọn nhầm block Model không gọi tool khi cần, hoặc gọi quá nhiều

Mẹo

Một kiến trúc context tốt luôn dùng cả ba chiến lược cùng lúc: static cho những ràng buộc và danh tính mang tính phổ quát, dynamic cho background liên quan đến task, và on-demand cho dữ liệu cụ thể được lấy về khi công việc tiến triển. Chỉ dựa hoàn toàn vào static context là sai lầm phổ biến nhất về token efficiency — nó đồng nghĩa với việc bạn trả tiền cho thông tin không liên quan đến phần lớn task mà agent xử lý.


Designing Static Context: What Truly Never Changes

Static context xứng đáng được rà soát khắt khe, vì mỗi token trong đó bị trả ở mọi lượt gọi, vĩnh viễn cho tới khi bạn sửa lại system prompt. Tiêu chuẩn để một thông tin được đưa vào static context nên là: "Thông tin này có cần thiết cho mọi lượt tương tác mà agent này thực hiện không?"

Nhóm thông tin hợp lệ cho static context

Danh tính và mục đích của agent (agent identity and purpose): định nghĩa vai trò, nhiệm vụ chính agent thực hiện, và các ràng buộc hành vi định nghĩa "agent này là ai". Đây là thông tin áp dụng cho mọi tương tác, theo đúng định nghĩa của nó.

Ràng buộc cứng và quy tắc an toàn (hard constraints, safety rules): những chỉ dẫn phải tuân theo ở mọi lượt gọi bất kể loại task — quy tắc bảo mật, các điều "tuyệt đối không làm", yêu cầu format output áp dụng chung.

Các hằng số của môi trường (environment invariants): tech stack, phiên bản ngôn ngữ, tên các service bên ngoài — những sự thật về thế giới mà agent đang hoạt động, đúng cho mọi tương tác trong lần deploy này.

Những gì KHÔNG nên nằm trong static context

  • Thông tin về một feature cụ thể đang được xây (thuộc về dynamic hoặc on-demand)
  • Nội dung file cụ thể (thuộc về dynamic hoặc on-demand)
  • Preference của user thu thập lúc bắt đầu session (thuộc về dynamic, nạp vào lúc khởi tạo session)
  • Ví dụ về công việc đã làm trước đó (thuộc về on-demand, lấy về khi liên quan)

Bài test rà soát static context

Với mỗi item trong static context, hãy tự hỏi: "Model có cần đến item này trong 10 lượt tương tác gần nhất không?" Nếu câu trả lời là "không" cho hơn 2 trong số 10 lượt đó, item này là ứng viên nên chuyển sang dynamic hoặc on-demand.

Static context audit — before:
[650 tokens]
Role: Senior code reviewer
Stack: TypeScript, Node.js, PostgreSQL, Prisma
Style guide: Airbnb TypeScript
Security constraints: No secrets, no PII in logs, parameterized queries
Output format: Structured JSON review
Current feature: User authentication module  ← dynamic, không phải static
Files to review pattern: src/auth/**          ← dynamic, không phải static
Recent changes: JWT added in PR #142          ← dynamic, không phải static
Static context — after audit:
[320 tokens]
Role: Senior code reviewer
Stack: TypeScript, Node.js, PostgreSQL, Prisma
Style guide: Airbnb TypeScript
Constraints: No secrets/PII in logs, parameterized queries only
Output format: {"verdict": string, "issues": array, "summary": string}

Ba dòng bị loại bỏ (feature đang làm, pattern file cần review, thay đổi gần đây) không phải thông tin sai — chúng chỉ đơn giản không phải "quy tắc chơi" cố định, mà là "hiện trạng công việc" thay đổi theo từng task. Đưa chúng sang dynamic context nghĩa là chỉ trả token cho chúng khi task đang diễn ra thực sự cần đến, thay vì trả ở mọi lượt gọi kể cả khi review một file hoàn toàn không liên quan tới authentication.

Sai lầm thường gặp: coi static context như "tài liệu kỹ thuật đầy đủ"

Nhiều team, khi mới làm context engineering, có xu hướng nhồi vào static context mọi thứ họ nghĩ agent "có thể cần biết" — mô tả chi tiết từng module, từng luồng nghiệp vụ. Cách làm này sai ở hai điểm. Một, nó tạo ra hai nguồn sự thật song song (code thực tế và mô tả tĩnh) — chúng sẽ lệch nhau ngay khi code đổi mà bạn quên cập nhật, và agent tin vào thông tin sai còn nguy hiểm hơn không có thông tin. Hai, nó lãng phí token ở mọi lượt gọi, kể cả những task đơn giản không cần đến phần mô tả đó.

Mẹo

Đối xử với static context như một sản phẩm có chính sách "feature freeze" nghiêm ngặt. Thêm bất cứ thứ gì vào static context phải là một quyết định có chủ đích, kèm bằng chứng rằng nó thực sự cần cho mọi lượt gọi. Gỡ bỏ một thứ ra khỏi static context cần một bài test xác nhận hành vi của agent không đổi khi thiếu nó. Kỷ luật này ngăn tình trạng static context phình to dần theo thời gian — căn bệnh gần như chắc chắn xảy ra với mọi agent chạy lâu dài nếu không ai chủ động kiểm soát.


Dynamic Context: Signal-Driven Assembly

Lắp ghép dynamic context bắt đầu bằng việc xác định các tín hiệu (signal) quyết định context nào là liên quan, sau đó xây một tầng lắp ghép dịch các tín hiệu đó thành các block context cụ thể.

Các tín hiệu phổ biến để chọn dynamic context

Tín hiệu Context được kích hoạt
Phân loại loại task (task type classification) Chỉ dẫn và ví dụ dành riêng cho loại task đó
Đuôi file của task hiện tại Pattern và convention riêng cho ngôn ngữ lập trình đó
Vai trò / persona người dùng Style output và trọng tâm phù hợp với vai trò
Số lượt hội thoại (turn number) Tóm tắt trạng thái conversation khi số lượt cao
Sprint hiện tại / feature flag Yêu cầu và ràng buộc riêng của feature đang chạy
Trạng thái lỗi được phát hiện Context debug và chỉ dẫn chẩn đoán

Xây tầng lắp ghép dynamic context

Dưới đây là một ví dụ implement thực tế: một agent xử lý nhiều loại task (code review, bug fix, viết test) cho nhiều vai trò người dùng khác nhau (engineer, QA, PM), lắp ghép dynamic context dựa trên signal thay vì nhồi tất cả context của mọi loại task vào system prompt.

from dataclasses import dataclass
from typing import Optional
import anthropic

@dataclass
class TaskSignals:
    task_type: str          # "code_review" | "bug_fix" | "feature_generation" | "test_writing"
    language: Optional[str] # "typescript" | "python" | "java"
    user_role: str          # "engineer" | "qa" | "pm"
    turn_number: int
    has_error_context: bool

CONTEXT_BLOCKS = {
    "code_review": """
Review priorities:
1. Correctness and logic errors
2. Security: injection, auth, secrets
3. Performance: N+1 queries, blocking calls
4. Maintainability: naming, complexity, duplication
""",
    "bug_fix": """
Debugging approach:
1. Reproduce the error from the provided context
2. Identify root cause before proposing fix
3. Check for similar patterns elsewhere in the provided code
4. Propose fix with test to prevent regression
""",
    "test_writing": """
Test writing standards:
- Use Vitest describe/it structure matching existing tests
- Test happy path, error cases, and edge cases
- Mock external dependencies, not internal functions
- Assert on behavior, not implementation
""",
    "typescript": """
TypeScript conventions for this project:
- Strict mode enabled
- No any types without explicit comment justification
- Use Zod for runtime validation
- Prefer type over interface for unions
""",
    "pm_output_style": """
Output style for product context:
- Use plain English, avoid technical jargon
- Structure with: Impact, Approach, Risks, Questions
- Acceptance criteria in Given/When/Then format
"""
}

def assemble_dynamic_context(signals: TaskSignals) -> str:
    blocks = []

    # Task-type context
    if signals.task_type in CONTEXT_BLOCKS:
        blocks.append(CONTEXT_BLOCKS[signals.task_type])

    # Language context
    if signals.language and signals.language in CONTEXT_BLOCKS:
        blocks.append(CONTEXT_BLOCKS[signals.language])

    # Role-specific output style
    if signals.user_role == "pm":
        blocks.append(CONTEXT_BLOCKS["pm_output_style"])

    # Long session: remind to ask for more context if needed
    if signals.turn_number > 15:
        blocks.append("Note: This is a long session. If prior context is ambiguous, ask for clarification rather than assuming.")

    return '\n'.join(blocks).strip()

def call_agent(static_system_prompt: str, signals: TaskSignals, user_message: str,
               conversation_history: list) -> str:
    client = anthropic.Anthropic()

    dynamic_context = assemble_dynamic_context(signals)

    # Combine static + dynamic as the full system prompt
    full_system = f"{static_system_prompt}\n\n{dynamic_context}".strip()

    response = client.messages.create(
        model="claude-sonnet-5",
        max_tokens=2048,
        system=full_system,
        messages=conversation_history + [{"role": "user", "content": user_message}]
    )

    return response.content[0].text

Cách làm này tiết kiệm token vì bạn chỉ trả cho block context riêng của một loại task khi loại task đó đang thực sự chạy. Nếu 40% lượt gọi là code review, 30% là bug fix, 30% là feature generation, mỗi lượt gọi chỉ trả cho một block context nhiệm vụ thay vì cả ba — giảm khoảng 66% chi phí token cho phần task-context so với việc nhồi tất cả vào static.

Điểm quan trọng cần lưu ý: assemble_dynamic_context không gọi LLM — nó là logic thuần (pure logic), chạy rất nhanh, gần như không tốn chi phí. Chi phí thực sự nằm ở bước "biết được signal" — tức bước classify task_type, language, user_role trước khi gọi hàm này.

Mẹo

Triển khai việc phân loại loại task như một bước nhẹ, nhanh, chạy trước lượt gọi LLM chính. Một keyword classifier đơn giản, một regex pattern matcher, hoặc một lượt gọi model nhỏ rất rẻ đều có thể phân loại task type trong vài chục milliseconds với độ chính xác cao. Khoản tiết kiệm token từ dynamic context assembly nhanh chóng vượt xa chi phí của bước classification này — đừng ngại thêm một bước rẻ để tiết kiệm một bước đắt.


On-Demand Context: Letting the Model Fetch What It Needs

On-demand context đảo ngược cách tiếp cận truyền thống. Thay vì dự đoán trước những gì model sẽ cần rồi nạp sẵn, bạn trao cho model các tool để tự lấy thông tin khi nó xác định nó cần.

Cách này mạnh vì nó giao quyền chọn context lại cho chính model — thực thể hiểu rõ nhất điều gì thực sự cần thiết để hoàn thành bước hiện tại. Model chỉ lấy thêm context khi gặp sự bất định thật (không chắc), hoặc khi phát hiện thiếu thông tin thật, thay vì bị "đổ" sẵn hàng nghìn token mà nó có thể không bao giờ dùng tới.

Nguyên tắc thiết kế tool on-demand context hiệu quả

Để on-demand context hoạt động tốt, tool của bạn cần đạt bốn tiêu chí:

  1. Tên tool chính xác, cụ thể — nói rõ cho model biết tool này cung cấp đúng cái gì, tránh tên mơ hồ như get_data hay fetch_info.
  2. Description nêu rõ tool trả về gì, không chỉ mô tả input nó nhận — model quyết định có gọi tool hay không dựa nhiều vào phần "trả về gì" hơn là "nhận gì".
  3. Chỉ trả về đúng phần liên quan — tool nên tự làm luôn việc chọn lọc context ở phía trong nó, đừng trả cả file 2000 dòng khi model chỉ cần một function.
  4. Báo lại phần không được trả về — để model biết còn gì khác có sẵn nếu nó cần đào sâu thêm, tránh việc model tưởng đó là toàn bộ thông tin.
tools = [
    {
        "name": "read_function_source",
        "description": "Read the source code of a specific function from the codebase. Returns only the function body, not the entire file. Use when you need to understand the implementation of a function before modifying it or writing tests for it.",
        "input_schema": {
            "type": "object",
            "properties": {
                "file_path": {"type": "string", "description": "Relative path to the file"},
                "function_name": {"type": "string", "description": "Exact name of the function to read"}
            },
            "required": ["file_path", "function_name"]
        }
    },
    {
        "name": "get_type_definition",
        "description": "Get the TypeScript type or interface definition for a named type. Returns the type definition and its direct dependencies. Use when you need to understand the shape of a data structure.",
        "input_schema": {
            "type": "object",
            "properties": {
                "type_name": {"type": "string", "description": "Name of the type or interface"}
            },
            "required": ["type_name"]
        }
    },
    {
        "name": "search_codebase_pattern",
        "description": "Search for usage patterns or examples in the codebase. Returns up to 5 matching code snippets with file locations. Use when you need to understand how a pattern is used elsewhere before applying it.",
        "input_schema": {
            "type": "object",
            "properties": {
                "pattern": {"type": "string", "description": "What to search for (function name, pattern, or concept)"},
                "context_lines": {"type": "integer", "description": "Lines of context around each match", "default": 5}
            },
            "required": ["pattern"]
        }
    }
]

Ba tool trên đều tuân đúng bốn nguyên tắc: tên nói rõ chức năng (read_function_source không phải get_code), description nêu rõ giới hạn của kết quả trả về ("chỉ function body, không phải cả file"), và mỗi tool tự làm việc chọn lọc bên trong (giới hạn 5 kết quả cho search, chỉ lấy type liên quan trực tiếp cho type definition) — tránh việc "over-fetching" (lấy về nhiều hơn cần thiết) làm phình context một cách âm thầm ngay trong chính kết quả tool call.

So sánh chi phí: preload toàn bộ vs. on-demand cho một task code generation

Cách preload (3.200 token mỗi lượt gọi):
- System prompt: 300 token
- Toàn bộ nội dung 5 file trong module: 2.400 token
- Mô tả task hiện tại: 150 token
- Lịch sử conversation: 350 token

Cách on-demand (900 token cho phần lớn lượt gọi):
- System prompt: 300 token
- Mô tả task hiện tại: 150 token
- Lịch sử conversation: 350 token
- Đọc 2 function liên quan khi cần: 400 token (chỉ tính vào những lượt thực sự đọc)

Với những task model không cần đọc file (ví dụ refactor đơn giản trong đoạn code đã cung cấp sẵn), cách on-demand chỉ tốn 800 token so với 3.200 token. Với task cần đọc cả hai function, chi phí là 1.200 so với 3.200. Cách on-demand không bao giờ tốn hơn cách preload, và thường tốn ít hơn 60–75%.

Mẹo

Gắn theo dõi tool-call (tool-call tracking) vào tầng observability của bạn. Log lại tool on-demand nào được gọi và tần suất gọi. Nếu một tool cụ thể được gọi ở hơn 80% lượt tương tác, hãy xem xét đưa giá trị trả về phổ biến nhất của tool đó lên tầng dynamic context (nó rõ ràng cần một cách có thể đoán trước, không còn thực sự "on-demand"). Nếu một tool chỉ được gọi ở dưới 5% lượt tương tác, đó là dấu hiệu tool này đang hoạt động đúng như kỳ vọng của on-demand context — không lãng phí, chỉ dùng khi thật cần.


Combining All Three Layers: A Reference Architecture

Một kiến trúc context layering ở mức production kết hợp cả ba chiến lược, mỗi chiến lược đóng một vai trò bổ trợ cho nhau.

┌─────────────────────────────────────────────────────────┐
│  STATIC LAYER (system prompt, always present)           │
│  • Agent identity and role (40 tokens)                  │
│  • Hard constraints and safety rules (80 tokens)        │
│  • Output format specification (60 tokens)              │
│  Total: ~180 tokens, paid every call                    │
├─────────────────────────────────────────────────────────┤
│  DYNAMIC LAYER (assembled at request time)              │
│  • Task-type specific instructions (0–100 tokens)       │
│  • User role context (0–60 tokens)                      │
│  • Session state summary (0–200 tokens, turn > 10)      │
│  • Feature-specific context (0–150 tokens)              │
│  Total: 0–510 tokens, depends on signals                │
├─────────────────────────────────────────────────────────┤
│  ON-DEMAND LAYER (fetched by model via tools)           │
│  • File and function source code                        │
│  • Type definitions and schemas                          │
│  • Database query results                                │
│  • Documentation sections                                │
│  • API responses                                         │
│  Total: 0–2,000 tokens, depends on task needs           │
└─────────────────────────────────────────────────────────┘

So sánh với cách một agent được implement ngây thơ (naive), nơi mọi thứ đều dồn vào một tầng static duy nhất:

┌────────────────────────────────────────────────────────┐
│  SINGLE STATIC LAYER (system prompt, always present)   │
│  • Everything above, plus:                              │
│  • Task-type instructions for ALL task types            │
│  • Pre-loaded file contents "just in case"               │
│  • Full conversation history (no summarization)          │
│  Total: 3,000–8,000 tokens per call                     │
└────────────────────────────────────────────────────────┘

Kiến trúc phân tầng đúng cách mang lại mức giảm token đều đặn 50–75% so với cách nhồi tất cả vào static.

Áp dụng vào một pipeline coding agent thực tế

Hình dung một coding agent phục vụ cả engineer, QA, và PM trong cùng một tổ chức. Static layer chứa danh tính agent, quy tắc bảo mật (không log secret, không xử lý PII sai cách), và format output chung. Dynamic layer nhận signal từ request đầu vào (loại task người dùng chọn, vai trò người dùng đăng nhập, ngôn ngữ của file đang mở trong editor) rồi lắp ghép đúng block chỉ dẫn — ví dụ engineer sửa file .ts sẽ nhận block convention TypeScript, PM hỏi về roadmap sẽ nhận block "plain English output style". On-demand layer chỉ được kích hoạt khi model, trong lúc xử lý, nhận ra nó cần đọc thêm một function cụ thể hoặc tra schema database — nó gọi tool tương ứng, nhận về đúng phần cần, rồi tiếp tục.

Với thiết kế này, một câu hỏi PM đơn giản ("tính năng discount voucher có ảnh hưởng gì tới luồng thanh toán không?") có thể được trả lời với chưa tới 500 token context, trong khi một task engineer sửa lỗi phức tạp có thể tốn tới 2.000–3.000 token vì cần đọc nhiều function qua on-demand layer — nhưng đó là chi phí hợp lý, vì tính phức tạp của task tương ứng với chi phí, không phải mọi task đều trả giá như task phức tạp nhất.

Mẹo

Ghi lại kiến trúc context layering của bạn trong wiki kỹ thuật của team, kèm sơ đồ mô tả cái gì thuộc tầng nào và tại sao. Sự hiểu biết chung này ngăn từng kỹ sư riêng lẻ vô tình thêm nội dung vào tầng sai — đây là cách phổ biến nhất khiến kiến trúc layering suy thoái dần theo thời gian. Giao cho một người phụ trách rà soát lại thiết kế layering theo chu kỳ quý (mỗi 3 tháng).


Managing Layer Interactions and Conflicts

Khi kết hợp nhiều tầng context, bạn phải thiết kế rõ ràng cho cách chúng tương tác với nhau. Context từ các tầng khác nhau có thể xung đột (chỉ dẫn mâu thuẫn nhau), trùng lặp (cùng một thông tin bị lặp lại), hoặc để lộ khoảng trống (gap — thông tin quan trọng bị giả định là có sẵn ở một tầng, nhưng thực tế tầng đó không cung cấp trong tình huống cụ thể đó).

Ngăn xung đột (conflict prevention)

Thiết lập một thứ tự ưu tiên rõ ràng: chỉ dẫn ở static layer luôn được ưu tiên hơn dynamic layer, và dynamic layer được ưu tiên hơn on-demand context. Khi model có khả năng gặp các chỉ dẫn tưởng chừng mâu thuẫn từ các tầng khác nhau, hãy đưa một quy tắc giải quyết xung đột rõ ràng vào chính static layer, ví dụ: "Nếu chỉ dẫn riêng của task mâu thuẫn với các ràng buộc chung này, hãy tuân theo ràng buộc chung."

Ngăn trùng lặp (duplication prevention)

Xây dựng các block dynamic context với ý thức rõ static layer đã cung cấp gì. Nếu static layer đã ghi "Language: TypeScript", block dynamic dành cho task TypeScript không nên lặp lại điều này — nó nên bổ sung thêm hướng dẫn riêng cho TypeScript áp dụng cho loại task hiện tại, thay vì nói lại điều đã biết.

Ngăn khoảng trống (gap prevention)

Kiểm thử từng tầng riêng lẻ và kiểm thử khi kết hợp. Với mỗi loại task agent xử lý, xác nhận rằng tổ hợp static + dynamic liên quan cung cấp đủ mọi thứ model cần để đưa ra quyết định đúng, không phải viện đến tool call chỉ để lấy những thông tin cơ bản lẽ ra nên có sẵn.

Tự động phát hiện xung đột/trùng lặp giữa các tầng

Việc rà soát trùng lặp giữa static prompt và các dynamic block bằng mắt thường rất dễ bỏ sót, đặc biệt khi có nhiều người cùng chỉnh sửa prompt theo thời gian. Một công cụ đơn giản dựa trên so khớp từ khóa (keyword overlap) có thể tự động cảnh báo những trường hợp nghi ngờ:

def detect_context_overlap(static_prompt: str, dynamic_blocks: dict[str, str]) -> list[str]:
    """Identify information repeated between static and dynamic layers."""
    overlaps = []
    static_keywords = extract_key_terms(static_prompt)

    for block_name, block_content in dynamic_blocks.items():
        block_keywords = extract_key_terms(block_content)
        overlap = static_keywords.intersection(block_keywords)
        if len(overlap) > 3:  # Threshold: >3 shared key terms suggests duplication
            overlaps.append(f"Potential overlap between static prompt and '{block_name}': {overlap}")

    return overlaps

Hàm extract_key_terms (không show ở đây, nhưng có thể implement bằng cách loại stop word rồi lấy noun/technical term xuất hiện nổi bật, hoặc dùng TF-IDF đơn giản) đóng vai trò trích xuất "từ khóa quan trọng" từ mỗi đoạn text để so khớp. Ngưỡng > 3 chỉ là điểm khởi đầu hợp lý — bạn nên tinh chỉnh dựa trên dữ liệu thật của agent mình, vì với static prompt dài, ngưỡng này có thể cần cao hơn để tránh báo động giả (false positive) quá nhiều.

Bạn có thể mở rộng logic này để phát hiện luôn cả gap: chạy hàm này ngược lại — so khớp keyword của một block dynamic với danh sách các câu hỏi thường gặp (FAQ) của loại task đó, nếu không có overlap nào, đó là dấu hiệu block dynamic có thể đang thiếu thông tin cần thiết.

Mẹo

Chạy công cụ phát hiện xung đột context ngay trong pipeline CI, là một phần của quy trình review khi có thay đổi vào system prompt. Khi một developer sửa static prompt hoặc thêm một dynamic block mới, một bước kiểm tra tự động gắn cờ khả năng trùng lặp hoặc xung đột sẽ bắt lỗi trước khi nó lên production — nơi mà lỗi này sẽ âm thầm làm phình token usage ở mọi lượt gọi, rất khó phát hiện bằng review thủ công thông thường.