Tác nhân yêu cầu hồ sơ khách hàng. API của bạn trả về thông tin khách hàng, cộng với 200 đơn hàng gần nhất của họ, cộng với từng mặt hàng trên các đơn hàng đó, cộng với dấu thời gian ở ba định dạng và một khối _links cho mỗi mục. Bốn mươi nghìn token rơi vào cửa sổ ngữ cảnh. Tác nhân chỉ cần địa chỉ email.
Thực hiện điều đó bốn lần trong một lượt chạy và tác nhân đã tiêu tốn phần lớn ngân sách của mình để đọc dữ liệu JSON mà nó không yêu cầu. Sau đó, những thất bại thú vị bắt đầu: nó quên hướng dẫn ban đầu, nó tóm tắt nhiệm vụ thay vì hoàn thành nó, và chi phí mỗi lượt chạy tăng lên trong khi chất lượng giảm sút.
Đây là vấn đề thiết kế ở tầng API, không phải vấn đề về prompt. Các tác nhân tiêu thụ phản hồi thông qua một cửa sổ cố định, và mỗi trường bạn trả về đều cạnh tranh với các hướng dẫn, cuộc hội thoại và kế hoạch. Hướng dẫn này bao gồm nguồn gốc của sự dư thừa, các mẫu chọn trường và phân trang để khắc phục nó, cách cắt giảm trong lớp công cụ khi bạn không kiểm soát API và cách đo lường sự khác biệt. Bài viết trụ cột của chúng tôi về lý do các tác nhân AI thất bại trong môi trường sản xuất coi việc cạn kiệt ngữ cảnh là một trong những chế độ lỗi cốt lõi, và đây là phần thực hành của vấn đề đó.
Apidog hỗ trợ về mặt đo lường: bạn có thể thấy kích thước phản hồi thực tế cho mỗi endpoint trước khi tác nhân gọi nó, và mô phỏng hình dạng đã được cắt giảm mà bạn muốn trước khi nhóm API triển khai.
Token đi đâu?
Các phản hồi được thiết kế cho trình duyệt và bảng điều khiển mang theo rất nhiều dữ liệu không cần thiết, làm tốn tiền của tác nhân.
Các envelope rườm rà. Một wrapper data, meta, links, included bao quanh một đối tượng năm trường có thể làm tăng gấp đôi dung lượng tải. Các liên kết hypermedia hữu ích cho một client theo dõi chúng. Các tác nhân hầu như không bao giờ làm vậy, và mỗi URL đều là token.
Các khóa lặp lại. JSON lặp lại tên của mỗi trường trên mỗi phần tử mảng. Một danh sách 200 mục với 15 trường mỗi mục phải trả tiền cho 3.000 chuỗi khóa. Đây là lý do tại sao các endpoint danh sách chiếm ưu thế trong việc sử dụng ngữ cảnh.
Mở rộng lồng nhau theo mặc định. Các endpoint nhúng các tài nguyên liên quan thì tiện lợi cho đến khi một tác nhân truy cập chúng. Một khách hàng cộng với các đơn hàng của họ cộng với các mặt hàng là một cây, và cây thì phát triển nhanh chóng.
Các định dạng dư thừa. created_at, created_at_unix, và created_at_human trên cùng một đối tượng có nghĩa là chi phí gấp ba cho một giá trị.
Giá trị null và rỗng. Nhiều trình tuần tự hóa phát ra mọi trường ngay cả khi chưa được đặt. Hai mươi giá trị null trên mỗi bản ghi là hoàn toàn lãng phí.
Một cách hữu ích để hình dung: chi phí token theo dõi kích thước của văn bản đã được tuần tự hóa, không phải số lượng bản ghi. Hai trăm bản ghi với năm trường mỗi bản có thể rẻ hơn một đối tượng lồng nhau sâu.
Quy tắc một: trả về các trường, không phải tài nguyên
Thay đổi có giá trị cao nhất là cho phép người gọi yêu cầu những gì họ cần.
GET /v1/customers/8812?fields=id,email,plan,status
{ "id": "8812", "email": "dana@example.com", "plan": "pro", "status": "active" }
Đó là giảm 90 phần trăm so với một bản ghi đầy đủ trên hầu hết các API, và chỉ mất một buổi chiều để thêm vào. Hướng dẫn thiết kế API của Google ghi lại mẫu field-mask nếu bạn muốn một phiên bản có tiền lệ, và GraphQL giải quyết cùng vấn đề bằng cách buộc phải chọn trường.
Hai lưu ý triển khai. Xác thực danh sách trường với schema và từ chối các tên không xác định, để một trường bị "ảo giác" tạo ra lỗi rõ ràng thay vì một đối tượng bị cắt bớt một cách âm thầm. Và giữ một tập hợp mặc định nhỏ cho những người gọi không gửi gì, thay vì mặc định trả về mọi thứ.
Sau đó, hiển thị tham số cho mô hình trong mô tả công cụ, với các trường được liệt kê rõ ràng:
{
"name": "getCustomer",
"description": "Lấy thông tin khách hàng bằng ID. Luôn truyền `fields` với chỉ những gì bạn cần. Các trường có sẵn: id, email, name, plan, status, created_at, billing_address, order_count.",
"input_schema": {
"type": "object",
"required": ["customerId", "fields"],
"properties": {
"customerId": { "type": "string" },
"fields": {
"type": "array",
"items": { "type": "string" },
"description": "Tên các trường cần trả về. Giữ danh sách này ở mức tối thiểu."
}
}
}
}
Các mô tả là nơi duy nhất mô hình học các quy tắc này, và cả hướng dẫn gọi hàm của OpenAI lẫn tài liệu sử dụng công cụ của Anthropic đều nhấn mạnh điều này như nhau. Biến fields thành bắt buộc là một mẹo. Một tham số tùy chọn sẽ bị bỏ qua; một tham số bắt buộc buộc mô hình phải suy nghĩ về những gì nó thực sự cần.
Quy tắc hai: luôn giới hạn danh sách
Các endpoint danh sách không giới hạn là nguồn gốc lớn thứ hai gây ra sự cố. Một tác nhân yêu cầu “các đơn hàng gần đây” và nhận được mọi thứ kể từ năm 2019.
Đặt một giới hạn cứng ở phía máy chủ, không chỉ là giá trị mặc định. Nếu tác nhân gửi limit=5000, hãy trả về 100 và thông báo điều đó. Các hướng dẫn của chúng tôi về phân trang REST API và thiết kế phân trang cho hàng triệu bản ghi đề cập đến cơ chế; các quy tắc dành riêng cho tác nhân thì hẹp hơn:
- Giới hạn trang ở mức mà mô hình có thể đọc, trong khoảng 20 đến 50 mục cho các bản ghi điển hình.
- Trả về tổng số lượng để tác nhân có thể biết liệu nó đã xem hết mọi thứ chưa mà không cần phải phân trang để tìm hiểu.
- Sử dụng phân trang con trỏ (cursor pagination). Các offset bị lệch khi dữ liệu thay đổi giữa chừng, và một tác nhân phân trang chậm sẽ gặp phải vấn đề đó.
- Bao gồm một câu lệnh rõ ràng trong phản hồi, chẳng hạn như
"truncated": true, để mô hình biết còn nhiều dữ liệu hơn. Các mô hình thường đánh giá sai tính đầy đủ chỉ từ độ dài của một mảng.
Cũng cung cấp cho tác nhân một cách để tránh phân trang hoàn toàn. Một endpoint count, một tìm kiếm được lọc với cửa sổ hẹp, hoặc một đối tượng tóm tắt thường sẽ trả lời câu hỏi mà không cần trả về bất kỳ bản ghi nào. Phản hồi rẻ nhất là phản hồi không chứa dữ liệu.
Quy tắc ba: cắt giảm trong lớp công cụ khi API không thuộc sở hữu của bạn
Các API của bên thứ ba sẽ không thêm tính năng chọn trường chỉ vì bạn yêu cầu. Thay vào đó, hãy thực hiện việc cắt giảm trong bộ thực thi của bạn, giữa phản hồi HTTP và mô hình.
KEEP = {
"getCustomer": ["id", "email", "plan", "status"],
"listOrders": ["id", "total", "status", "created_at"],
}
def project(tool_name, payload):
keep = KEEP.get(tool_name)
if keep is None:
return payload
if isinstance(payload, list):
return [{k: item.get(k) for k in keep if k in item} for item in payload]
return {k: payload.get(k) for k in keep if k in payload}
Ba cải tiến sau đây giúp điều này khả thi trong thực tế.
- Lưu trữ toàn bộ phản hồi và cung cấp cho mô hình phần được chiếu. Giữ tải trọng chưa cắt giảm trong nhật ký chạy của bạn để việc gỡ lỗi vẫn có thể thực hiện được. Bài viết của chúng tôi về truy vết các lệnh gọi công cụ của tác nhân đề cập đến những gì cần ghi lại.
- Cho mô hình biết bạn đã xóa gì. Một dòng như
"_omitted": ["billing_address", "notes", "metadata"]cho phép nó yêu cầu bản ghi đầy đủ khi thực sự cần, thay vì kết luận dữ liệu không tồn tại. - Chuyển đổi danh sách sang định dạng nhỏ gọn. Đối với kết quả dạng bảng, CSV hoặc bảng markdown tốn ít token hơn JSON rất nhiều vì tên trường chỉ xuất hiện một lần thay vì mỗi hàng. Các mô hình đều đọc tốt cả hai định dạng.
id,total,status,created_at
ord_91,4900,paid,2026-08-21
ord_92,1200,refunded,2026-08-22
Quy tắc bốn: tóm tắt trên máy chủ cho các trường hợp nặng
Một số câu hỏi không cần đến bản ghi nào cả. “Khách hàng này có bất kỳ khoản thanh toán thất bại nào trong tháng này không?” là một câu hỏi dạng boolean. Trả về 40 đối tượng thanh toán để mô hình tự xử lý là cách trả lời tốn kém.
Khi một câu hỏi lặp lại, hãy thêm endpoint trả lời trực tiếp câu hỏi đó. Một bản tóm tắt tình trạng tài khoản, một bản tổng hợp trạng thái, một tập hợp nhỏ. Việc này trông giống như công việc thiết kế API thông thường bởi vì nó là như vậy, và đây là phiên bản có giá trị nhất của tất cả những điều trên: thay vì cắt giảm một phản hồi lớn, bạn tránh tạo ra một phản hồi như vậy.
Hai rào cản bảo vệ. Giữ các bản tóm tắt ổn định về hình dạng để các tác nhân có thể dựa vào chúng, và tạo phiên bản cho chúng, bởi vì prompt của một tác nhân được viết dựa trên một hình dạng và một thay đổi âm thầm sẽ làm hỏng nó. Bài viết của chúng tôi về điều gì xảy ra khi API thay đổi bên dưới một tác nhân đề cập đến rủi ro đó, và chiến lược tạo phiên bản API tốt nhất đề cập đến cơ chế.
Đo lường trước và sau
Không điều nào trong số này đáng làm một cách mù quáng. Ba con số sau đây sẽ cho bạn biết vấn đề nằm ở đâu.
- Bytes mỗi phản hồi, mỗi endpoint. Gửi một yêu cầu thực tế đến mỗi công cụ mà tác nhân của bạn có thể gọi và ghi lại kích thước tải trọng. Bất cứ thứ gì vượt quá vài kilobyte đều là ứng cử viên. Trong Apidog, bạn có thể chạy mỗi endpoint một lần và đọc kích thước trực tiếp từ phản hồi, sau đó lưu yêu cầu để kiểm tra lặp lại khi API thay đổi.
- Token mỗi lần gọi công cụ. Bytes là một đại diện; token là hóa đơn. Chạy các tải trọng qua trình tokenizer của nhà cung cấp của bạn, chẳng hạn như tiktoken cho các mô hình OpenAI, và xếp hạng các endpoint. Xếp hạng thường bị lệch, với một hoặc hai endpoint chịu trách nhiệm cho hầu hết chi phí.
- Ngữ cảnh được sử dụng mỗi lần chạy. Ghi lại tổng số đang chạy trong toàn bộ tác vụ của tác nhân. Nếu một tác vụ kết thúc gần giới hạn, việc cắt giảm sẽ giúp bạn có các lượt chạy hoàn chỉnh, không chỉ rẻ hơn.

