·

Một lệnh gọi LLM đơn lẻ (single-shot) có chi phí token dễ ước tính: bạn biết trước prompt dài bao nhiêu, output tối đa bao nhiêu, và bill cuối cùng nằm trong một khoảng dự đoán được. Agentic loop — vòng lặp mà agent tự lên kế hoạch, gọi tool, đọc kết quả rồi lại lên kế hoạch tiếp — phá vỡ hoàn toàn giả định đó. Chi phí không cộng dồn theo cấp số cộng mà theo cấp số nhân: mỗi lượt lặp không chỉ tốn token cho riêng nó, mà còn kéo theo toàn bộ lịch sử của các lượt trước đó, và lỗi thiết kế nhỏ ở một chỗ có thể khiến hoá đơn của một task lẽ ra chỉ tốn vài nghìn đồng bỗng dưng "leo" lên gấp 20-40 lần. Bài này bóc tách chính xác token trong một agentic loop chảy về đâu, chỉ ra bốn nguyên nhân khiến chi phí nhân lên, và đưa ra các pattern kỹ thuật cụ thể để khống chế nó — áp dụng được cho dù bạn là người viết code cho agent, người thiết kế test case cho agent, hay người quản lý budget vận hành agent trong sản phẩm.


Cấu Trúc Của Một Agentic Loop — Token Đi Đâu Về Đâu

Về bản chất, một agentic loop chạy theo chu trình Plan → Execute → Observe → Plan lặp đi lặp lại cho đến khi agent tự quyết định đã hoàn thành nhiệm vụ (hoặc bị chặn bởi một giới hạn nào đó). Điểm mấu chốt khiến vòng lặp này "ăn" token dữ dội hơn nhiều so với một lần gọi API thông thường: tại mỗi lượt lặp, toàn bộ context tích lũy từ đầu task phải được gửi lại từ đầu, vì LLM không có trạng thái (stateless) — nó không "nhớ" cuộc trò chuyện trước, mọi thứ phải nằm trong request hiện tại.

Hãy bóc tách một lượt lặp (iteration) điển hình của một agent sửa lỗi (debugging agent) để thấy token đi đâu:

Lượt lặp thứ N của một debugging agent
──────────────────────────────────────────────────────────
Input gửi lên LLM (request):
  system prompt                         ~800 token
  toàn bộ history của N-1 lượt trước     ~12,000 token  ← tích lũy
  tool schema (định nghĩa các tool)      ~600 token
  ─────────────────────────────────────────────
  Tổng input                            ~13,400 token

Output LLM trả về (response):
  reasoning (suy luận trước khi hành động) ~300 token
  tool call (gọi tool, kèm arguments)      ~150 token
  ─────────────────────────────────────────────
  Tổng output                              ~450 token

Sau khi tool thực thi xong:
  tool result được nối vào history        ~2,000 token  ← có thể phình rất lớn

Nhìn vào bảng trên, ba điều đáng chú ý:

  1. Input luôn lớn hơn output rất nhiều. Ở agentic loop, phần trả tiền đắt nhất không phải là câu trả lời của model, mà là toàn bộ "hành lý" context phải mang theo mỗi lượt.
  2. History chỉ có tăng, gần như không giảm — trừ khi bạn chủ động can thiệp. Đây là nguồn gốc của phần lớn chi phí ẩn.
  3. Reasoning token (token dùng để model "suy nghĩ" trước khi hành động, ví dụ chain-of-thought hoặc extended thinking) cũng tính vào chi phí, dù người dùng không thấy trực tiếp nội dung đó.

Với một task cần 15 lượt lặp mà mỗi lượt history tăng thêm ~2.000 token, tổng input tích lũy qua cả task có thể lên tới hàng trăm nghìn token — trong khi giá trị thực sự tạo ra (câu trả lời cuối, đoạn code được sửa) chỉ chiếm một phần rất nhỏ trong số đó.

Mẹo

Trước khi tối ưu bất cứ thứ gì, hãy đo trước khi sửa: log token count (input/output) của từng lượt lặp ra một bảng riêng (không cần công cụ phức tạp, một dòng log JSON mỗi lượt là đủ). Chỉ khi nhìn thấy đường cong tăng của input theo số lượt, bạn mới biết mình đang tối ưu đúng chỗ hay không — rất nhiều team tối ưu sai chỗ (ví dụ rút ngắn system prompt) trong khi thủ phạm thực sự là tool result không được compress.


