
Pydantic AI Gửi Lại Toàn Bộ History Mỗi Bước: Chi Phí O(n²)
Mỗi run của Pydantic AI giữ một danh sách hội thoại và gửi lại toàn bộ ở mỗi bước, khiến chi phí token tăng theo bình phương số bước chạy của agent.
Agent Pydantic AI của bạn chạy đúng, test xanh, nhưng hoá đơn token tăng nhanh hơn số bước. Lý do nằm trong run graph: mỗi run giữ đúng một danh sách hội thoại và gửi lại toàn bộ danh sách đó ở mỗi bước.
Một danh sách, mỗi lượt thêm hai lần
Tác giả bài phân tích cho biết đã đọc trực tiếp run graph — "pydantic_ai_slim/pydantic_ai/_agent_graph.py on main" — để xem model thực sự nhận gì. Kết luận: "Each run holds a single mutable conversation list on its state", và ở mỗi bước graph nối thêm hai phần tử, request đi ra rồi response trả về.
Điểm mấu chốt là không có gì bị xoá: "Nothing is removed. The list only grows: request, response, request, response — with tool calls and, crucially, tool outputs riding inside those messages." Tool output — nội dung file, kết quả search, response API — chính là hành lý nặng nhất, và chúng đi kèm mọi bước sau đó.
Vì sao chi phí tăng theo bình phương
Khi dựng input cho lệnh gọi kế tiếp, graph lấy bản sao đầy đủ của lịch sử. Theo bài viết: "A run of n steps sends roughly 1 + 2 + 3 + … + n copies of history — O(n²) cumulative tokens in the step count."
Ngưỡng thực tế mà tác giả đưa ra: "A 3-step agent is fine. A 12-step agent that reads a couple of files is not: each file's contents rides along on every later step."
Và đây là lý do bug này sống lâu trong codebase: "The run still succeeds, your tests still pass — the only artifact is a bigger number on the usage line, and you don't see it until the invoice." Không có exception, không có cảnh báo, không có metric nào đỏ.
Cái nút bạn thật sự có
Pydantic AI trả lại toàn bộ transcript qua result.all_messages(), và mọi run method đều nhận tham số message_history. Theo bài viết, đó chính là đòn bẩy: "across a multi-turn conversation you decide what prior history to replay, so you can pass a trimmed or summarized history into the next run instead of the raw accumulation".
Cần nói rõ giới hạn, và tác giả cũng nói rõ: bên trong một tool-loop sâu, việc gửi lại là bản chất của tool calling — "it's true of every framework". Cái bạn kiểm soát được là ranh giới giữa các run, và kích thước của tool output được đưa vào context.
Tác giả cũng giới thiệu hai công cụ của chính mình cho việc đo và chặn chi phí — một thư viện quy đổi usage ra số tiền mỗi run, và một GitHub Action gắn ngưỡng max-usd để fail CI khi vượt. Đây là sản phẩm của tác giả, không phải khuyến nghị trung lập; nguyên tắc bên dưới thì độc lập với công cụ: đo trước, tranh luận sau.
Kiểm tra trong 10 phút
Mở agent sâu nhất đang chạy và trả lời hai câu hỏi mà bài viết đặt ra: nó chạy bao nhiêu bước, và tool output mà nó gửi lại ở mỗi bước lớn cỡ nào? Nếu một tool đọc file, hãy đo dung lượng file đó rồi nhân với số bước còn lại của run — con số đó là phần bạn đang trả thêm mà không nhận lại giá trị nào.
Hai cách xử lý nằm trong tầm tay ngay hôm nay: tóm tắt hoặc cắt bớt history khi bắt đầu run mới, và trả về tham chiếu thay vì toàn bộ nội dung từ các tool đọc dữ liệu lớn — ví dụ trả đường dẫn kèm đoạn trích thay vì cả file. Cả hai đều là thay đổi ở tầng harness, không phải đổi model.
Không spam, hủy đăng ký bất kỳ lúc nào.