Sau đó, thiết kế hình dạng bạn muốn và mô phỏng nó trước khi nhóm API xây dựng. Một máy chủ giả lập trả về phản hồi đã được cắt giảm cho phép bạn đo lường sự cải thiện và xác minh rằng tác nhân vẫn thành công với ít dữ liệu hơn, đây là câu hỏi thực sự quan trọng. Bài viết của chúng tôi về chạy tác nhân với máy chủ giả lập thay vì môi trường sản xuất đề cập đến quy trình làm việc.
Một kết quả tốt trông như thế nào
Một phản hồi thân thiện với tác nhân thì nhỏ gọn, phẳng và trung thực về những gì nó đã bỏ qua:
{
"customer": { "id": "8812", "email": "dana@example.com", "plan": "pro" },
"recent_orders": [
{ "id": "ord_91", "total_cents": 4900, "status": "paid" },
{ "id": "ord_92", "total_cents": 1200, "status": "refunded" }
],
"recent_orders_total": 47,
"truncated": true,
"_omitted": ["billing_address", "metadata", "order_line_items"]
}
Dưới 200 token. Nó trả lời câu hỏi phổ biến, nó cho biết có 47 đơn hàng thay vì ngụ ý chỉ có hai, và nó cho mô hình biết những gì có thể hỏi tiếp theo.
Bắt đầu với endpoint gây tốn kém nhất của bạn. Đo lường nó, thêm tính năng chọn trường, giới hạn danh sách và chạy lại tác nhân. Khoảng cách giữa hai con số thường đủ lớn để biện minh cho phần còn lại của công việc. Tải xuống Apidog nếu bạn muốn có công cụ đo lường và giả lập trong cùng một dự án.
Ba nơi điều này xuất hiện
- Phân loại hỗ trợ. Một tác nhân đọc một yêu cầu hỗ trợ, lấy thông tin khách hàng và quyết định có nên leo thang hay không. Phiên bản ngây thơ sẽ lấy toàn bộ đối tượng khách hàng và 50 yêu cầu hỗ trợ gần nhất, đốt cháy 30.000 token trước khi đọc được khiếu nại thực tế. Phiên bản đã sửa đổi gọi một endpoint tóm tắt trả về gói dịch vụ, trạng thái, số lượng yêu cầu hỗ trợ mở và ngày liên hệ cuối cùng. Khoảng 80 token, và quyết định leo thang tốt hơn vì các sự kiện liên quan không bị chôn vùi.
- Các tác nhân vận hành nội bộ. Một tác nhân triển khai kiểm tra tình trạng dịch vụ trên 40 dịch vụ. Các đối tượng trạng thái đầy đủ làm tràn cửa sổ ở dịch vụ thứ 12. Một bản tổng hợp trả về một dòng cho mỗi dịch vụ, tên cộng trạng thái cộng tỷ lệ lỗi, vừa vặn tất cả 40 dịch vụ trong vài trăm token và cho phép tác nhân suy luận trên toàn bộ hệ thống thay vì quên đi nửa đầu.
- Nhập liệu và đối chiếu dữ liệu. Một tác nhân đối chiếu hóa đơn với các khoản thanh toán. Việc trả về các tài liệu hóa đơn đầy đủ khiến nó thất bại sau vài chục bản ghi. Việc trả về
id,amount_cents,date, vàreferencedưới dạng CSV cho phép nó xử lý vài trăm bản ghi trong một lần, vì việc so sánh chỉ sử dụng bốn trường.
Mẫu chung của cả ba trường hợp: tác nhân cần một bề mặt ra quyết định, và API đã cung cấp cho nó một tài liệu.
Bạn cần lịch sử chạy để thấy được mô hình
Một lần chạy duy nhất cho bạn biết rằng một phản hồi lớn. Mô hình, tức là endpoint nào vượt quá ngân sách và tần suất như thế nào, chỉ xuất hiện qua nhiều lần chạy.
Điều đó có nghĩa là các con số phải tồn tại sau phiên làm việc. Đối với một dịch vụ bạn đã triển khai, đó là dữ liệu đo từ xa của riêng bạn. Đối với các tác nhân mã hóa thực hiện công việc được giao, đó là bất kỳ nền tảng nào chạy chúng: Sharkly giữ dấu vết thực thi và kết quả của mỗi lần chạy trên Task mà nó đến từ đó, do đó việc so sánh giữa các lần chạy là vấn đề đọc lịch sử tác vụ chứ không phải tái tạo các phiên terminal. Dù sao đi nữa, việc thực thi ngân sách mà không có lịch sử sẽ cho bạn biết rằng có điều gì đó quá lớn, nhưng không phải là điều cần khắc phục đầu tiên.