Bốn Yếu Tố Nhân Token Trong Agentic Loop

Chi phí token của agentic loop không tăng tuyến tính mà tăng theo kiểu cộng gộp (compounding), vì bốn yếu tố sau tương tác và khuếch đại lẫn nhau:

1. Nhân theo số lượt lặp (turn count multiplication). Mỗi lượt lặp thêm là một lần gọi API đầy đủ, với toàn bộ context phải gửi lại. Một task cần 20 lượt sẽ tốn tiền theo cách hoàn toàn khác một task cần 5 lượt — không phải gấp 4 lần, mà gấp nhiều hơn, vì context ở lượt 20 đã tích lũy dày hơn lượt 5 rất nhiều.

2. Tool-output phình to (tool-output bloat). Khi agent gọi tool — đọc file, query database, gọi API bên ngoài, chạy test — kết quả trả về thường là dữ liệu thô: toàn bộ nội dung file, toàn bộ response JSON, toàn bộ log build. Agent chỉ cần một phần nhỏ trong đó (ví dụ 3 dòng lỗi trong log build dài 5.000 dòng), nhưng nếu không xử lý, toàn bộ 5.000 dòng đó bị nhét vào history.

3. Tái truyền history (history re-transmission). Đây là hệ quả trực tiếp của việc LLM không có trạng thái: tool result phình to ở lượt 3 không chỉ tốn tiền một lần ở lượt 3, mà còn bị gửi lại nguyên vẹn ở lượt 4, 5, 6... cho đến hết task. Một đoạn log 5.000 dòng "vô tình" bị lưu vào history ở lượt đầu có thể bị trả tiền lại 15 lần nếu task còn 15 lượt nữa.

4. Phân nhánh qua sub-agent (sub-agent fan-out). Khi agent chính giao việc cho các sub-agent (ví dụ một agent điều phối gọi 4 sub-agent con để xử lý 4 file riêng biệt), mỗi sub-agent lại tự chạy agentic loop riêng của nó, với system prompt riêng, context riêng. Chi phí không cộng thêm một lượng cố định — nó nhân theo số sub-agent được sinh ra, và nếu không kiểm soát, một agent điều phối "hào phóng" có thể sinh ra hàng chục sub-agent cho một task tưởng chừng đơn giản.

Bốn yếu tố này không độc lập — chúng khuếch đại nhau. Tool-output bloat (yếu tố 2) trở nên nguy hiểm gấp nhiều lần chính vì history re-transmission (yếu tố 3) khiến nó bị trả tiền lại mỗi lượt. Và nếu agent đó lại là một sub-agent trong một cấu trúc fan-out (yếu tố 4), thì lỗi đó bị nhân bản ra N lần tương ứng số sub-agent.

Anti-pattern: lưu nguyên tool output thô vào history

def execute_tool_call(tool_name, args, history):
    result = call_tool(tool_name, args)  # ví dụ: đọc file, chạy test, gọi API

    # Sai lầm: nhồi thẳng dữ liệu thô vào history,
    # dữ liệu này sẽ được gửi lại NGUYÊN VẸN ở MỌI lượt lặp tiếp theo
    history.append({
        "role": "tool",
        "tool_name": tool_name,
        "content": result,  # có thể là log dài 5,000 dòng, response JSON vài MB...
    })
    return history

Với pattern trên, nếu result là log build dài 5.000 dòng nhưng agent chỉ cần biết "build fail ở dòng nào, lỗi gì", thì 4.995 dòng còn lại là token lãng phí — và bị trả tiền lại ở mọi lượt lặp sau đó cho đến khi task kết thúc hoặc history bị cắt.

Pattern tốt hơn: lưu tham chiếu đã compress (compressed reference)

def execute_tool_call(tool_name, args, history, blob_store):
    raw_result = call_tool(tool_name, args)

    # Lưu dữ liệu thô ra ngoài history (blob store / file tạm),
    # KHÔNG gửi lại cho LLM ở các lượt sau
    ref_id = blob_store.save(raw_result)

    # Chỉ trích xuất phần liên quan (ví dụ: dòng lỗi, exit code)
    # để đưa vào history — đây là phần model thực sự cần để ra quyết định tiếp
    compressed_summary = extract_relevant_snippet(
        raw_result, max_lines=20
    )

    history.append({
        "role": "tool",
        "tool_name": tool_name,
        "content": compressed_summary,
        "full_result_ref": ref_id,  # agent có thể "gọi lại" nếu thật sự cần chi tiết
    })
    return history

