·

Khi bạn xây dựng một AI agent, phần lớn thời gian và ngân sách token không nằm ở câu trả lời cuối cùng mà nằm ở quá trình agent gọi tool (tool call) qua lại với hệ thống. Mỗi lần agent gọi một custom tool — ví dụ tool đọc file, tool truy vấn database, tool gọi API GitHub — thì cả tool definition (định nghĩa tool, mô tả schema và tham số cho model biết), tham số đầu vào (input parameters), và kết quả trả về (tool output) đều được nạp vào context window và tính vào token.

Vấn đề là hầu hết các nhóm phát triển thiết kế tool theo tư duy "REST API cho con người" — tận dụng lại luôn response của một API nội bộ hoặc third-party, giữ đầy đủ field, giữ format đẹp để dev debug dễ. Điều đó hợp lý khi người dùng cuối là con người, nhưng khi người "tiêu thụ" tool output là một LLM, mọi field dư thừa, mọi khoảng trắng thừa, mọi tên field dài dòng đều là token bị đốt một cách vô ích — và tệ hơn, nó còn làm loãng context, khiến model dễ bị phân tâm (distraction) hoặc trả lời sai vì phải lọc thông tin không liên quan trong hàng trăm field.

Bài này đi sâu vào 5 nguyên tắc thiết kế custom tool theo hướng tối ưu token: từ schema, tham số, output, đến độ chi tiết (granularity) của tool và cách thiết kế error handling. Đây là kỹ năng bắt buộc phải có với bất kỳ ai đang xây dựng agent dùng tool — từ engineer viết code, QA kiểm thử tool contract, đến product manager định nghĩa yêu cầu tool.

Nguyên tắc 1: Thiết kế Schema — Tối giản mặc định

Tool schema (định dạng JSON Schema mô tả tool cho model, gồm tên, mô tả, và cấu trúc tham số) được gửi kèm trong system prompt hoặc tools definition của mọi request — dù model có gọi tool đó hay không trong turn hiện tại. Nghĩa là nếu bạn có 10 tool, cả 10 schema đều bị tính token ở mọi lượt hội thoại, không chỉ lượt gọi tool.

Sai lầm phổ biến nhất là copy nguyên schema từ OpenAPI spec hoặc từ ORM model của database, rồi dán thẳng vào tool definition. Kết quả là description dài dòng, ví dụ mẫu (examples) lặp lại thông tin đã có trong description, và field enum liệt kê toàn bộ giá trị có thể trong khi chỉ cần liệt kê 3-4 giá trị phổ biến kèm ghi chú "hoặc giá trị hợp lệ khác theo hệ thống".

Ví dụ một schema viết theo tư duy "OpenAPI đầy đủ":

{
  "name": "search_products",
  "description": "This tool allows you to search for products in the e-commerce catalog system. You can specify a search query string, and optionally filter by category, price range, availability status, and sort the results. The tool will return a paginated list of matching products with full details. Use this when the user asks about finding products, checking prices, or browsing the catalog.",
  "parameters": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "The search query string that will be used to search for products in the catalog based on their title and description fields"
      },
      "category_filter": {
        "type": "string",
        "description": "Optional. Filter results to only include products belonging to this category. Valid categories include: electronics, clothing, home-and-garden, books, toys, sports-and-outdoors, beauty, automotive, groceries, office-supplies"
      }
    }
  }
}

Cùng ý tưởng đó, viết gọn lại:

{
  "name": "search_products",
  "description": "Search product catalog by keyword. Optional filters: category, price range.",
  "parameters": {
    "type": "object",
    "properties": {
      "query": { "type": "string", "description": "Search keyword" },
      "category": { "type": "string", "description": "e.g. electronics, clothing" }
    }
  }
}

Bản gọn giảm khoảng 55-60% token cho schema này, nhưng model vẫn hiểu đúng ý nghĩa và cách dùng — bởi vì model đủ thông minh để suy luận từ tên field và ví dụ ngắn, không cần văn xuôi giải thích từng chữ. Quy tắc chung: description chỉ cần trả lời "tool này làm gì" và "khi nào dùng", không cần giải thích lại cách hoạt động nội bộ.

