docs/compaction.mdXem bản gốc Bản dịch đầy đủ theo đúng cấu trúc tài liệu; code, liên kết và định danh kỹ thuật được giữ nguyên.
nén chặt
Nén là một menu gồm các chiến lược để lưu giữ lịch sử hội thoại của tổng đài viên trong cửa sổ ngữ cảnh của mô hình. Hầu hết là AI Pydantic Capability các lớp chỉnh sửa lịch sử tin nhắn ngay trước khi mỗi yêu cầu được gửi đi. FallbackCompaction thay vào đó là một sáng tác CompactionStrategy sử dụng thông qua TieredCompaction hoặc compact_now; nó không có yêu cầu kích hoạt riêng của nó. Các chỉnh sửa vẫn tồn tại trong lịch sử tin nhắn của lần chạy, do đó, việc cắt bỏ, xóa hoặc tóm tắt sẽ được chuyển sang các bước sau -- nó không được tính toán lại từ toàn bộ lịch sử mỗi lượt.
Tất cả các chiến lược đều duy trì việc gọi công cụ / trả lại công cụ ghép nối. Core không xác thực điều này và nhà cung cấp từ chối một cặp mồ côi, do đó, đảm bảo ghép nối là điều khiến những điều này trở nên an toàn khi được đưa vào agent. Chiến lược zero-LLM không bao giờ gọi là mô hình; chỉ SummarizingCompaction (và TieredCompaction khi nó leo thang đến mức đó) sẽ chi tiêu token.
Trên OpenAI và Anthropic, lõi cũng cung cấp nén chặt từ nhà cung cấp -- nhà cung cấp tóm tắt lịch sử phía máy chủ. Các chiến lược trên trang này là giải pháp thay thế không phụ thuộc vào mô hình: chúng hoạt động với mọi mô hình và giữ logic nén (và chi phí của nó) trong tầm kiểm soát của bạn.
Mặc dù Pydantic AI Harness có trên các bản phát hành 0.x nhưng API có thể thay đổi giữa các bản phát hành nhỏ; khi đó, cảnh báo ngừng sử dụng và hướng dẫn di chuyển ghi chú phát hành sẽ cho bạn (hoặc người đại diện của bạn) biết chính xác cách nâng cấp. Xem chính sách phiên bản.
vấn đề
Một agent chạy nhiều lượt sẽ tích lũy lịch sử: đầu ra của công cụ, lần đọc tệp, lý luận mô hình, nội dung lặp lại. Nếu không được chọn, lịch sử đó sẽ vượt quá cửa sổ ngữ cảnh của mô hình và yêu cầu tiếp theo sẽ không thành công. Việc nén chặt giữ cho lịch sử bị giới hạn và chiến lược phù hợp phụ thuộc vào nơi tồn tại của khối phình to và số tiền bạn có thể chi ra để khôi phục nó.
Thực đơn
| Thành phần | Chi phí | Nó làm gì | Hãy tiếp cận nó khi |
|---|---|---|---|
ClampOversizedMessages | không-LLM | Cắt bớt phần đầu/đuôi một phần quá khổ (văn bản phản hồi, đối số lệnh gọi công cụ) | Một thế hệ chạy trốn đã vượt qua giới hạn bối cảnh và không chiến lược nào khác có thể đạt được nó |
SlidingWindowCompaction | không-LLM | Giảm toàn bộ tin nhắn cũ nhất xuống một phần đuôi | Bạn chỉ cần các lượt gần đây và có thể loại bỏ hoàn toàn bối cảnh cũ |
ClearToolResults | không-LLM | Xóa nội dung của công cụ cũ kết quả tại chỗ, giữ nguyên nội dung cuối cùng keep_pairs | Đầu ra của công cụ chiếm ưu thế trong bối cảnh và có thể được tìm nạp lại theo yêu cầu (cấp đầu tiên giá rẻ) |
DeduplicateFileReads | không-LLM | Xóa mọi tệp đã đọc được thay thế bằng tệp đọc mới hơn của cùng một tệp | agent đọc lại các tập tin và chỉ có phiên bản mới nhất mới quan trọng |
SummarizingCompaction | một cuộc gọi LLM | Tóm tắt các tin nhắn cũ hơn thành một bản tóm tắt có cấu trúc, giữ nguyên phần đuôi | Bối cảnh cũ vẫn còn quan trọng nhưng phải được nén lại; sử dụng đằng sau các tầng giá rẻ |
TieredCompaction | leo thang | Chạy vé giá rẻ trước, chỉ tóm tắt nếu vẫn còn target_tokens | Bạn muốn có một mặc định hợp lý: chỉ sử dụng bản tóm tắt đắt tiền khi cần thiết |
FallbackCompaction | phụ thuộc vào chuỗi | Thử chiến lược tiếp theo khi một người tăng | Việc tóm tắt có thể thất bại và việc cắt ngắn mang tính quyết định phải duy trì hoạt động |
WarnNearLimits | không-LLM | Đưa ra cảnh báo KHẨN CẤP/TUYỆT VỜI khi tiếp cận giới hạn | Bạn muốn agent kết thúc thay vì viết lại lịch sử của nó |
ReportContextUsage | không-LLM | Báo cáo việc sử dụng ngữ cảnh cho ứng dụng của bạn; không bao giờ chỉnh sửa lịch sử | Bạn muốn có thước đo ngữ cảnh trực tiếp trong giao diện người dùng |
Trình kích hoạt
Mọi chiến lược dựa trên quy mô đều kích hoạt max_messages, max_tokens (ước tính), hoặc max_fraction. Số lượng token neo vào việc sử dụng phản hồi mô hình gần đây nhất do nhà cung cấp báo cáo khi có sẵn. Việc sử dụng nhà cung cấp đó bao gồm các hướng dẫn, định nghĩa công cụ và FilePart tải trọng được gửi trong yêu cầu cố định; chỉ những tin nhắn được thêm vào mới được ước tính. Hậu tố sau điểm neo hoặc lịch sử không có điểm neo sử dụng sẽ sử dụng tokenizer hoặc phương pháp phỏng đoán ~4 ký tự cho mỗi token và không thể nhìn thấy FilePart tải trọng. Các lược đồ công cụ đang chờ xử lý mới được tiết lộ cho yêu cầu được ước tính một cách thận trọng trong quá trình triển khai. DeduplicateFileReads chạy theo mọi yêu cầu khi không có trình kích hoạt nào được đặt (nó rẻ và gần như không bị mất). TieredCompaction kích hoạt và dừng lại trên một target_tokens / target_fraction ngân sách. ClampOversizedMessages kích hoạt cho mỗi phần (max_part_tokens / max_part_chars), không phải trong toàn bộ lịch sử -- thất bại mà nó nhắm tới là một phần quá lớn, không phải là tổng thể lớn.
max_fraction: một cài đặt cho mỗi kiểu máy
Một sự tuyệt đối max_tokens chỉ đúng với mô hình mà nó được đo. Cấu hình 180_000 và mô hình ngữ cảnh 1M thu gọn ở mức 1/5 công suất, trả tiền cho những bản tóm tắt mà nó không cần; một mô hình 128K được cấu hình cho 1_000_000 không bao giờ thu gọn trước khi nhà cung cấp từ chối yêu cầu.
max_fraction được giải quyết theo yêu cầu đối với cửa sổ ngữ cảnh thực của mô hình, do đó, một cấu hình đúng ở mọi nơi:
from pydantic_ai import Agent
from pydantic_ai_harness import SummarizingCompaction
agent = Agent(
'anthropic:claude-sonnet-5',
capabilities=[SummarizingCompaction(max_fraction=0.9, keep_messages=20)],
)
Nó thu gọn ở mức 900K trên mẫu 1M và 115K trên mẫu 128K. WarnNearLimits có hình dạng tương tự như max_context_fractionvà TieredCompaction như target_fraction. max_tokens và max_fraction loại trừ lẫn nhau -- một chiến lược sử dụng cả hai sẽ phải chọn một cái và loại bỏ cái kia, khiến người gọi không thể biết ngân sách nào đang có hiệu lực.
Cửa sổ đến từ genai-prices, đã là sự phụ thuộc của pydantic-ai-slim; resolve_context_window được xuất nếu bạn muốn có số đó. Pydantic AI chưa vạch trần nó (ModelProfile không có context_window trường), vì vậy khi có, một chức năng đó sẽ chuyển đổi. Không có gì được lưu vào bộ nhớ đệm: chỉ số được đăng ký xác nhận mới được coi là cửa sổ thực.
Mô hình được tư vấn là ModelRequestContext.model, yêu cầu sẽ được gửi tới chứ không phải yêu cầu bắt đầu chạy. Khả năng được đặt hàng trước đó có thể thay thế nó và ngân sách sẽ theo sau.
Khi cửa sổ không giải quyết được
Không phải mọi mô hình đều có trong sổ đăng ký. Điểm cuối cục bộ, triển khai riêng, tham chiếu có tiền tố Bedrock chẳng hạn như bedrock:us.anthropic.claude-sonnet-5, một mô hình mà cơ quan đăng ký biết mà không có cửa sổ được ghi lại và bất kỳ mô hình nào FallbackModel (nó model_id là một hỗn hợp fallback:...) tất cả đều giải quyết được không có gì. Sau đó, phần này được lấy từ fallback_context_window, mặc định là 200K thận trọng (DEFAULT_CONTEXT_WINDOW): nén sớm hơn chi phí cần thiết một bản tóm tắt, đánh giá quá cao chi phí toàn bộ yêu cầu.
Mọi khả năng sử dụng một phần nhỏ cũng sẽ sử dụng dự phòng, vì vậy bạn không bị mắc kẹt với 200K trên mô hình mà bạn biết kích thước của:
from pydantic_ai import Agent
from pydantic_ai_harness import SummarizingCompaction
agent = Agent(
'bedrock:us.anthropic.claude-sonnet-5',
capabilities=[SummarizingCompaction(max_fraction=0.9, fallback_context_window=1_000_000)],
)
Nó chỉ được tư vấn khi giải pháp không thành công, do đó, nó không tốn kém gì đối với mô hình mà cơ quan đăng ký biết.
TestModel là một trong những mô hình không giải quyết được: model_id là test:test, do đó một phần được lấy từ fallback_context_window và max_fraction=0.9 trở thành trình kích hoạt 180.000 token. Cấu hình nén chỉ được thực hiện đối với TestModel sẽ có vẻ như nó không bao giờ cháy; vượt qua context_window= hoặc fallback_context_window= trong bài kiểm tra để đặt cò súng ở nơi bạn có thể chạm tới.
Khi cửa sổ giải quyết sai số
Việc giải quyết cũng có thể thành công và có thể sai lầm, mà fallback_context_window không thể trợ giúp -- nó chỉ áp dụng khi độ phân giải không thành công. Ba trường hợp:
-
Bản thân mục đăng ký đã sai. Khai thác đọc
genai-pricesvà không thể xác nhận nó. Đo chống lạigenai-prices0.1.3:mã model hồ sơ đăng ký cửa sổ thật anthropic:claude-sonnet-4-51,000,000 200,000 anthropic:claude-opus-4-6200.000 1.000.000 Cửa sổ được ghi quá mức là hướng làm gián đoạn quá trình chạy. Bật
anthropic:claude-sonnet-4-5,max_fraction=0.9giải quyết trình kích hoạt 900.000 token đối với cửa sổ 200.000 token: quá trình nén không bao giờ kích hoạt và thay vào đó, nhà cung cấp từ chối yêu cầu. Vượt quacontext_window=200_000rõ ràng trênclaude-sonnet-4-5.claude-sonnet-5được ghi lại là 1.000.000 khớp với tài liệu mô hình của Anthropic, vì vậy nó không cần ghi đè; kiểm tra bất kỳ id Sonnet nào khác mà bạn sử dụng dựa trên tài liệu riêng của nhà cung cấp trước khi dựa vào số đã giải quyết. Cửa sổ chưa được ghi âm thì an toàn nhưng lãng phí -- nó thu gọn sớm hơn mức cần thiết. -
Cơ quan đăng ký ghi lại mức tối đa mà một mô hình có thể chấp nhận. Khi mức tối đa đó được kiểm soát -- tiêu đề beta, bậc định giá -- yêu cầu thông thường sẽ ít hơn và một phần số được ghi sẽ không bao giờ kích hoạt trước khi nhà cung cấp từ chối yêu cầu.
-
Điểm cuối tự lưu trữ hoặc được ủy quyền báo cáo id mô hình có mục đăng ký mô tả hoạt động triển khai của người khác.
context_window ghi đè hoàn toàn độ phân giải, trên mọi khả năng chiếm một phần nhỏ:
from pydantic_ai import Agent
from pydantic_ai_harness import SummarizingCompaction
agent = Agent(
'openai:gpt-5.6-luna', # served by a local endpoint with a smaller window than the registry records
capabilities=[SummarizingCompaction(max_fraction=0.9, context_window=32_000)],
)
Những gì được tính vào phân số
Với điểm cố định mức sử dụng, mức sử dụng do nhà cung cấp báo cáo bao gồm mọi thứ được tính phí cho yêu cầu cố định,
bao gồm các hướng dẫn, định nghĩa công cụ và FilePart tải trọng. Đối với hậu tố sau
neo và trong toàn bộ lịch sử không có neo sử dụng, công cụ ước tính sẽ sử dụng tokenizer khi được cung cấp
hoặc phương pháp phỏng đoán ~ 4 ký tự cho mỗi token. Phần ước tính đó không thể nhìn thấy FilePart tải trọng.
Các lược đồ công cụ đang chờ xử lý mới được tiết lộ cho yêu cầu được ước tính một cách thận trọng bởi
việc triển khai vì chúng không được bao phủ bởi điểm cố định trước đó.
Nếu bạn đã đặt giá trị tuyệt đối max_tokens, hãy kiểm tra lại. Công cụ ước tính được sử dụng để chỉ tính các lời nhắc của người dùng và hệ thống, trả về công cụ, văn bản phản hồi và lệnh gọi công cụ. ThinkingPart / CompactionPart nội dung, RetryPromptPart nội dung, NativeToolCallPart / NativeToolReturnPart, và mới nhất ModelRequest.instructions hiện cũng được tính, vì vậy cùng một lịch sử đo lường cao hơn và không thay đổi max_tokens compact trước đó. Sớm hơn bao nhiêu tùy thuộc vào mức độ lịch sử của các khối suy nghĩ, số lần thử lại và hướng dẫn; đối với lịch sử gọi công cụ nặng về tư duy, con số này có thể gấp vài lần so với số liệu cũ. Những gì mỗi chiến lược xóa là không thay đổi -- chỉ khi nó chạy.
Báo cáo việc sử dụng: ReportContextUsage
Một chiến lược biết khi nào nên hành động nhưng không nói gì về mức độ tiến gần đến giới hạn, do đó, một ứng dụng muốn hiển thị context: 73% cuối cùng đếm lại lịch sử và đoán mẫu số. ReportContextUsage cũng không -- nó sử dụng lại cùng một công cụ ước tính và cùng một cửa sổ được giải quyết và chỉ quan sát:
from pydantic_ai import Agent
from pydantic_ai_harness import ReportContextUsage, SummarizingCompaction
agent = Agent(
'anthropic:claude-sonnet-5',
capabilities=[
SummarizingCompaction(max_fraction=0.9, keep_messages=20),
ReportContextUsage(on_usage=lambda usage: print(f'{usage.fraction:.0%}')),
],
)
Mỗi lần đọc mang theo used_tokens, window_tokensvà resolved -- False khi cửa sổ là dự phòng chứ không phải cửa sổ thực của mô hình, do đó, thước đo có thể cho thấy tỷ lệ phần trăm là phỏng đoán. on_usage có thể là một chức năng coroutine, do đó, một thước đo đẩy qua ổ cắm không cần cầu nối đồng bộ. Vấn đề về thứ tự: đăng ký màn hình sau khả năng nén để quan sát lịch sử hiện tại đã sửa sau khi nén cùng chu kỳ hoặc trước nó để xem điều gì đã kích hoạt quá trình nén.
used_tokens tuân theo cách tính toán ở trên: các neo sử dụng của nhà cung cấp bao gồm hướng dẫn, công cụ
định nghĩa và FilePart tải trọng từ yêu cầu cố định. Hậu tố sau mỏ neo, hoặc một
lịch sử không có mỏ neo, sử dụng tokenizer hoặc phương pháp phỏng đoán ~4 ký tự cho mỗi token và không thể nhìn thấy
FilePart tải trọng. Các lược đồ công cụ mới được tiết lộ đang chờ xử lý được ước tính một cách thận trọng bởi
thực hiện. Khi khả năng nén chạy sớm hơn trong cùng một chu kỳ, một màn hình đã đăng ký
sau khi nó trừ đi khả năng thu hồi heuristic của việc viết lại trong khi vẫn giữ lại nhà cung cấp cố định của mỏ neo
trên cao.
Nén bên ngoài đường chạy: compact_now
Một chiến lược compact mất một RunContext, điều mà một ứng dụng đang tổ chức cuộc trò chuyện giữa các lần chạy không có -- và đó chính xác là khi người dùng gõ /compact. compact_now xây dựng một bối cảnh dùng một lần để chiến lược tương tự mà agent sử dụng có thể được điều khiển từ trình xử lý lệnh:
from pydantic_ai_harness import SummarizingCompaction
from pydantic_ai_harness.compaction import compact_now
strategy = SummarizingCompaction(max_fraction=0.9, keep_messages=20)
history = await compact_now(
strategy,
history,
model='anthropic:claude-sonnet-5',
focus='the auth refactor, not the earlier CSS work',
)
compact_now không áp dụng kích hoạt riêng của nó, vì vậy một chiến lược có compact là các lần chạy vô điều kiện bất kể kích thước lịch sử. Một chiến lược xác định điều kiện dừng của chính nó vẫn tôn trọng nó: TieredCompaction chỉ leo thang cho đến khi lịch sử phù hợp với mục tiêu của nó, vì vậy lịch sử đã nằm trong mục tiêu sẽ không thay đổi. Vượt qua cấp độ trực tiếp nếu bạn cần nó để chạy bất kể.
focus chỉ đạo các chiến lược viết văn xuôi -- SummarizingCompaction, thông qua xuất khẩu SupportsFocus giao thức with_focus -- và bị bỏ qua bởi những người bỏ hoặc để trống nội dung theo quy định, vì họ không có gì để điều khiển. TieredCompaction có thể lấy tiêu điểm khi có bất kỳ cấp nào của nó, do đó, tiêu điểm sẽ đạt đến cấp tóm tắt thay vì dừng lại ở trình bao bọc.
Một sự nén làm thay đổi lịch sử phát ra cùng một compact_messages span mà đường dẫn đang chạy phát ra, do đó, ứng dụng được đo lường sẽ nhìn thấy một hình dạng cho dù quá trình nén đã được kích hoạt. Vượt qua tracer= để ghi lại nó; không có một nhịp sẽ chuyển sang trình theo dõi không hoạt động.
Mặc định được đề xuất: TieredCompaction
Sự đồng thuận trong lĩnh vực (Anthropic, OpenCode, Letta) là xóa và loại bỏ sự trùng lặp trước tiên và chỉ tóm tắt khi điều đó là không đủ. Tóm tắt biến token đầu vào thành token đầu ra, được tính phí cao hơn và được tạo ra theo chuỗi, do đó, nó thực sự đắt tiền. Chiến lược zero-LLM chỉ liên quan đến phía đầu vào rẻ hơn.
TieredCompaction mã hóa sự leo thang đó: nó chạy từng cấp theo thứ tự, đo lại số lượng token sau mỗi cấp và dừng ngay khi cuộc trò chuyện phù hợp target_tokens. Sắp xếp các bậc từ rẻ đến đắt để chỉ đạt được bậc tóm tắt đắt tiền khi lượt đi rẻ không thể lấy lại đủ.
from pydantic_ai import Agent
from pydantic_ai_harness import ClearToolResults, DeduplicateFileReads, SummarizingCompaction, TieredCompaction
from pydantic_ai.messages import ToolCallPart
def my_file_key(call: ToolCallPart) -> str | None:
if call.tool_name != 'read_file':
return None
return call.args_as_dict().get('path')
agent = Agent(
'openai:gpt-5.6-luna',
capabilities=[
TieredCompaction(
tiers=[
DeduplicateFileReads(file_key=my_file_key),
ClearToolResults(max_tokens=1, keep_pairs=3),
SummarizingCompaction(max_messages=1, keep_messages=20), # model inherits the run's
],
target_tokens=120_000,
)
],
)
Một tầng bên trong TieredCompaction được điều khiển trực tiếp bởi người điều phối, đo lại sau mỗi bậc và dừng một lần dưới target_tokens. Của riêng một bậc max_* do đó kích hoạt không liên quan khi nó chạy bên trong TieredCompaction -- đặt nó thành bất cứ điều gì hợp lệ (ví dụ ClearToolResults(max_tokens=1)). Bất kỳ đồ vật nào có async def compact(messages, ctx) -> list[ModelMessage] (cái CompactionStrategy giao thức) có thể là một cấp độ, vì vậy bạn có thể cắm vào cấp độ của riêng mình.
FallbackCompaction: phục hồi khi chiến lược thất bại
TieredCompaction tăng lên khi cấp thành công không thu hồi đủ. FallbackCompaction chỉ tiến bộ khi chiến lược đưa ra một ngoại lệ được chọn bởi fallback_on, mặc định là Pydantic AI ModelAPIError và FallbackExceptionGroup. Cái sau được nâng lên khi mọi mô hình trong một FallbackModel thất bại. Mỗi lần thử sẽ nhận được một danh sách mới chứa các đối tượng thư gốc, do đó những thay đổi ở cấp danh sách do chiến lược thất bại không ảnh hưởng đến dự phòng của nó. Các chiến lược vẫn phải tôn trọng CompactionStrategy hợp đồng và tránh làm thay đổi các đối tượng tin nhắn. Nếu mọi chiến lược đều thất bại, ngoại lệ cuối cùng sẽ được đưa ra lại. Các trường hợp ngoại lệ không khớp, việc hủy và các trường hợp khác BaseException các lớp con đi qua ngay lập tức; fallback_on từ chối các loại không xuất phát từ Exception.
Sử dụng nó như một cấp độ khi phần tóm tắt sẽ quay trở lại dạng cắt ngắn xác định:
from pydantic_ai_harness import FallbackCompaction, SlidingWindowCompaction, SummarizingCompaction
fallback = FallbackCompaction(
fallback_chain=[
SummarizingCompaction(max_messages=1, keep_tokens=20_000),
SlidingWindowCompaction(max_messages=1, keep_tokens=20_000),
]
)
Các trường kích hoạt của chiến lược không được tham khảo khi chiến lược soạn thảo gọi compact trực tiếp. đặt fallback bên trong TieredCompaction để cung cấp cho chuỗi một trình kích hoạt ngữ cảnh hoặc chuyển nó tới compact_now để nén thủ công.
ClampOversizedMessages: sống sót sau một thế hệ chạy trốn
Một phản hồi mô hình duy nhất có khoảng trắng lặp đi lặp lại hoặc một lệnh gọi công cụ có tải trọng khổng lồ có thể tạo ra một phần lớn đến mức yêu cầu tiếp theo vượt quá giới hạn ngữ cảnh của nhà cung cấp. Không có chiến lược nào khác có thể đạt được nó: SlidingWindowCompaction bỏ những tin nhắn cũ nhất nhưng người vi phạm là tin nhắn mới nhất; ClearToolResults chỉ chạm vào công cụ kết quả; WarnNearLimits không bao giờ chỉnh sửa lịch sử; và nuôi dưỡng lịch sử SummarizingCompaction đạt cùng một giới hạn.
ClampOversizedMessages cắt bỏ phần vi phạm tại chỗ, giữ lại phần đầu và phần đuôi bằng một [clamped: removed N of M characters] điểm đánh dấu giữa chúng. Các thế hệ thoái hóa là sự lặp lại có entropy thấp, do đó lát cắt đầu/đuôi mất đi rất ít.
from pydantic_ai import Agent
from pydantic_ai_harness import ClampOversizedMessages
agent = Agent(
'openai:gpt-5.6-terra',
capabilities=[
ClampOversizedMessages(max_part_tokens=50_000, keep_head_chars=2_000, keep_tail_chars=2_000)
],
)
Một bộ phận chỉ được kẹp khi nó quá khổ và chiếc kẹp thực sự làm nó co lại, vì vậy hãy giữ keep_head_chars + keep_tail_chars thấp hơn ngưỡng cho mỗi phần của bạn.
Nó kẹp hai loại bộ phận bên trong mỗi bộ phận ModelResponse:
- Văn bản phản hồi (
TextPart) -- trường hợp quan trọng, phần văn bản phản hồi mô hình chạy trốn. - Đối số lệnh gọi công cụ (
ToolCallPart), khiclamp_tool_call_args=True(mặc định) -- hình dạng hư hỏng tương tự đối với một trọng tải khổng lồ (ví dụ: một chiếc tàu chạy trốnwrite_plan). Các đối số được thay thế bằng một đối tượng JSON nhỏ{"_clamped": "<head>...<tail>"}để chúng giữ nguyên các đối số hàm hợp lệ; cuộc gọi ban đầu đã được thực hiện nên thao tác này chỉ thu nhỏ bản sao lịch sử. Đặtclamp_tool_call_args=Falsechỉ để kẹp văn bản phản hồi. Các bộ phận gọi theo khung - lõisearch_toolsvàload_capabilitycác cuộc gọi -- không bao giờ bị giới hạn, vì các đối số đã nhập của chúng được xác thực khi lịch sử liên tục được khôi phục (ví dụ:StepPersistencesơ yếu lý lịch) và_clampedđối tượng sẽ thất bại trong chuyến đi khứ hồi đó.
Các phần bên yêu cầu (lời nhắc của người dùng, công cụ trả về, lời nhắc hệ thống) cố tình nằm ngoài phạm vi: đầu vào của người dùng không được viết lại một cách âm thầm và việc trả về công cụ quá khổ là công việc của ClearToolResults.
Sử dụng nó như là cấp độ đầu tiên của TieredCompaction, trước ClearToolResults:
from pydantic_ai_harness import ClampOversizedMessages, ClearToolResults, TieredCompaction
TieredCompaction(
tiers=[
ClampOversizedMessages(max_part_tokens=50_000),
ClearToolResults(max_tokens=1, keep_pairs=3),
],
target_tokens=120_000,
)
ClearToolResults: tầng đầu tiên giá rẻ
Đầu ra của công cụ thường chi phối bối cảnh của agent và agent thường có thể chạy lại một công cụ nếu cần lại dữ liệu. ClearToolResults thay thế nội dung của công cụ kết quả cũ nhất bằng một trình giữ chỗ ngắn trong khi vẫn giữ nội dung gần đây nhất keep_pairs cặp công cụ gọi/trả công cụ còn nguyên vẹn. Các lệnh gọi công cụ vẫn được ghép nối với các kết quả hiện đã bị trống, do đó lịch sử vẫn hợp lệ.
Kết quả của công cụ gõ khung - lõi search_tools và load_capability trả về -- được giữ nguyên (một tầng token nhỏ), vì nội dung có cấu trúc của chúng được phân tích lại theo các yêu cầu sau này và viết lại thông qua dataclasses.replace sẽ bỏ qua xác nhận và làm hỏng phần.
from pydantic_ai import Agent
from pydantic_ai_harness import ClearToolResults
agent = Agent(
'openai:gpt-5.6-luna',
capabilities=[ClearToolResults(max_tokens=100_000, keep_pairs=3)],
)
Đặt clear_tool_inputs=True cũng để trống các đối số của các cuộc gọi đã bị xóa và exclude_tools đến một tập hợp các tên công cụ mà kết quả của chúng không bao giờ bị xóa.
DeduplicateFileReads: thả các lần đọc thay thế
Khi cùng một tệp được đọc nhiều lần, chỉ lần đọc mới nhất mới giữ được nội dung của nó; các lần đọc trước đó được làm trống bằng một trình giữ chỗ, với việc ghép nối được giữ nguyên.
Không có mặc định file_key: việc xác định tệp đã đọc là dành riêng cho agent và việc đoán sai sẽ làm mất dữ liệu trực tiếp. Cung cấp ánh xạ có thể gọi được ToolCallPart đến một khóa tập tin ổn định, hoặc None khi cuộc gọi không phải là một tập tin đọc:
from pydantic_ai import Agent
from pydantic_ai.messages import ToolCallPart
from pydantic_ai_harness import DeduplicateFileReads
def file_key(call: ToolCallPart) -> str | None:
if call.tool_name != 'read_file':
return None
return call.args_as_dict().get('path')
agent = Agent('openai:gpt-5.6-terra', capabilities=[DeduplicateFileReads(file_key=file_key)])
không có max_messages hoặc max_tokens bộ kích hoạt, DeduplicateFileReads chạy theo mọi yêu cầu. Nó rẻ và gần như không bị mất mát, vì vậy giá trị mặc định thường là thứ bạn muốn.
SlidingWindowCompaction: chỉ giữ lại phần đuôi gần đây
Khi cuộc trò chuyện vượt quá ngưỡng được định cấu hình, SlidingWindowCompaction loại bỏ toàn bộ thông báo cũ nhất xuống còn một đuôi, giữ nguyên các cặp lệnh gọi công cụ/trả lại công cụ. Hãy tiếp cận nó khi bạn chỉ cần những lượt gần đây và có thể loại bỏ hoàn toàn bối cảnh cũ.
from pydantic_ai import Agent
from pydantic_ai_harness import SlidingWindowCompaction
agent = Agent(
'openai:gpt-5.6-terra',
capabilities=[SlidingWindowCompaction(max_messages=80, keep_messages=40)],
)
Theo mặc định preserve_first_user_message=True giữ lượt của người dùng đầu tiên (ngoài lời nhắc của hệ thống) ngay cả khi nó rơi ra ngoài cửa sổ, do đó agent không bị mất nhiệm vụ ban đầu. Vượt qua keep_tokens thay vì keep_messages để cắt giảm ngân sách token thay vì số lượng tin nhắn.
SummarizingCompaction: nén, không loại bỏ
Khi bối cảnh cũ vẫn còn quan trọng nhưng phải được nén lại, SummarizingCompaction tóm tắt các thông báo cũ hơn bằng lệnh gọi mô hình chuyên dụng và thay thế chúng bằng một bản tóm tắt có cấu trúc duy nhất, duy trì tính toàn vẹn của phần đuôi và lệnh gọi công cụ gần đây. Đây là bậc đắt tiền, vì vậy nó được sử dụng tốt nhất sau những tấm vé rẻ hơn (xem TieredCompaction).
from pydantic_ai import Agent
from pydantic_ai_harness import SummarizingCompaction
agent = Agent(
'anthropic:claude-opus-5',
capabilities=[
SummarizingCompaction(
model='anthropic:claude-sonnet-5',
max_messages=60,
keep_messages=20,
)
],
)
model chấp nhận tên model hoặc một Model; khi còn lại None nó kế thừa mô hình của agent đang chạy. Quá trình chạy tóm tắt lồng nhau của nó kế thừa giới hạn sử dụng cấp độ gốc và dành một yêu cầu từ giới hạn yêu cầu hữu hạn cho yêu cầu cấp độ gốc đang chờ xử lý. Vượt qua model_settings để cung cấp các cài đặt cuộc gọi tóm tắt chuyên dụng khác với các cài đặt mặc định của kiểu máy đó; các cài đặt được cung cấp hợp nhất với các giá trị mặc định của mô hình mà không làm thay đổi mô hình hoặc từ điển cài đặt. Theo mặc định incremental=True cập nhật bản tóm tắt hiện có mới nhất dưới dạng neo. Điều này thay đổi lời nhắc cuộc gọi tóm tắt từ các bản phát hành trước đó; đặt incremental=False để giữ lại hành vi tái sinh trước đó.
Cả hai bề mặt nhắc nhở của yêu cầu tóm tắt đều là các trường: summary_prompt là mẫu do người dùng chuyển (nó phải chứa một {messages} giữ chỗ) và instructions đặt các hướng dẫn tĩnh của agent nội bộ mà Pydantic AI gửi trong lời nhắc hệ thống của yêu cầu. ghi đè instructions khi điểm cuối của trình tóm tắt yêu cầu một lệnh dẫn đầu cố định.
Kế toán sử dụng
Cuộc gọi tóm tắt là một yêu cầu thực sự đối với mô hình, do đó, toàn bộ việc sử dụng nó -- token và chính yêu cầu đó -- được xếp vào phần chạy ctx.usage. Điều này là có chủ ý: nó đảm bảo chi phí trung thực, giữ số lượng yêu cầu nhất quán (một yêu cầu mẫu không được tính là một yêu cầu gây ngạc nhiên) và cho phép một UsageLimits yêu cầu giới hạn bắt một nén chạy trốn. Lần chạy lồng nhau nhận được các giới hạn gốc khác không thay đổi; giới hạn yêu cầu hữu hạn giảm đi một nên nó không thể sử dụng vị trí đã được phê duyệt cho yêu cầu gốc. Do đó, bộ giới hạn yêu cầu chạy hoặc số lần lặp sẽ thấy các lệnh gọi nén trong số các yêu cầu của nó.
WarnNearLimits: cảnh báo thay vì viết lại
WarnNearLimits không bao giờ chỉnh sửa lịch sử. Khi quá trình chạy đạt đến giới hạn đã định cấu hình, nó sẽ đưa ra cảnh báo KHẨN CẤP (sau đó là QUAN TRỌNG) khi người dùng ở cuối lượt, do đó, mô hình sẽ kết thúc thay vì viết lại ngữ cảnh theo nó. Các mô hình có xu hướng chú ý đến thông báo của người dùng hơn là thông báo hệ thống, đó là lý do tại sao cảnh báo sẽ đến lượt người dùng. Các cảnh báo trước đây về khả năng này sẽ bị loại bỏ trước khi quyết định có nên đưa vào một cảnh báo mới hay không.
from pydantic_ai import Agent
from pydantic_ai_harness import WarnNearLimits
agent = Agent(
'google:gemini-3.6-flash',
capabilities=[
WarnNearLimits(
max_iterations=40,
max_context_tokens=100_000,
)
],
)
Cảnh báo bắt đầu lúc warning_threshold (mặc định 0.7, một phần của giới hạn) và trở thành TIÊU CHUẨN cho các lần lặp lại sau khi số lượng yêu cầu còn lại giảm xuống critical_remaining_iterations (mặc định 3). Nó theo dõi ba loại giới hạn -- max_iterations, max_context_tokens (hoặc max_context_fraction) và max_total_tokens -- và theo mặc định sẽ cảnh báo về bất kỳ điều gì được định cấu hình; thu hẹp điều đó với warn_on.
Đánh đổi bộ nhớ đệm
Xóa, loại bỏ trùng lặp, kẹp và tóm tắt tất cả nội dung tin nhắn viết lại, làm mất hiệu lực bộ nhớ đệm nhắc nhở của nhà cung cấp kể từ thời điểm chỉnh sửa trở đi -- yêu cầu tiếp theo sẽ trả phí ghi vào bộ nhớ đệm. cho ClearToolResults, sử dụng min_clear_tokens bỏ qua việc xóa mà lấy lại quá ít để có thể xóa bộ đệm. cho ClampOversizedMessages việc phá sản bộ đệm là không thể tránh khỏi vì giải pháp thay thế là một yêu cầu không thành công.
Truy tìm
Khi thiết bị cốt lõi đang hoạt động ( Instrumentation khả năng, agent.instrumenthoặc Agent.instrument_all()), mỗi chiến lược phát ra một compact_messages trải dài trên dấu vết của đường chạy vào thời điểm nó thực sự thu gọn -- nghĩa là, trong before_model_request, khi vượt quá ngưỡng của chiến lược (ClampOversizedMessages chỉ phát ra khi một phần thực sự được kẹp). TieredCompaction phát ra một khoảng duy nhất cho toàn bộ quá trình leo thang thay vì một khoảng cho mỗi tầng, bởi vì nó điều khiển tốc độ của mỗi tầng compact trực tiếp. Nếu không có thiết bị đo thì thiết bị theo dõi sẽ không hoạt động nên nhịp không tăng thêm chi phí.
Tên nhịp là tĩnh compact_messages; chiến lược là một thuộc tính, không phải là một phần của tên, để giữ số lượng thẻ ở mức thấp. Thuộc tính:
| Thuộc tính | Loại | Ý nghĩa |
|---|---|---|
gen_ai.conversation.compacted | bool | Luôn luôn true; cờ của quy ước GenAI OpenTelemetry cho bối cảnh được nén |
compaction.strategy | str | Tên lớp chiến lược (ví dụ SlidingWindowCompaction, SummarizingCompaction) |
compaction.messages_before | int | Đếm tin nhắn trước khi nén |
compaction.messages_after | int | Số lượng tin nhắn sau khi nén |
compaction.tokens_before | int | Số lượng token ước tính trước khi nén |
compaction.tokens_after | int | Số lượng token ước tính sau khi nén |
gen_ai.conversation.compacted là cờ của quy ước ngữ nghĩa GenAI; phần còn lại là dành riêng cho harness. Số lượng token sử dụng chiến lược tokenizer khi được đặt, nếu không thì heuristic ~4 ký tự cho mỗi token. Nội dung tin nhắn thô không được ghi lại.
Biên lai đầm nén
Nén là một thao tác xóa bộ nhớ mà mô hình không thể phủ quyết và thường không thể phát hiện, điều này dẫn đến sự trôi dạt nối lại -- mô hình kết hợp tính liên tục với lịch sử mà nó không còn nữa. Biên nhận giúp thao tác xóa trở nên dễ đọc: sau khi chiến lược vượt ranh giới viết lại lịch sử, nó sẽ thêm một ghi chú ngắn, xác định ghi lại số lượng đã được nén, cảnh báo rằng những gì còn sót lại là đồ cũ và -- khi đính kèm nhà cung cấp tay cầm -- một mã định danh cho lịch sử chạy liên tục.
from pydantic_ai_harness import SlidingWindowCompaction, SummarizingCompaction
SummarizingCompaction(max_messages=60, keep_messages=20, receipts=True)
SlidingWindowCompaction(max_messages=80, keep_messages=40, receipts=True)
Văn bản biên nhận không có dấu thời gian, vì vậy đây là một hàm thuần túy của quá trình nén. Phần tin nhắn vẫn có dấu thời gian yêu cầu thông thường.
Từ ngữ tuân theo những gì thực sự tồn tại. SummarizingCompaction để lại một bản tóm tắt, vì vậy biên nhận cho biết bản tóm tắt ở trên là đã qua sử dụng; SlidingWindowCompaction bỏ hoàn toàn lịch sử nên biên nhận của nó cho biết bối cảnh đó đã biến mất. Các chiến lược tại chỗ trống (ClearToolResults, DeduplicateFileReads, ClampOversizedMessages) giữ mọi tin nhắn và không vượt qua ranh giới, vì vậy chúng không phát ra biên nhận.
Đính kèm bất kỳ khả năng nào để lộ compaction_transcript_handle() -> str | None -- cái TranscriptHandleProvider giao thức -- và biên nhận nhận được một Persisted run handle: <handle> con trỏ. StepPersistence thực hiện nó, trả lại nó run_id, thế là gắn vào là đủ. Mỗi biên lai cũng được phát ra dưới dạng compaction.receipt sự kiện trên compact_messages nhịp, mang theo compaction.receipt.strategy, compaction.receipt.messages_dropped, compaction.receipt.tokens_dropped, compaction.receipt.byvà compaction.receipt.handle khi một tay cầm được tìm thấy.
Phần xử lý đề cập đến chạy liên tục, không phải là bản chép lại nguyên sơ. Các chỉnh sửa của quá trình nén vẫn tồn tại trong lịch sử tin nhắn của lần chạy, do đó ảnh chụp nhanh mới nhất của lần chạy phản ánh lịch sử được nén -- việc đọc lại không khôi phục được những gì biên nhận cho biết đã bị loại bỏ. Cửa hàng lưu giữ ảnh chụp nhanh theo từng bước vẫn có thể giữ các bước nén trước, tùy thuộc vào khả năng lưu giữ của chính nó (max_snapshots_per_run trên các cửa hàng được vận chuyển). Hãy coi tay cầm như một con trỏ để chạy và kiểm tra khả năng lưu giữ của cửa hàng của bạn trước khi hứa với agent rằng họ có thể đọc bản gốc.
Biên nhận by phân bổ sử dụng phương pháp phỏng đoán họ thô giống như tiền tố cầu nối, với cùng các giá trị gần đúng -- xem ghi chú bên dưới.
Biên lai được chọn tham gia (receipts=False theo mặc định) vì văn bản biên nhận là nội dung: từ ngữ chính xác là tạm thời trong khi chờ vượt qua tiêu chuẩn eval-rig chuẩn, trong khi cơ chế mang tính cấu trúc.
Ghim: nội dung tồn tại sau quá trình nén
pin đánh dấu nội dung mà mọi chiến lược được vận chuyển phải bảo tồn:
from pydantic_ai_harness.compaction import pin
# In a ModelRequest placed in the run's message history (by a capability or the user):
pinned = pin('Durable task state the model must never lose across compaction.')
Một phần được ghim không bao giờ được tóm tắt hoặc bỏ đi; nếu một chiến lược đã loại bỏ nó thì chiến lược đó sẽ đưa nó lại ở gần đầu lịch sử còn sót lại. is_pinned báo cáo liệu một phần nhất định có mang điểm đánh dấu hay không.
Ghim sử dụng mô hình vô hình TextContent.metadata, vì vậy nội dung của chúng vẫn giữ nguyên bối cảnh người dùng thông thường trong khi việc nén có thể phân biệt chúng với lượt của người dùng.
các Planning khả năng không cần ghim: kế hoạch của nó được đưa lại nhanh chóng theo mọi yêu cầu trong wrap_model_request, vì vậy nó đã sống sót sau quá trình nén chặt bằng cách xây dựng. Ghim dành cho trạng thái tác vụ lâu dài và các bảng ghi nhớ tồn tại trong lịch sử.
Giữ tin nhắn của người dùng (keep_user_messages)
Lượt người dùng là nội dung có tín hiệu trên mỗi token cao nhất trong cuộc trò chuyện và việc mất lượt người dùng là nguyên nhân chính dẫn đến việc tiếp tục trôi dạt. SummarizingCompaction(keep_user_messages=True) duy trì các lượt chuyển đổi của người dùng mới nhất từ tiền tố tóm tắt cùng với phần tóm tắt. Họ tiêu thụ những gì hiện có keep_messages ngân sách đuôi, nhiều nhất là nhiều tin nhắn người dùng được giữ lại và tin nhắn đuôi tồn tại cùng nhau; do đó quá trình nén không phát triển các bản sao được giữ lại trên mỗi chu kỳ. Khi nào keep_tokens được đặt, những tin nhắn người dùng được giữ lại và tin nhắn đuôi đó cũng chia sẻ ngân sách token của nó; thay vào đó, lượt người dùng không phù hợp sẽ được tóm tắt. Mỗi lượt giữ lại được giới hạn ở keep_user_messages_max_chars (mặc định 20k) bằng dấu cắt ngắn rõ ràng khi vượt quá. Ngân sách ký tự áp dụng cho mỗi phần và được chia sẻ trên các mục văn bản của lời nhắc nhiều phần; hình ảnh, âm thanh và các điểm bộ nhớ đệm đi qua mà không bị ảnh hưởng. Điều này thay thế preserve_first_user_message, chỉ giữ lượt đầu tiên.
from pydantic_ai_harness import SummarizingCompaction
SummarizingCompaction(max_tokens=120_000, keep_messages=20, keep_user_messages=True)
Việc giữ lại các lượt của người dùng để lại bản tóm tắt, bất kỳ biên nhận nào và các lượt được giữ lại như liền kề ModelRequests. Các nhà cung cấp yêu cầu một yêu cầu mỗi lượt -- Bedrock Converse và Gemini trong số đó -- không bao giờ thấy hình dạng đó: Pydantic AI bình thường hóa lịch sử với _merge_consecutive_messages sau before_model_request hook chạy, kết hợp các yêu cầu liền kề thành một lượt trước khi gửi đi. keep_user_messages do đó không cần xử lý cụ thể của nhà cung cấp.
Tóm tắt gia tăng cố định và cầu nối mô hình chéo
Với incremental=True (mặc định), bản tóm tắt trước đó không được tóm tắt lại -- việc tóm tắt tóm tắt sẽ phân rã theo các phép nén liên tiếp. Nó được đưa trở lại như một cái neo <previous-summary> chặn bằng lệnh cập nhật: giữ nguyên các chi tiết vẫn còn đúng, xóa các chi tiết cũ, hợp nhất các dữ kiện mới. Bản tóm tắt trở thành một tài liệu sống được cập nhật tại chỗ theo một cấu trúc cố định.
!!! lưu ý “Thay đổi hành vi: incremental=True là mặc định"
Mỗi hiện có SummarizingCompaction người dùng nhận được lời nhắc cuộc gọi tóm tắt khác với bản phát hành này vào ngày: sau khi có bản tóm tắt trước đó, trình tóm tắt sẽ được yêu cầu cập nhật bản tóm tắt đó trong <previous-summary> thay vì tạo lại một từ cuộc trò chuyện. Các bản tóm tắt nó tạo ra sẽ có cách đọc khác nhau. Đặt incremental=False để giữ hành vi tái tạo lại từ đầu.
bridge_prefix=True chỉ thêm một ghi chú một dòng vào phần tóm tắt khi họ mô hình của trình tóm tắt khác với họ tạo ra lịch sử, bắt nguồn từ họ mẫu của lịch sử. model_name và cấu hình tóm tắt. Nó đánh dấu bản tóm tắt là một bản chuyển giao giữa các mô hình để mô hình tiếp tục xây dựng dựa trên nó thay vì nhầm lẫn rằng nó đã tự thực hiện công việc đó. Nó không bao giờ kích hoạt trong trường hợp cùng kiểu máy thông thường, vì vậy nó rẻ. Nó mặc định là False vì ghi chú là nội dung nhắc nhở.
token gia đình là một giá trị gần đúng: bỏ bất kỳ provider: tiền tố, sau đó lấy token hàng đầu trước token đầu tiên - hoặc /. Nó ngăn cách gpt từ claude trên các tài liệu tham khảo thông thường (openai:gpt-5.6-luna -> gpt, google:gemini-3.6-flash -> gemini) và đọc sai một số số thực -- us.anthropic.claude-sonnet-4-5-v1:0 giảm xuống 0, ollama/llama3 để ollama, và một fallback: model string vào mô hình được liệt kê cuối cùng thay vì mô hình đầu tiên. MỘT FallbackModel đối tượng được đọc chính xác, từ mô hình đầu tiên của nó. Vì vậy, việc ghi nhận cầu nối và biên nhận là nỗ lực tốt nhất: một họ đọc sai có thể loại bỏ một ghi chú cầu nối hoặc kích hoạt một ghi chú giữa hai mô hình cùng một gia đình. Cả hai kết quả đều không thay đổi việc nén giữ hay giảm.
Giống như các biên nhận, hướng dẫn cập nhật và từ ngữ tiền tố cầu nối là nội dung, được vận chuyển ở mức tối thiểu và trung tính trong khi chờ vượt qua giàn khoan eval-rig; các cơ chế neo đậu và kiểm soát gia đình đều mang tính cấu trúc.
Ngoài phạm vi
Các chiến lược này nén hoặc thả ngữ cảnh bên trong cửa sổ. Di chuyển các đầu ra công cụ lớn ra khỏi cửa sổ -- tràn chúng vào một tệp mà agent (hoặc subagent) có thể truy vấn theo yêu cầu -- là một khả năng riêng biệt (giới hạn đầu ra công cụ), không bị cắt bớt mất dữ liệu. Thích nó hơn là giới hạn đầu ra của từng công cụ.
Tham chiếu API
Mặc định được đề xuất là TieredCompaction; các chiến lược khác bên dưới có thể được sử dụng độc lập hoặc được kết hợp dưới dạng các cấp độ của nó.
::: pydantic_ai_harness.compaction.TieredCompaction
::: pydantic_ai_harness.compaction.ClampOversizedMessages
::: pydantic_ai_harness.compaction.ClearToolResults
::: pydantic_ai_harness.compaction.DeduplicateFileReads
::: pydantic_ai_harness.compaction.SlidingWindowCompaction
::: pydantic_ai_harness.compaction.SummarizingCompaction
::: pydantic_ai_harness.compaction.WarnNearLimits
::: pydantic_ai_harness.compaction.ReportContextUsage
::: pydantic_ai_harness.compaction.ContextUsage
::: pydantic_ai_harness.compaction.pin
::: pydantic_ai_harness.compaction.is_pinned
::: pydantic_ai_harness.compaction.reinject_pinned
::: pydantic_ai_harness.compaction.TranscriptHandleProvider