Sự khác biệt cốt lõi: dữ liệu thô vẫn tồn tại (không mất thông tin), nhưng nó không còn nằm trong context bị gửi lại mỗi lượt. Agent chỉ "trả tiền" cho phần thực sự cần để ra quyết định, và có đường để truy xuất bản đầy đủ (full_result_ref) nếu về sau thực sự cần đến — ví dụ khi người dùng hỏi lại chi tiết log.

Mẹo

Khi review code của một agent, hãy tự hỏi: "Nếu tool này trả về một payload lớn gấp 100 lần bình thường, hệ thống có sập ngân sách token không?" Nếu câu trả lời là có, đó là dấu hiệu bạn đang mắc anti-pattern lưu raw output — và nên xử lý ngay ở bước thiết kế tool wrapper, trước khi nó thành thói quen lan ra toàn bộ codebase agent.


Compress Tool Result — Vô Hiệu Hoá Yếu Tố Nhân Lớn Nhất

Trong bốn yếu tố ở trên, tool-output bloat kết hợp với history re-transmission thường là "cặp bài trùng" gây tốn kém nhất trong thực tế — vì nó xảy ra ở gần như mọi lượt lặp có gọi tool (đọc file, query, gọi API), và mỗi lần xảy ra lại bị nhân bản qua các lượt sau. Compress tool result đúng cách là biện pháp có tỷ lệ lợi ích/công sức (ROI) cao nhất trong toàn bộ việc tối ưu token cho agentic loop.

Có ba chiến lược compress thường dùng, có thể phối hợp với nhau:

1. Trích lọc theo cấu trúc (structured extraction). Với output có cấu trúc rõ (JSON, log có format, kết quả test), viết một bước xử lý trung gian để chỉ giữ field cần thiết. Ví dụ: response từ API tìm kiếm trả về 50 field mỗi item, nhưng agent chỉ cần title, url, score — cắt bỏ 47 field còn lại trước khi đưa vào history.

2. Tóm tắt bằng LLM phụ (sub-summarization). Với output dạng văn bản tự do dài (log lỗi, nội dung file lớn), dùng một lệnh gọi LLM rẻ (model nhỏ, nhanh) để tóm tắt trước khi đưa vào history của agent chính. Chi phí của lệnh gọi tóm tắt này thường nhỏ hơn rất nhiều so với chi phí trả lại nguyên văn dữ liệu thô qua nhiều lượt lặp.

3. Cắt theo ngưỡng và đánh dấu bị cắt (truncation with markers). Cách đơn giản và rẻ nhất: giới hạn số dòng/ký tự tối đa, và chèn rõ dấu hiệu "đã cắt N dòng" để agent biết dữ liệu không đầy đủ và có thể chủ động yêu cầu xem thêm nếu cần, thay vì âm thầm giả định đã thấy toàn bộ.

Việc chọn chiến lược nào phụ thuộc vào loại tool: đọc file mã nguồn nên dùng structured extraction (chỉ lấy hàm/class liên quan); log CI/CD nên dùng truncation + trích dòng lỗi; kết quả research trên web nên dùng sub-summarization.

Ví dụ tích hợp: compress tool result trong một agentic loop

def agentic_loop(task, max_iterations=15):
    history = []
    for iteration in range(max_iterations):
        # 1. Gửi toàn bộ history + system prompt lên LLM để lấy quyết định tiếp theo
        response = llm_call(system_prompt=SYSTEM_PROMPT, history=history)

        if response.is_final_answer:
            return response.content

        # 2. LLM quyết định gọi tool
        tool_call = response.tool_call
        raw_result = call_tool(tool_call.name, tool_call.args)

        # 3. Compress TRƯỚC khi ghi vào history — bước bắt buộc, không phải tùy chọn
        compressed = compress_tool_result(
            tool_name=tool_call.name,
            raw_result=raw_result,
            strategy=COMPRESSION_STRATEGY.get(tool_call.name, "truncate"),
        )

        history.append({"role": "assistant", "tool_call": tool_call})
        history.append({"role": "tool", "content": compressed})

    return "Task chưa hoàn thành sau khi đạt số lượt lặp tối đa."


