Bỏ qua để đến nội dung

Ngữ cảnh công cụ và bộ nhớ đệm prompt

Có hai điều luôn đúng với mọi yêu cầu mà harness tích hợp sẵn gửi đi: định nghĩa công cụ là khoản chi phí cố định lớn nhất trong đó, và gần như không công cụ nào trong số đó sẽ được dùng đến. Trang này giải thích Control Center làm gì trước tình trạng ấy, và vì sao câu trả lời không đơn giản chỉ là “gửi ít công cụ hơn”.

Vấn đề nằm ở sự chú ý, không phải số byte

Phần tiêu đề “Vấn đề nằm ở sự chú ý, không phải số byte”

Control Center đăng ký khoảng 126 công cụ. Nếu gửi đầy đủ, định nghĩa của chúng tốn khoảng 23.000 token trong mỗi yêu cầu — một khoản chi phí mà không phép nén nào chạm tới được, bởi định nghĩa công cụ đi trong một phần riêng của yêu cầu, tách khỏi phần hội thoại.

Hóa đơn token là lời phàn nàn hiển nhiên, và khi đã có bộ nhớ đệm prompt thì nó cũng là khoản nhỏ hơn. Chi phí thật sự nằm ở độ chính xác khi chọn lựa. Các đánh giá đã công bố về việc gọi công cụ đều nhất trí rằng mô hình chọn đúng công cụ một cách đáng tin cậy cho tới khoảng 30–50 công cụ trong ngữ cảnh, rồi càng vượt qua mức đó càng kém dần. Nguyên nhân được chỉ ra không phải số lượng token mà là chồng lấn ngữ nghĩa: một khi danh mục có vài công cụ với phần mô tả đọc giống nhau, mô hình đang phải chọn giữa những thứ na ná nhau thay vì nhớ ra một năng lực.

Ở mức 126 công cụ, agent đã vượt xa vùng đó trong từng lượt một — kể cả nhiều lượt mà nó chỉ cần đọc một tệp.

Vì vậy một lượt chạy được hiển thị hai thứ:

  • một tập thường trú — khoảng hai chục công cụ, được gửi kèm lược đồ đầy đủ trong mọi yêu cầu;
  • một chỉ mục tên của mọi thứ còn lại, được nhóm dưới các tiêu đề như “Tickets and projects” hay “Memory and knowledge”, tốn vài trăm token cho toàn bộ danh mục.

Đo trên danh mục hiện tại, cách này đưa khối công cụ từ ~23,1k token xuống ~5,0k, tức nhỏ hơn khoảng 78%, đồng thời đặt tập luôn hiển thị vào đúng vùng mà việc chọn lựa diễn ra đáng tin cậy.

Tập thường trú là những công cụ mà một lượt chạy dùng trong hầu hết các lượt: từ vựng về tệp và shell (read, write, edit, search, bash, …), các công cụ khám phá, danh sách việc cần làm, tìm kiếm bộ nhớ, và động từ bất kỳ mà chế độ hiện tại phải gọi để tạo ra kết quả của nó. Động từ đầu ra của chính một chế độ luôn thường trú — một lượt chạy không bao giờ phải đi tìm lời gọi duy nhất tạo ra sản phẩm giao nộp của nó.

Bị trì hoãn không có nghĩa là không dùng được

Phần tiêu đề “Bị trì hoãn không có nghĩa là không dùng được”

Đây chính là phần khiến thiết kế này an toàn chứ không chỉ rẻ. Một công cụ bị trì hoãn vẫn gọi được ngay từ lượt đầu tiên. Chỉ có lược đồ của nó là bị giữ lại.

Agent nhìn thấy cái tên ấy trong prompt của mình. Nếu nó gọi thẳng tên đó, harness nạp lược đồ và chạy công cụ ngay trong cùng một bước — không lỗi, không phải đi thêm một vòng. Mô hình không cần biết rằng công cụ từng bị “dỡ ra”.