Mẹo

  • Viết description bằng cụm từ ngắn (fragment), không viết câu hoàn chỉnh có chủ ngữ-vị ngữ dư thừa. "Search product catalog by keyword" tốt hơn "This tool searches the product catalog using a keyword you provide".
  • Với enum dài, chỉ nêu 3-5 giá trị đại diện kèm "e.g." (ví dụ), đừng liệt kê hết nếu model không cần validate chính xác giá trị.
  • Định kỳ đo token của toàn bộ tool definitions bằng tokenizer (ví dụ tiktoken hoặc token counter của provider) để biết "chi phí cố định" mỗi request đang gánh bao nhiêu, từ đó có mục tiêu cắt giảm rõ ràng.
  • Loại bỏ các trường không bao giờ được model dùng tới (như internal_debug_flag, _metadata) ra khỏi schema công khai cho model, dù backend vẫn giữ chúng.

Nguyên tắc 2: Thiết kế Parameter — Flat, có kiểu, tối thiểu

Tham số (parameter) của tool call là phần mà model phải tự sinh ra ở mỗi lần gọi tool — nghĩa là mỗi ký tự dư trong parameter không chỉ tốn token khi model "viết" nó, mà còn tăng khả năng model mắc lỗi cú pháp (đặc biệt với schema lồng nhiều cấp).

Ba lỗi thiết kế parameter thường gặp:

  1. Lồng cấp quá sâu (deeply nested objects): filter.price.range.min thay vì min_price. Object lồng nhau buộc model phải sinh ra nhiều dấu ngoặc, nhiều key trung gian không mang thông tin.
  2. Kiểu dữ liệu mơ hồ: dùng string cho mọi thứ (kể cả số, boolean, ngày) khiến model phải "đoán" format, dẫn đến lỗi parse ở phía server và phải retry — tốn gấp đôi token.
  3. Tham số thừa "để dự phòng": thêm các field optional "cho chắc" như request_id, client_version, debug_mode vào schema tool trong khi model không cần biết và không nên tự sinh ra các giá trị này.

So sánh cụ thể — tool tạo task trong hệ thống quản lý công việc:

// Bad: nested, kiểu dữ liệu mơ hồ, dư tham số
{
  "task": {
    "info": {
      "title": "Fix login bug",
      "meta": { "priority_level": "high", "due": "2026-07-15T00:00:00.000Z" }
    }
  },
  "options": { "notify": "true", "client_source": "agent-v2" }
}
// Good: flat, đúng kiểu, tối thiểu
{
  "title": "Fix login bug",
  "priority": "high",
  "due_date": "2026-07-15",
  "notify": true
}

Bản "good" ngắn hơn khoảng 45% token và ít khả năng model sinh sai cú pháp hơn nhiều, vì mỗi tham số nằm ở cấp cao nhất (flat), đúng kiểu dữ liệu gốc (boolean là true, không phải string "true"), và không có field nào model phải tự bịa giá trị (như client_source).

Một mẹo nâng cao: với các trường ngày giờ, chỉ yêu cầu độ chính xác model thực sự cần. Nếu hệ thống không quan tâm đến giờ:phút:giây, đừng bắt model sinh full ISO 8601 timestamp (2026-07-15T00:00:00.000Z) — chỉ cần 2026-07-15. Bạn xử lý chuẩn hóa ở backend, không đẩy việc đó lên model.

Mẹo

  • Áp dụng nguyên tắc "flatten": nếu một object lồng chỉ có 1-2 field bên trong và không tái sử dụng ở tool khác, hãy đưa field đó lên cấp cao nhất (top-level).
  • Luôn dùng đúng kiểu JSON (number, boolean, string, array) — không "stringify" mọi thứ. Việc này giảm token và giảm lỗi parse ở backend.
  • Đừng thêm parameter mà model không có thông tin để điền chính xác (như ID nội bộ, timestamp hệ thống) — hãy để backend tự sinh giá trị đó.
  • Giới hạn số parameter bắt buộc (required) ở mức tối thiểu cần thiết để tool hoạt động đúng; các phần còn lại nên có default hợp lý ở backend.

Nguyên tắc 3: Thiết kế Output — Trả về đúng cái model cần, không hơn

Đây là nguyên tắc có tác động token lớn nhất trong toàn bộ vòng đời tool call, vì output của tool thường lớn hơn gấp nhiều lần input, và nó bị nạp thẳng vào context của model để xử lý bước tiếp theo.