def compress_tool_result(tool_name, raw_result, strategy):
    if strategy == "structured_extract" and tool_name == "search_api":
        # Chỉ giữ field cần thiết, bỏ metadata thừa
        return [
            {"title": r["title"], "url": r["url"], "score": r["score"]}
            for r in raw_result["items"][:5]
        ]

    if strategy == "sub_summarize" and tool_name == "read_large_file":
        # Gọi model nhỏ, rẻ để tóm tắt trước khi đưa vào history của agent chính
        return llm_call(
            model="fast-small-model",
            prompt=f"Tóm tắt nội dung sau trong 100 từ, giữ lại các định nghĩa hàm quan trọng:\n{raw_result}",
        ).content

    # Mặc định: cắt theo ngưỡng, luôn đánh dấu rõ nếu có cắt
    max_chars = 2000
    if len(raw_result) > max_chars:
        return raw_result[:max_chars] + f"\n...[đã cắt {len(raw_result) - max_chars} ký tự]"
    return raw_result

Điểm quan trọng trong đoạn code trên: bước compress không phải là "dọn dẹp sau" mà nằm ngay trong luồng chính của loop, thực thi trước khi bất cứ thứ gì được ghi vào history. Đây là nguyên tắc thiết kế cốt lõi: compress tại nguồn, không compress khi đã quá muộn.

Mẹo

Với QA/product manager không viết code trực tiếp cho agent: khi review một agent framework hoặc đánh giá vendor, hãy hỏi thẳng "tool result của các bạn được compress bằng cơ chế nào, và có log lại tỷ lệ compress (bao nhiêu % dữ liệu thô bị cắt) không?" — đây là câu hỏi lọc rất nhanh giữa một hệ thống agent được thiết kế nghiêm túc về chi phí và một hệ thống chỉ mới ở giai đoạn demo.


Quản Lý History — Ngăn Chặn Tăng Trưởng Không Giới Hạn

Dù đã compress tool result tốt, history vẫn có xu hướng tăng dần theo số lượt lặp — vì mỗi lượt đều thêm ít nhất một cặp "assistant reasoning + tool result" mới. Nếu không có chiến lược quản lý history, một task dài (nhiều lượt) sẽ sớm hay muộn chạm giới hạn context window, hoặc đơn giản là tốn tiền vô lý cho những thông tin không còn liên quan (ví dụ: log của lượt 2 không còn ý nghĩa gì ở lượt 18).

Ba chiến lược quản lý history phổ biến, xếp theo độ phức tạp tăng dần:

1. Sliding window (cửa sổ trượt). Chỉ giữ N lượt lặp gần nhất trong history đầy đủ, các lượt cũ hơn bị loại bỏ hoàn toàn hoặc tóm tắt gộp thành một entry duy nhất. Đơn giản, dễ triển khai, nhưng rủi ro mất thông tin quan trọng từ đầu task (ví dụ: yêu cầu gốc của người dùng nếu nó không được "pin" riêng).

2. Rolling summary (tóm tắt cuộn). Sau mỗi K lượt lặp, dùng một lệnh gọi LLM để tóm tắt toàn bộ những gì đã xảy ra thành một đoạn ngắn, thay thế các entry chi tiết cũ bằng đoạn tóm tắt đó. History luôn có kích thước tương đối ổn định theo thời gian, dù task kéo dài bao nhiêu lượt.

3. Structured memory (bộ nhớ có cấu trúc). Thay vì giữ history dạng chuỗi hội thoại tuyến tính, tách riêng các "sự kiện" quan trọng (file đã sửa, quyết định đã chốt, lỗi đã gặp) thành một cấu trúc dữ liệu riêng (ví dụ danh sách key-value hoặc bảng trạng thái), và chỉ đưa cấu trúc này vào prompt — không đưa lại toàn bộ log hội thoại. Đây là cách hiệu quả nhất về token nhưng cũng tốn công thiết kế nhất, vì cần xác định trước "loại sự kiện nào cần nhớ".

Trong thực tế, các agent framework trưởng thành (Claude Code, các agent điều phối trong CI/CD) thường kết hợp cả ba: structured memory cho các fact quan trọng (đường dẫn file, trạng thái build), rolling summary cho phần diễn giải quá trình, và sliding window làm lớp bảo vệ cuối cùng khi context vẫn phình quá giới hạn dù đã áp hai lớp trên.

Mẹo