Khi agent biết việc cần làm nhưng không biết tên công cụ, search_tools nhận một mô tả bằng ngôn ngữ tự nhiên (“gán một phiếu cho ai đó”), xếp hạng toàn bộ bề mặt của lượt chạy, rồi trả về các kết quả khớp cùng với lược đồ đã được nạp sẵn, để lượt kế tiếp có thể gọi thẳng một trong số đó. Nếu tìm kiếm không ra gì, nó nói rõ điều đó và gợi ý cách diễn đạt khác hoặc một phạm vi tìm rộng hơn — một cách có chủ đích, bởi một agent đọc kết quả tìm kiếm rỗng thành “năng lực này không tồn tại” sẽ tự bịa ra thứ thay thế thay vì tìm lại.

search_tools chỉ tồn tại trong harness tích hợp sẵn. Máy khách MCP bên ngoài thì có search_tool_bm25, công cụ này xếp hạng cùng chỉ mục đó nhưng chỉ mô tả các kết quả khớp — những máy khách ấy đã nhận mọi lược đồ từ tools/list rồi, nên không có gì để nạp.

Việc trì hoãn được áp dụng sau bộ lọc công cụ của chế độ, chứ không bao giờ thay thế nó. Nó giấu một lược đồ; nó không thể để lộ ra một công cụ mà chế độ đã từ chối. Một công cụ được kích hoạt vẫn phải qua lời nhắc phê duyệt và rào chắn hành động đúng như một công cụ thường trú, và một công cụ không thể nạp một cái tên vốn không nằm trên bề mặt của lượt chạy ngay từ đầu.

Bề mặt MCP mà máy khách bên ngoài nhìn thấy không bị ảnh hưởng. tools/list vẫn trả về danh mục đầy đủ. Một nỗ lực trước đây nhằm cắt gọn nó đã làm hỏng agent hoàn toàn: máy khách MCP đối chiếu tên công cụ với danh sách đã lưu trong bộ nhớ đệm của chúng và từ chối mọi thứ không có trong danh sách mà không hề hỏi máy chủ, nên một công cụ “bị ẩn nhưng vẫn gọi được” đơn giản là không thể tiếp cận. Việc trì hoãn nằm trong harness, nơi mà việc nạp một công cụ giữa lượt chạy là điều harness thực sự làm được.

--tool-deferral=off (hoặc CC_SERVER_TOOL_DEFERRAL=off) khiến mọi công cụ được chấp nhận trở lại thường trú, tạo ra đúng những yêu cầu mà máy chủ đã gửi trước khi có tính năng trì hoãn. Xem tham chiếu CLI cc_server.

Khối lượng công việc của agent rất lệch: chúng gửi những prompt khổng lồ và nhận về những câu trả lời nhỏ, hết lượt này đến lượt khác, lần nào cũng với cùng một tiền tố. Các nhà cung cấp lưu đệm tiền tố đó, và một lần đọc từ bộ nhớ đệm chỉ tốn khoảng một phần mười so với một lần đọc mới. Vì thế tỷ lệ trúng bộ nhớ đệm là một thuộc tính hạng nhất của harness chứ không phải một phép tối ưu — và trên Anthropic nó còn mua thêm thông lượng, bởi token đã lưu đệm không bị tính vào giới hạn đầu vào mỗi phút.

Bộ nhớ đệm hoạt động dựa trên khớp tiền tố chính xác, và điều đó dẫn tới một quy tắc chi phối.

Bất kỳ byte nào thay đổi cũng vô hiệu hóa bộ nhớ đệm từ điểm đó trở đi. Vì vậy harness chỉ nối thêm: tin nhắn mới ở cuối, và những lược đồ công cụ vừa được kích hoạt nằm sau khối công cụ đã lưu đệm thay vì được gộp vào trong đó. Chính thứ tự ấy khiến việc kích hoạt trở nên rẻ — tiền tố đã lưu đệm giữ nguyên không đổi và lượt chạy chỉ trả tiền cho những lược đồ vừa nạp.

Vòng lặp cũng cắt bớt những kết quả công cụ cũ từ các lượt trước để thu hồi ngữ cảnh. Nó chờ đến khi thu hồi được đủ nhiều để đáng công viết lại, nhờ đó một lần cắt bớt là một lần viết lại sâu hiếm hoi thay vì liên tục trượt bộ nhớ đệm.