Hãy xét một ví dụ thực tế: bạn xây một tool get_github_issue để agent đọc chi tiết một issue trên GitHub. Nhiệm vụ của model chỉ là: đọc issue number, title, body, labels, và status để trả lời câu hỏi người dùng hoặc quyết định hành động tiếp theo (ví dụ đóng issue, gán label mới).

Nhưng nếu implementation "naive" (ngây thơ) chỉ là forward thẳng response gốc từ GitHub REST API, model sẽ nhận về hơn 150 field — bao gồm thông tin về user (avatar_url, gravatar_id, organizations_url, followers_url...), milestone, pull_request liên kết, reactions (từng loại emoji), các _links HATEOAS, timestamps ở nhiều định dạng khác nhau, v.v.

// Bad: forward toàn bộ GitHub API response
{
  "url": "https://api.github.com/repos/acme/webapp/issues/482",
  "repository_url": "https://api.github.com/repos/acme/webapp",
  "labels_url": "https://api.github.com/repos/acme/webapp/issues/482/labels{/name}",
  "comments_url": "https://api.github.com/repos/acme/webapp/issues/482/comments",
  "events_url": "https://api.github.com/repos/acme/webapp/issues/482/events",
  "html_url": "https://github.com/acme/webapp/issues/482",
  "id": 1928374651,
  "node_id": "I_kwDOAbCdEf5x8kLZ",
  "number": 482,
  "title": "Login form crashes on Safari 17",
  "user": {
    "login": "janedoe",
    "id": 5566778,
    "avatar_url": "https://avatars.githubusercontent.com/u/5566778?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/janedoe",
    "html_url": "https://github.com/janedoe",
    "followers_url": "https://api.github.com/users/janedoe/followers",
    "type": "User",
    "site_admin": false
  },
  "labels": [
    { "id": 1, "node_id": "LA_kwDOAbCdEf5x9k", "url": "...", "name": "bug", "color": "d73a4a", "default": true, "description": "Something isn't working" }
  ],
  "state": "open",
  "locked": false,
  "assignee": null,
  "milestone": null,
  "comments": 3,
  "created_at": "2026-06-20T09:14:33Z",
  "updated_at": "2026-07-01T02:10:11Z",
  "closed_at": null,
  "author_association": "CONTRIBUTOR",
  "body": "Steps to reproduce...\n\n1. Open login page on Safari 17\n2. Click submit\n3. Page crashes",
  "reactions": { "url": "...", "total_count": 2, "+1": 2, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 }
}

Response này riêng phần rút gọn ở trên đã hơn 300 token; response thật với đầy đủ field còn dài hơn nhiều — trong khi model chỉ cần 6 thông tin.

// Good: chỉ trả về field model cần
{
  "issue_number": 482,
  "title": "Login form crashes on Safari 17",
  "body": "Steps to reproduce...\n\n1. Open login page on Safari 17\n2. Click submit\n3. Page crashes",
  "labels": ["bug"],
  "status": "open",
  "comment_count": 3
}

Chỉ riêng bước lọc field này đã giảm khoảng 75-80% token so với response gốc. Nhưng còn hai lớp tối ưu nữa có thể áp dụng thêm ở tầng format:

Lớp tối ưu 2 — pretty-print vs compact JSON. Khi serialize JSON để trả về cho model, JSON pretty-print (có indent, xuống dòng) trực quan hơn cho con người đọc log, nhưng tốn thêm token cho mọi khoảng trắng và ký tự xuống dòng — các ký tự này vẫn được tokenizer tính là token thật.

// Pretty-print (~20 token cho object nhỏ này)
{
  "issue_number": 482,
  "status": "open",
  "comment_count": 3
}
// Compact (~12 token, giảm khoảng 40%)
{"issue_number":482,"status":"open","comment_count":3}

Với response nhỏ, mức giảm tuyệt đối không nhiều, nhưng với response có hàng chục field và được gọi hàng trăm lần trong một agent loop, hoặc khi bạn trả về list gồm nhiều item, mức giảm 40% này cộng dồn rất đáng kể.

Lớp tối ưu 3 — rút ngắn tên field (khi phù hợp). Với nội bộ hệ thống — nơi bạn kiểm soát cả tool và cách model được hướng dẫn dùng tool — có thể rút ngắn tên field miễn là vẫn giữ được ngữ nghĩa rõ trong context (đặc biệt nếu bạn có mô tả field ngắn trong tool description một lần).