Đặt ngân sách cho mỗi công cụ, không chỉ cho mỗi lần chạy
Hầu hết các nhóm giới hạn tổng ngữ cảnh và dừng lại ở đó. Ngân sách cho mỗi công cụ hữu ích hơn, vì nó biến một vấn đề mơ hồ thành một vấn đề cụ thể.
Hãy đặt một giới hạn cho mỗi công cụ, ví dụ 1.500 token. Khi một phản hồi vượt quá giới hạn đó, bộ thực thi sẽ cắt giảm theo phần được chiếu, thêm dấu chỉ trường bị bỏ qua và ghi lại sự tràn bộ nhớ. Bây giờ bạn có một danh sách các endpoint thường xuyên vượt quá ngân sách, được xếp hạng theo tần suất tác nhân gọi chúng, đây chính là hàng đợi công việc của bạn.
Ngân sách cũng bảo vệ bạn khỏi các endpoint nhỏ trong thử nghiệm nhưng lại khổng lồ đối với một khách hàng thực tế. Các phân phối có đuôi, và tài khoản có 4.000 đơn hàng là tài khoản sẽ làm hỏng một lần chạy lúc 2 giờ sáng. Một giới hạn cứng sẽ biến điều đó thành một phản hồi đã cắt giảm thay vì một tác vụ thất bại.
Các câu hỏi thường gặp
- Việc cắt bớt phản hồi có rủi ro không nếu tác nhân cần dữ liệu bị thiếu? Chỉ khi bạn che giấu việc cắt bớt. Bao gồm một dấu hiệu rõ ràng và một danh sách các trường bị bỏ qua để mô hình có thể yêu cầu chúng. Việc cắt bớt âm thầm là nguyên nhân gây ra câu trả lời sai, chứ không phải bản thân việc cắt giảm.
- Tôi có nên sử dụng GraphQL cho tác nhân thay vì REST không? GraphQL làm cho việc chọn trường là bắt buộc, điều này giải quyết vấn đề này một cách gọn gàng, nhưng nó chuyển sự phức tạp sang việc xây dựng truy vấn và các mô hình viết các truy vấn không hợp lệ thường xuyên hơn là chúng lạm dụng danh sách trường. Thêm
fieldsvào các endpoint REST thường là thay đổi nhỏ hơn. - Phản hồi của công cụ nên nhỏ đến mức nào? Mục tiêu là dưới 1.000 token cho một lần đọc bản ghi đơn lẻ và dưới 2.000 cho một danh sách. Vượt quá mức đó, hãy tự hỏi liệu tác nhân cần bản ghi hay một câu trả lời.
- Bộ nhớ đệm prompt có giải quyết được vấn đề này không? Nó giảm chi phí của ngữ cảnh lặp lại, chứ không phải không gian mà nó chiếm giữ. Một phản hồi 40.000 token được lưu vào bộ đệm vẫn lấp đầy cửa sổ, vì vậy việc lưu vào bộ đệm giúp giảm hóa đơn trong khi vẫn giữ nguyên vấn đề về độ tin cậy.
- Còn phản hồi nhị phân và tệp thì sao? Không bao giờ đưa chúng vào ngữ cảnh. Lưu trữ tệp, cung cấp cho tác nhân một tham chiếu và mô tả ngắn gọn, và cung cấp cho nó một công cụ riêng để chỉ trích xuất những gì nó cần.
- Việc cắt giảm nên nằm ở đâu, trong API hay trong wrapper công cụ? Trong API khi bạn sở hữu nó, bởi vì mọi người gọi đều hưởng lợi và các byte không bao giờ truyền qua mạng. Trong wrapper khi bạn không sở hữu API. Làm cả hai đều tốt.