Mỗi nhà cung cấp chỉ cho phép một số lượng điểm đánh dấu bộ nhớ đệm nhất định, và Control Center dùng chúng một cách có tính toán:

Điểm đánh dấu Bao phủ Thời hạn
Công cụ thường trú cuối cùng Khối công cụ 1 giờ
Khối hệ thống cuối cùng Công cụ + system prompt 1 giờ
Đuôi lượt trước Lịch sử tính đến yêu cầu cuối cùng 5 phút
Đuôi lượt hiện tại Toàn bộ lịch sử 5 phút

Hai điểm cuối là một cặp luân chuyển, và lý do có cặp này khá tinh tế: một lần tra bộ nhớ đệm sẽ tìm ngược lại một số khối có giới hạn để tìm thứ mà một yêu cầu trước đó đã lưu. Một lượt chạy nhiều công cụ song song có thể phát ra nhiều khối hơn cửa sổ đó, và khi ấy một điểm đánh dấu ở đuôi sẽ lặng lẽ trượt, khiến yêu cầu phải trả đủ giá mà trong phản hồi chẳng có gì nói lên điều đó. Đánh dấu đúng vị trí mà yêu cầu trước đã dừng lại biến lần trúng bộ nhớ đệm thành chuyện tất yếu về mặt cấu trúc thay vì một cuộc đua may rủi.

Tiền tố công cụ-và-hệ thống nhận thời hạn dài bởi nó không đổi suốt cả lượt chạy, được mọi lượt chạy và agent con cùng hình dạng chia sẻ, và bởi đồng hồ bắt đầu chạy khi một yêu cầu khởi đầu — một lượt phát trực tiếp trong vài phút đã tiêu gần hết một cửa sổ ngắn trước khi yêu cầu kế tiếp hình thành.

Các agent con cùng loại tạo ra những tiền tố prompt giống nhau đến từng byte, nên chúng nên cùng đọc một mục bộ nhớ đệm thay vì mỗi agent tự ghi mục riêng. Nhưng một mục chỉ trở nên đọc được sau khi yêu cầu đã ghi nó bắt đầu phản hồi, nên một đợt khởi chạy tỏa rộng cùng lúc sẽ khiến mọi agent con đều phải trả giá đắt cho một tiền tố mà tất cả cùng chia sẻ.

Harness cho agent con đầu tiên của một hình dạng nhất định khởi chạy trước, rồi thả các anh em của nó ngay khi nó bắt đầu phát trực tiếp. Thời gian chờ là có giới hạn: một agent con đầu tiên chậm chạp chỉ khiến mất một lần trúng bộ nhớ đệm, chứ không bao giờ làm hỏng cả đợt tỏa rộng. Vì cùng lý do, mọi agent con đều nhận cùng một tập công cụ thường trú bất kể hồ sơ của nó — thay đổi theo từng hồ sơ sẽ chia cắt tiền tố dùng chung mà chẳng được lợi gì, bởi hồ sơ vốn đã quyết định agent con được gọi những gì.

Cả hai nửa đều quan sát được chứ không phải giả định:

  • Trình khám phá ngữ cảnh trên một cuộc hội thoại báo cáo các công cụ thường trú, chỉ mục bị trì hoãn và việc mỗi lược đồ bị giữ lại sẽ tốn bao nhiêu, được dựng từ đúng đường mã mà một lượt chạy thật dùng.
  • Mỗi lượt chạy ghi lại những công cụ bị trì hoãn mà nó đã nạp và điều gì đã kích hoạt việc đó, nên một tìm kiếm không ra kết quả sẽ hiện lên như một lần truy hồi trượt, thay vì biến mất vào một agent đang lặng lẽ làm một việc gì đó na ná.
  • Trang Quan sát báo cáo Tỷ lệ cache là số lần đọc từ bộ nhớ đệm trên toàn bộ token prompt đã xử lý, kể cả các lần ghi vào bộ nhớ đệm — một lượt chạy cứ mãi dựng lại tiền tố của mình sẽ đạt điểm thấp, thay vì được điểm như một lượt chạy chưa từng cần bộ nhớ đệm.