// Full field name
{"issue_number": 482, "comment_count": 3, "status": "open"}
// Abbreviated (giảm ~25%, model vẫn hiểu đúng nhờ context của cuộc hội thoại)
{"num": 482, "comments": 3, "status": "open"}

Lưu ý: kỹ thuật rút ngắn tên field cần cân nhắc kỹ — chỉ nên áp dụng khi field đã có ngữ cảnh rõ ràng (ví dụ tool chỉ trả về issue nên num chắc chắn là issue number, không gây nhầm lẫn với các loại "num" khác). Nếu rút ngắn khiến field trở nên mơ hồ (ví dụ st có thể là "status" hay "state"?), model có thể hiểu sai — lúc đó lợi ích giảm token không bù được rủi ro sai logic.

Mẹo

  • Luôn đặt câu hỏi "model cần field này để làm gì trong bước tiếp theo?" cho từng field trước khi đưa vào response — nếu không trả lời được, bỏ field đó.
  • Ưu tiên compact JSON làm mặc định cho tool output gửi tới model; chỉ pretty-print khi output đó cũng được hiển thị trực tiếp cho người dùng xem.
  • Rút ngắn tên field một cách có chủ đích và nhất quán trong toàn bộ hệ tool (ví dụ luôn dùng id, không lúc id lúc identifier), tránh gây nhầm lẫn.
  • Với các trường mảng lớn (như comments, reactions), xem xét trả về số lượng (comment_count) thay vì toàn bộ nội dung, và cung cấp tool riêng để lấy chi tiết khi cần (liên kết với Nguyên tắc 4 dưới đây).

Nguyên tắc 4: Độ chi tiết của Tool — Composite vs. Atomic Tools

Một quyết định thiết kế dễ bị bỏ qua nhưng ảnh hưởng lớn đến tổng token tiêu thụ trong một agent loop là: nên thiết kế nhiều tool nhỏ, đơn nhiệm (atomic tool) hay ít tool lớn, đa nhiệm (composite tool)?

Atomic tool có ưu điểm là dễ hiểu, dễ test, tuân theo nguyên tắc single responsibility. Nhưng khi một tác vụ thực tế của người dùng luôn đòi hỏi gọi nhiều atomic tool liên tiếp theo một trình tự cố định, thì mỗi lần gọi tool là một round-trip — và mỗi round-trip kéo theo: (1) model phải sinh ra một tool call request, (2) toàn bộ tool definitions phải được model "nhìn lại" trong context để quyết định gọi tool nào tiếp theo, (3) tool response được nạp vào context, và cả assistant message trung gian (dù ngắn) cũng cộng vào lịch sử hội thoại.

Ví dụ: agent hỗ trợ debug cần trả lời câu hỏi "dòng code này ai sửa gần nhất và tại sao". Với thiết kế atomic:

def get_file_content(path: str, line_start: int, line_end: int) -> str:
    """Return raw content of file at given line range."""
    ...

def get_git_blame(path: str, line_start: int, line_end: int) -> dict:
    """Return blame info: author, commit hash, date for given line range."""
    ...

def get_recent_changes(commit_hash: str) -> dict:
    """Return commit message and diff for a given commit hash."""
    ...

Với thiết kế composite — gộp cả 3 bước vào một tool phục vụ đúng một use case rõ ràng và lặp lại thường xuyên:

def explain_line_history(path: str, line_start: int, line_end: int) -> dict:
    """
    Return everything needed to explain who last changed these lines and why:
    the current code, blame info, and the commit message/diff of the last change.
    """
    content = _read_file(path, line_start, line_end)
    blame = _git_blame(path, line_start, line_end)
    change = _get_commit_details(blame["commit_hash"])
    return {
        "code": content,
        "author": blame["author"],
        "commit": blame["commit_hash"][:8],
        "commit_message": change["message"],
        "diff_summary": change["diff_summary"]
    }

Composite tool này giảm số round-trip từ 3 xuống 1, đồng nghĩa giảm 2 lần "làm mới" toàn bộ tool definitions trong context, và loại bỏ 2 tool-response trung gian không cần thiết. Trong thực nghiệm, với các use case có trình tự cố định, composite tool có thể giảm 50-65% token tổng của cả agent loop cho tác vụ đó.