Đừng tóm tắt hoặc cắt bỏ yêu cầu gốc của người dùng hay các ràng buộc cứng (constraint) đã được xác nhận (ví dụ: "không sửa file config production"). Hãy "pin" (giữ cố định, không bị cuốn theo cơ chế cắt/tóm tắt) các thông tin này ở một vị trí riêng trong prompt — tách biệt khỏi phần history bị quản lý động. Mất một dòng log là chấp nhận được; mất một ràng buộc an toàn có thể gây hậu quả nghiêm trọng hơn nhiều so với số tiền tiết kiệm được.


Sub-agent Delegation — Khoanh Vùng Chi Phí Qua Phân Rã Nhiệm Vụ

Khi một task đủ lớn và có thể chia thành các phần độc lập (ví dụ: "review 5 file trong pull request" chia thành 5 phần độc lập theo file), một pattern phổ biến là để agent điều phối (orchestrator) giao mỗi phần cho một sub-agent riêng, mỗi sub-agent chạy agentic loop của chính nó rồi trả kết quả tóm tắt về cho agent điều phối.

Nghe có vẻ nghịch lý — tạo ra nhiều agent hơn để tiết kiệm token? — nhưng lợi ích nằm ở chỗ: mỗi sub-agent có context riêng, không phải mang theo history của các phần việc không liên quan đến nó. Nếu không phân rã, agent điều phối sẽ phải tự đọc và giữ trong history toàn bộ nội dung của cả 5 file cùng lúc, khiến context của nó phình to và mọi lượt lặp sau đều phải trả tiền cho cả 5 file — dù đang xử lý file thứ 3.

Với sub-agent delegation, context của mỗi sub-agent chỉ chứa những gì liên quan đến phần việc của nó, và quan trọng hơn: kết quả trả về từ sub-agent cho orchestrator phải là bản tóm tắt, không phải toàn bộ history bên trong của sub-agent đó. Đây chính là điểm dễ mắc lỗi — nhiều implementation vô tình để orchestrator "hứng" lại nguyên history đầy đủ từ sub-agent, biến pattern tiết kiệm thành pattern tốn kém gấp đôi.

def orchestrator_delegate(files_to_review):
    findings = []
    for file_path in files_to_review:
        # Mỗi sub-agent nhận một context RIÊNG, hẹp, chỉ chứa file cần xử lý —
        # không mang theo history của orchestrator hay các file khác
        sub_agent_context = {
            "task": f"Review code quality và tìm bug tiềm ẩn trong file: {file_path}",
            "file_content": read_file(file_path),
        }

        # Sub-agent chạy agentic loop RIÊNG (có thể vài lượt lặp bên trong)
        sub_agent_result = run_sub_agent(sub_agent_context, max_iterations=5)

        # QUAN TRỌNG: chỉ lấy bản tóm tắt kết quả, KHÔNG kéo nguyên history
        # nội bộ của sub-agent (reasoning, tool call, tool result bên trong nó)
        # về context của orchestrator
        findings.append({
            "file": file_path,
            "summary": sub_agent_result.summary,   # vài trăm token
            "severity": sub_agent_result.severity,
            # sub_agent_result.full_history bị bỏ qua có chủ đích
        })

    return findings

Chi phí của pattern này = chi phí chạy 5 agentic loop nhỏ, độc lập, song song được — thay vì 1 agentic loop lớn với context tích lũy cả 5 file. Với các phần việc thực sự độc lập, tổng chi phí (và cả thời gian, nếu chạy song song) thường thấp hơn đáng kể. Nhưng nếu các phần việc phụ thuộc lẫn nhau (ví dụ sửa file A cần biết agent đã sửa gì ở file B), sub-agent delegation sẽ tạo ra chi phí giao tiếp qua lại (round-trip) giữa các sub-agent và orchestrator — lúc này chia nhỏ sai cách có thể làm chi phí tăng thêm chứ không giảm.

Mẹo

Chỉ áp dụng sub-agent delegation khi các phần việc thực sự độc lập hoặc phụ thuộc rất lỏng. Trước khi thiết kế cấu trúc fan-out, hãy vẽ sơ đồ phụ thuộc giữa các phần việc — nếu sơ đồ đó có nhiều mũi tên hai chiều, phân rã thành sub-agent nhiều khả năng sẽ khiến chi phí tăng do phải đồng bộ qua lại, không giảm.


Early Termination Và Exit Condition — Biết Khi Nào Nên Dừng