Nhưng composite không phải luôn thắng. Nếu tool quá "to", gộp quá nhiều nhiệm vụ không liên quan, nó sẽ khó mô tả gọn trong schema, khó tái sử dụng cho các use case khác, và model khó chọn đúng tham số. Quy tắc thực tế: composite hóa khi có một trình tự tool-call cố định, lặp lại nhiều, và luôn đi cùng nhau trong ít nhất 80% trường hợp sử dụng thực tế. Giữ atomic khi các tool được dùng độc lập, linh hoạt, ở nhiều ngữ cảnh khác nhau.

Mẹo

  • Trước khi tách hoặc gộp tool, hãy nhìn vào log agent loop thực tế (nếu có) để xem tần suất các tool được gọi liên tiếp — dữ liệu thật quan trọng hơn cảm tính "nên gộp hay nên tách".
  • Đặt tên composite tool theo "mục đích nghiệp vụ" (ví dụ explain_line_history) thay vì theo "các bước kỹ thuật bên trong" — điều này cũng giúp model chọn tool đúng ngay từ đầu, giảm số lần thử-sai.
  • Giữ atomic tool cho các operation có thể tái sử dụng độc lập ở nhiều luồng khác nhau (như get_file_content dùng ở cả luồng review code và luồng debug).
  • Có thể giữ cả hai lớp: expose composite tool cho các luồng phổ biến, nhưng vẫn giữ atomic tool "ẩn" hoặc ít ưu tiên cho các trường hợp đặc biệt cần linh hoạt.

Nguyên tắc 5: Thiết kế Error Handling — Gọn, có thể hành động

Error response (kết quả trả về khi tool thất bại) là phần dễ bị bỏ qua nhất khi thiết kế tool, vì dev thường tập trung vào "happy path" (luồng chạy thành công). Nhưng trong thực tế vận hành agent, lỗi xảy ra thường xuyên — sai tham số, resource không tồn tại, hết quyền truy cập, timeout — và một error response tệ không chỉ tốn token vô ích, mà còn khiến model không biết phải làm gì tiếp theo, dẫn đến việc model thử lại mù quáng, tốn thêm nhiều round-trip.

Lỗi thiết kế phổ biến là forward thẳng stack trace hoặc exception object gốc của ngôn ngữ lập trình — thông tin này hữu ích cho dev debug production, nhưng vô nghĩa với model và tốn token khủng khiếp.

// Bad: verbose, ~45 token nhưng vô dụng với model
{
  "error": true,
  "exception_type": "ResourceNotFoundException",
  "message": "The requested resource could not be located",
  "stack_trace": "at ProductService.findById (product.service.ts:142)\n at ProductController.get (product.controller.ts:58)\n at Router.handle (router.ts:210)",
  "trace_id": "8f3e2b91-4a7c-4d1e-9c88-1a2b3c4d5e6f",
  "timestamp": "2026-07-09T08:22:41.309Z"
}

Response này dài, chứa stack trace (mà model không thể hành động dựa vào), timestamp và trace_id chỉ có ích cho hệ thống logging nội bộ, và message chung ("could not be located") không nói rõ nguyên nhân hay hướng xử lý.

// Good: compact, ~15 token, actionable — model biết chính xác phải làm gì
{
  "error": "product_id 'SKU-9921' not found",
  "suggestion": "Verify SKU or use search_products to find valid IDs"
}

Bản "good" giảm khoảng 65-70% token so với bản verbose, nhưng quan trọng hơn số token là nó actionable (có thể hành động) — model đọc xong biết ngay: ID sai, và biết luôn công cụ nào (search_products) để tự sửa mà không cần hỏi lại người dùng hoặc đoán mò. Đây chính là điểm khác biệt cốt lõi giữa "error message cho máy" và "error message cho model": model cần một gợi ý hành động tiếp theo, không cần chi tiết kỹ thuật của lỗi.

Một pattern hữu ích khác là chuẩn hóa cấu trúc error trên toàn bộ hệ tool, để model học được pattern chung và xử lý nhất quán:

{"error": "<mô tả ngắn nguyên nhân>", "suggestion": "<hành động khắc phục cụ thể>"}

Với lỗi do hệ thống (không phải do input sai của model), như timeout hoặc service down, suggestion nên hướng dẫn model dừng lại, báo cho người dùng, thay vì khuyến khích model thử lại vô hạn:

{"error": "inventory service timeout after 3 retries", "suggestion": "Inform user the check is temporarily unavailable; do not retry further"}

Mẹo

  • Luôn tách riêng error response ở tầng tool khỏi exception gốc của ngôn ngữ lập trình — viết một lớp "translate" chuyển exception kỹ thuật thành message ngắn, thân thiện với model, trước khi trả về.
  • Luôn kèm field gợi ý hành động (suggestion hoặc next_step) trong mọi error response — đây là yếu tố quan trọng nhất để giảm số lần retry mù quáng của model.
  • Log đầy đủ thông tin kỹ thuật (stack trace, trace_id) ở tầng logging nội bộ (observability), tách biệt hoàn toàn khỏi response trả cho model.
  • Với lỗi do hệ thống ngoài tầm kiểm soát của model, hướng dẫn rõ "đừng thử lại" để tránh vòng lặp retry gây tốn token không kiểm soát.

Danh sách kiểm tra thiết kế Tool thực tế

Trước khi merge một custom tool mới hoặc review lại tool cũ, hãy đối chiếu với checklist sau — có thể dùng trực tiếp trong PR review hoặc trong tài liệu thiết kế tool (tool design doc):

  • Schema: Description có ngắn, dùng cụm từ thay vì câu văn dài dòng? Enum có chỉ liệt kê giá trị đại diện thay vì toàn bộ?
  • Parameter: Cấu trúc có flat (không lồng object quá 1 cấp)? Kiểu dữ liệu có đúng chuẩn JSON (không stringify số/boolean)? Có field nào model không đủ thông tin để điền chính xác không?
  • Output — nội dung: Từng field trong response có được model dùng đến ở bước tiếp theo không? Có field nào chỉ phục vụ mục đích nội bộ/debug bị lọt vào response không?
  • Output — format: Response gửi cho model có dùng compact JSON (không pretty-print) không? Tên field có thể rút ngắn an toàn mà không gây mơ hồ không?
  • Granularity: Tool này có luôn được gọi cùng 1-2 tool khác theo một trình tự cố định không? Nếu có, có nên gộp thành composite tool không? Ngược lại, composite tool hiện tại có đang gộp quá nhiều nhiệm vụ không liên quan không?
  • Error handling: Error response có ngắn gọn, có field gợi ý hành động cụ thể không? Có forward nhầm stack trace hoặc exception kỹ thuật ra ngoài không?
  • Đo lường: Đã dùng tokenizer đo tổng token của schema + một vài response mẫu (thành công và lỗi) chưa? Có baseline để so sánh trước/sau khi tối ưu không?

Mẹo

  • Biến checklist này thành một phần của code review template cho mọi PR thêm/sửa custom tool, để việc tối ưu token trở thành quy trình chuẩn, không phụ thuộc vào việc "có ai đó nhớ để làm" hay không.
  • Định kỳ (mỗi quý hoặc mỗi khi thêm tool mới đáng kể) chạy lại toàn bộ checklist trên các tool đang production, vì response từ API bên thứ ba có thể thay đổi cấu trúc theo thời gian và schema tool có thể "phình" trở lại nếu không kiểm soát.

Tổng kết

Thiết kế custom tool tối ưu token không phải là một kỹ thuật đơn lẻ, mà là một tư duy xuyên suốt năm khía cạnh: schema tối giản, tham số flat và có kiểu, output chỉ chứa đúng thông tin cần thiết ở định dạng compact, độ chi tiết tool được cân bằng hợp lý giữa composite và atomic, và error response gọn nhưng actionable. Từng nguyên tắc riêng lẻ có thể chỉ giảm 20-40% token, nhưng khi áp dụng đồng thời trên toàn bộ hệ tool của một agent, mức giảm tổng thể trên cả agent loop có thể lên tới 60-80% — đồng thời giảm luôn số round-trip và giảm khả năng model bị "lạc" trong quá nhiều thông tin dư thừa.

Điều quan trọng cần nhớ: người "đọc" tool output không phải con người, mà là model. Mọi quyết định thiết kế — từ tên field đến cấu trúc error — nên bắt đầu từ câu hỏi "thông tin này giúp model làm đúng bước tiếp theo như thế nào?", không phải "thông tin này có đầy đủ và đẹp mắt để dev debug không?". Khi tư duy đó trở thành mặc định trong team, việc thiết kế tool tối ưu token sẽ không còn là một bước tối ưu riêng lẻ về sau, mà là một phần tự nhiên của quy trình phát triển agent ngay từ đầu.