Yếu tố nhân token nguy hiểm nhất trong thực tế không phải luôn là tool-output bloat hay sub-agent fan-out, mà đơn giản là: agent không biết khi nào nên dừng. Một agentic loop không có exit condition (điều kiện thoát) rõ ràng có xu hướng tiếp tục lặp — thử cách khác, kiểm tra lại, "để chắc chắn hơn" — cho đến khi chạm giới hạn lượt lặp tối đa (nếu có) hoặc tệ hơn, chạy đến khi hết ngân sách mà vẫn chưa xong.

Ba loại exit condition nên được thiết kế tường minh ngay từ đầu, không để agent tự "cảm nhận":

1. Success condition (điều kiện thành công). Tiêu chí rõ ràng, có thể kiểm tra được bằng code — không phải để LLM tự đánh giá mơ hồ. Ví dụ: "tất cả test case pass" (kiểm tra được qua exit code), không phải "tôi nghĩ code đã đúng" (LLM tự nhận định, dễ sai).

2. Budget exhaustion (cạn ngân sách). Giới hạn cứng về số lượt lặp, tổng token đã dùng, hoặc thời gian chạy. Đây là lớp bảo vệ cuối cùng, đảm bảo agent không chạy vô hạn dù logic success condition có lỗi.

3. Stagnation detection (phát hiện bế tắc). Nếu N lượt lặp liên tiếp không tạo ra tiến triển nào có thể đo được (ví dụ: agent thử sửa cùng một dòng code 3 lần với các cách gần giống nhau, hoặc lặp lại chính xác một tool call đã gọi trước đó), nên chủ động dừng và báo cáo bế tắc, thay vì để agent tự lặp đến khi hết ngân sách.

Ví dụ: logic exit condition trong thực tế

def agentic_loop_with_exit_conditions(task, max_iterations=20, max_tokens=100_000):
    history = []
    total_tokens_used = 0
    recent_tool_calls = []  # dùng để phát hiện lặp lại (stagnation)

    for iteration in range(max_iterations):
        response = llm_call(system_prompt=SYSTEM_PROMPT, history=history)
        total_tokens_used += response.token_usage.total

        # --- Success condition: kiểm tra bằng code, không suy đoán ---
        if response.is_final_answer and run_verification(response.content):
            return {"status": "success", "result": response.content, "iterations": iteration + 1}

        # --- Budget exhaustion: chặn cứng theo token và số lượt ---
        if total_tokens_used >= max_tokens:
            return {"status": "budget_exceeded", "iterations": iteration + 1, "tokens": total_tokens_used}

        tool_call = response.tool_call
        recent_tool_calls.append((tool_call.name, tool_call.args))

        # --- Stagnation detection: 3 tool call gần nhất giống nhau => bế tắc ---
        if len(recent_tool_calls) >= 3 and len(set(map(str, recent_tool_calls[-3:]))) == 1:
            return {
                "status": "stagnation_detected",
                "reason": f"Lặp lại tool call giống nhau 3 lần: {tool_call.name}",
                "iterations": iteration + 1,
            }

        raw_result = call_tool(tool_call.name, tool_call.args)
        compressed = compress_tool_result(tool_call.name, raw_result, strategy="truncate")
        history.append({"role": "assistant", "tool_call": tool_call})
        history.append({"role": "tool", "content": compressed})

    return {"status": "max_iterations_reached", "iterations": max_iterations}

Lưu ý cách run_verification() được gọi bằng code thực (chạy test, kiểm tra exit code, so khớp schema) chứ không dựa vào việc LLM tự nói "tôi đã xong". Đây là khác biệt giữa một exit condition đáng tin cậy và một exit condition chỉ mang tính hình thức — rất nhiều agent "chạy quá đà" chính vì success condition của nó thực chất chỉ là câu trả lời tự tin của model, không phải một phép kiểm tra độc lập.

Mẹo

Khi thiết kế test case hoặc tiêu chí chấp nhận (acceptance criteria) cho một agent (đây là phần QA nên đặc biệt quan tâm), luôn yêu cầu có ít nhất một test case cố ý đưa agent vào tình huống bế tắc (ví dụ: yêu cầu sửa một lỗi không tồn tại, hoặc test luôn fail vì lý do ngoài khả năng sửa của agent). Một agent thiết kế tốt phải phát hiện được bế tắc và dừng có kiểm soát trong vài lượt lặp — nếu nó cứ lặp đến khi chạm max_iterations, đó là dấu hiệu stagnation detection chưa được cài đặt đúng, và chi phí thực tế khi vận hành sẽ cao hơn nhiều so với con số đo được trong môi trường test nhẹ nhàng.