Tóm tắt nén và phân nhánh
LLM có cửa sổ ngữ cảnh hạn chế. Khi các cuộc trò chuyện quá dài, Pi sử dụng tính năng nén để tóm tắt nội dung cũ hơn trong khi vẫn giữ nguyên tác phẩm gần đây. Trang này bao gồm cả tự động nén và branch summarization.
Tệp nguồn (pi-mono):
packages/coding-agent/src/core/compaction/compaction.ts- Logic tự động nénpackages/coding-agent/src/core/compaction/branch-summarization.ts- Tổng hợp chi nhánhpackages/coding-agent/src/core/compaction/utils.ts- Tiện ích chia sẻ (theo dõi tập tin, tuần tự hóa)packages/coding-agent/src/core/session-manager.ts- Loại mục nhập (CompactionEntry,BranchSummaryEntry)packages/coding-agent/src/core/extensions/types.ts- Các loại sự kiện mở rộng
Để biết các định nghĩa TypeScript trong dự án của bạn, hãy kiểm tra node_modules/@earendil-works/pi-coding-agent/dist/.
Tổng quan
Pi có hai cơ chế tóm tắt:
| Cơ chế | Cò súng | Mục đích |
|---|---|---|
| nén chặt | Ngữ cảnh vượt quá ngưỡng hoặc /compact |
Tóm tắt tin nhắn cũ để giải phóng ngữ cảnh |
| Tóm tắt chi nhánh | /tree điều hướng |
Bảo toàn bối cảnh khi chuyển nhánh |
Cả hai đều sử dụng cùng một định dạng tóm tắt có cấu trúc và theo dõi các hoạt động tập tin một cách tích lũy. Yêu cầu nén và tóm tắt nhánh sử dụng ID phiên định tuyến mới và, nếu được nhà cung cấp hỗ trợ, sẽ vô hiệu hóa tính năng ghi vào bộ nhớ đệm vì những lời nhắc một lần này khó có thể được sử dụng lại.
nén chặt
Khi nó kích hoạt
Kích hoạt tự động nén khi:
contextTokens > contextWindow - reserveTokensTheo mặc định, reserveTokens là 16384 mã thông báo (có thể định cấu hình trong ~/.pi/agent/settings.json hoặc <project-dir>/.pi/settings.json). Điều này nhường chỗ cho phản hồi của LLM.
Bạn cũng có thể kích hoạt thủ công bằng /compact [instructions], trong đó các hướng dẫn tùy chọn tập trung vào phần tóm tắt.
Nó hoạt động như thế nào
- Tìm điểm cắt: Đi lùi từ tin nhắn mới nhất, tích lũy ước tính mã thông báo cho đến khi đạt đến
keepRecentTokens(20k mặc định, có thể định cấu hình trong~/.pi/agent/settings.jsonhoặc<project-dir>/.pi/settings.json) - Trích xuất tin nhắn: Thu thập tin nhắn từ ranh giới được giữ trước đó (hoặc bắt đầu phiên) cho đến điểm cắt
- Tạo bản tóm tắt: Gọi LLM để tóm tắt bằng định dạng có cấu trúc, chuyển bản tóm tắt trước đó dưới dạng ngữ cảnh lặp lại khi có
- Nối mục nhập: Lưu
CompactionEntryvới bản tóm tắt vàfirstKeptEntryId - Tải lại: Tải lại phiên, sử dụng tóm tắt + tin nhắn từ
firstKeptEntryIdtrở đi
Before compaction:
entry: 0 1 2 3 4 5 6 7 8 9
┌─────┬─────┬─────┬─────┬──────┬─────┬──── ─┬──────┬─────┬─────┐
│ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│
└─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┘
└────────┬───────┘ └──────────────┬──────────────┘
messagesToSummarize kept messages
↑
firstKeptEntryId (entry 4)
After compaction (new entry appended):
entry: 0 1 2 3 4 5 6 7 8 9 10
┌─────┬─────┬─────┬─────┬──────┬─────┬──── ─┬──────┬─────┬─────┬─────┐
│ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│ cmp │
└─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┴─────┘
└──────────┬──────┘ └──────────────────────┬───────────────────┘
not sent to LLM sent to LLM
↑
starts from firstKeptEntryId
What the LLM sees:
┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐
│ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │
└────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘
↑ ↑ └─────────────────┬────────────────┘
prompt from cmp messages from firstKeptEntryIdTrong các lần nén lặp lại, khoảng tóm tắt bắt đầu ở ranh giới được giữ lại của lần nén trước đó (firstKeptEntryId), chứ không phải ở chính mục nhập nén, quay trở lại mục nhập sau lần nén trước đó nếu không thể tìm thấy mục nhập được lưu giữ đó trong đường dẫn. Điều này bảo tồn các thông báo còn sót lại sau quá trình nén trước đó bằng cách đưa chúng vào lần tóm tắt tiếp theo. Pi cũng tính toán lại tokensBefore từ bối cảnh phiên được xây dựng lại trước khi viết CompactionEntry mới, do đó số lượng mã thông báo phản ánh bối cảnh thực tế trước khi nén trước khi được thay thế.
Chia lượt
Một "lượt" bắt đầu bằng tin nhắn của người dùng và bao gồm tất cả các phản hồi của trợ lý và lệnh gọi công cụ cho đến tin nhắn tiếp theo của người dùng. Thông thường, độ nén cắt ở ranh giới rẽ.
Khi một lượt vượt quá keepRecentTokens, điểm cắt sẽ xuất hiện ở giữa lượt với thông báo trợ lý. Đây là một "lượt rẽ":
Split turn (one huge turn exceeds budget):
entry: 0 1 2 3 4 5 6 7 8
┌─────┬─────┬─────┬──────┬─────┬──────┬──────┬─────┬──────┐
│ hdr │ usr │ ass │ tool │ ass │ tool │ tool │ ass │ tool │
└─────┴─────┴─────┴──────┴─────┴──────┴──────┴─────┴──────┘
↑ ↑
turnStartIndex = 1 firstKeptEntryId = 7
│ │
└──── turnPrefixMessages (1-6) ───────┘
└── kept (7-8)
isSplitTurn = true
messagesToSummarize = [] (no complete turns before)
turnPrefixMessages = [usr, ass, tool, ass, tool, tool]Đối với các lượt chia tách, Pi tạo hai bản tóm tắt và hợp nhất chúng:
- Tóm tắt lịch sử: Bối cảnh trước đó (nếu có)
- Tóm tắt tiền tố lượt: Phần đầu của lượt chia
Quy tắc điểm cắt
Điểm cắt hợp lệ là:
- Tin nhắn của người dùng
- Tin nhắn trợ lý
- Thông báo thực thi Bash
- Tin nhắn tùy chỉnh (custom_message, Branch_summary)
Không bao giờ cắt ở kết quả công cụ (họ phải tuân theo lệnh gọi công cụ của mình).
Cấu trúc nén chặt
Được xác định trong session-manager.ts:
interface CompactionEntry<T = unknown> {
type: "compaction";
id: string;
parentId: string;
timestamp: number;
summary: string;
firstKeptEntryId: string;
tokensBefore: number;
usage?: Usage; // LLM usage that generated the summary
fromHook?: boolean; // true if provided by extension (legacy field name)
details?: T; // implementation-specific data
}
// Default compaction uses this for details (from compaction.ts):
interface CompactionDetails {
readFiles: string[];
modifiedFiles: string[];
}Extensions có thể lưu trữ bất kỳ dữ liệu có thể tuần tự hóa JSON nào trong details. Việc nén mặc định theo dõi các hoạt động của tệp, nhưng việc triển khai tiện ích mở rộng tùy chỉnh có thể sử dụng cấu trúc riêng của chúng. Các bản tóm tắt được tạo và do tiện ích mở rộng cung cấp sẽ lưu trữ LLM usage của chúng khi có sẵn để tổng số phiên bao gồm cả công việc tóm tắt.
Xem prepareCompaction() và compact() để biết cách triển khai. Đối với tóm tắt trực tiếp theo chương trình, generateSummary() trả về văn bản tóm tắt và generateSummaryWithUsage() trả về { text, usage }.
Tóm tắt chi nhánh
Khi nó kích hoạt
Khi bạn sử dụng /tree để điều hướng đến một nhánh khác, Pi đề xuất tóm tắt công việc bạn sắp hoàn thành. Thao tác này sẽ chèn ngữ cảnh từ nhánh bên trái vào nhánh mới.
Nó hoạt động như thế nào
- Tìm tổ tiên chung: Nút sâu nhất được chia sẻ bởi các vị trí cũ và mới
- Sưu tầm: Đi từ lá già về tổ chung
- Chuẩn bị ngân sách: Bao gồm các tin nhắn tối đa ngân sách mã thông báo (mới nhất trước)
- Tạo tóm tắt: Gọi LLM với định dạng có cấu trúc
- Nối mục nhập: Lưu
BranchSummaryEntrytại điểm điều hướng
Tree before navigation:
┌─ B ─ C ─ D (old leaf, being abandoned)
A ───┤
└─ E ─ F (target)
Common ancestor: A
Entries to summarize: B, C, D
After navigation with summary:
┌─ B ─ C ─ D
A ───┤
└─ E ─ F ─ [summary of B,C,D] (new leaf)Theo dõi tệp tích lũy
Cả tệp nén và tệp theo dõi branch summarization đều được tích lũy. Khi tạo bản tóm tắt, pi trích xuất các thao tác tệp từ:
- Cuộc gọi công cụ trong các tin nhắn đang được tóm tắt
- Việc nén trước hoặc tóm tắt nhánh
details(nếu có)
Điều này có nghĩa là việc theo dõi tệp được tích lũy qua nhiều lần nén hoặc tóm tắt nhánh lồng nhau, lưu giữ toàn bộ lịch sử của các tệp đã đọc và sửa đổi.
Chi nhánhTóm tắtCấu trúc đầu vào
Được xác định trong session-manager.ts:
interface BranchSummaryEntry<T = unknown> {
type: "branch_summary";
id: string;
parentId: string;
timestamp: number;
summary: string;
fromId: string; // Entry we navigated from
usage?: Usage; // LLM usage that generated the summary
fromHook?: boolean; // true if provided by extension (legacy field name)
details?: T; // implementation-specific data
}
// Default branch summarization uses this for details (from branch-summarization.ts):
interface BranchSummaryDetails {
readFiles: string[];
modifiedFiles: string[];
}Tương tự như nén, tiện ích mở rộng có thể lưu trữ dữ liệu tùy chỉnh trong details.
Xem collectEntriesForBranchSummary(), prepareBranchEntries() và generateBranchSummary() để biết cách triển khai.
Định dạng tóm tắt
Cả nén và branch summarization đều sử dụng cùng một định dạng có cấu trúc:
## Goal
[What the user is trying to accomplish]
## Constraints & Preferences
- [Requirements mentioned by user]
## Progress
### Done
- [x] [Completed tasks]
### In Progress
- [ ] [Current work]
### Blocked
- [Issues, if any]
## Key Decisions
- **[Decision]**: [Rationale]
## Next Steps
1. [What should happen next]
## Critical Context
- [Data needed to continue]
<read-files>
path/to/file1.ts
path/to/file2.ts
</read-files>
<modified-files>
path/to/changed.ts
</modified-files>Tuần tự hóa tin nhắn
Trước khi tóm tắt, tin nhắn được tuần tự hóa thành văn bản qua serializeConversation():
[User]: What they said
[Assistant thinking]: Internal reasoning
[Assistant]: Response text
[Assistant tool calls]: read(path="foo.ts"); edit(path="bar.ts", ...)
[Tool result]: Output from toolĐiều này ngăn mô hình coi nó như một cuộc trò chuyện để tiếp tục.
Kết quả của công cụ bị cắt ngắn còn 2000 ký tự trong quá trình tuần tự hóa. Nội dung vượt quá giới hạn đó sẽ được thay thế bằng điểm đánh dấu cho biết số lượng ký tự bị cắt bớt. Điều này giúp duy trì các yêu cầu tóm tắt trong phạm vi ngân sách mã thông báo hợp lý, vì kết quả của công cụ (đặc biệt là từ read và bash) thường là yếu tố đóng góp lớn nhất cho quy mô ngữ cảnh.
Tóm tắt tùy chỉnh qua Extensions
Extensions có thể chặn và tùy chỉnh cả nén và branch summarization. Xem extensions/types.ts để biết định nghĩa loại sự kiện.
phiên_trước_compact
Được kích hoạt trước khi tự động nén hoặc /compact. Có thể hủy bỏ hoặc cung cấp bản tóm tắt tùy chỉnh. Xem SessionBeforeCompactEvent và CompactionPreparation trong tệp loại.
pi.on("session_before_compact", async (event, ctx) => {
const { preparation, branchEntries, customInstructions, reason, willRetry, signal } = event;
// preparation.messagesToSummarize - messages to summarize
// preparation.turnPrefixMessages - split turn prefix (if isSplitTurn)
// preparation.previousSummary - previous compaction summary
// preparation.fileOps - extracted file operations
// preparation.tokensBefore - context tokens before compaction
// preparation.firstKeptEntryId - where kept messages start
// preparation.settings - compaction settings
// branchEntries - all entries on current branch (for custom state)
// reason - "manual" (/compact), "threshold", or "overflow"
// willRetry - whether the aborted turn is retried after compaction (overflow recovery)
// signal - AbortSignal (pass to LLM calls)
// Cancel:
return { cancel: true };
// Custom summary:
return {
compaction: {
summary: "Your summary...",
firstKeptEntryId: preparation.firstKeptEntryId,
tokensBefore: preparation.tokensBefore,
// usage: summaryResponse.usage, // Optional; included in session totals
details: { /* custom data */ },
}
};
});Chuyển tin nhắn thành văn bản
Để tạo bản tóm tắt bằng mô hình của riêng bạn, hãy chuyển tin nhắn thành văn bản bằng cách sử dụng serializeConversation:
import { convertToLlm, serializeConversation } from "@earendil-works/pi-coding-agent";
pi.on("session_before_compact", async (event, ctx) => {
const { preparation } = event;
// Convert AgentMessage[] to Message[], then serialize to text
const conversationText = serializeConversation(
convertToLlm(preparation.messagesToSummarize)
);
// Returns:
// [User]: message text
// [Assistant thinking]: thinking content
// [Assistant]: response text
// [Assistant tool calls]: read(path="..."); bash(command="...")
// [Tool result]: output text
// Now send to your model for summarization
const { summary, usage } = await myModel.summarize(conversationText);
return {
compaction: {
summary,
firstKeptEntryId: preparation.firstKeptEntryId,
tokensBefore: preparation.tokensBefore,
usage,
}
};
});Xem custom-compaction.ts để biết ví dụ hoàn chỉnh sử dụng mô hình khác.
phiên_trước_tree
Đã kích hoạt trước /tree điều hướng. Luôn kích hoạt bất kể người dùng có chọn tóm tắt hay không. Có thể hủy điều hướng hoặc cung cấp tóm tắt tùy chỉnh.
pi.on("session_before_tree", async (event, ctx) => {
const { preparation, signal } = event;
// preparation.targetId - where we're navigating to
// preparation.oldLeafId - current position (being abandoned)
// preparation.commonAncestorId - shared ancestor
// preparation.entriesToSummarize - entries that would be summarized
// preparation.userWantsSummary - whether user chose to summarize
// Cancel navigation entirely:
return { cancel: true };
// Provide custom summary (only used if userWantsSummary is true):
if (preparation.userWantsSummary) {
return {
summary: {
summary: "Your summary...",
// usage: summaryResponse.usage, // Optional; included in session totals
details: { /* custom data */ },
}
};
}
});Xem SessionBeforeTreeEvent và TreePreparation trong tệp loại.
Cài đặt
Định cấu hình nén trong ~/.pi/agent/settings.json hoặc <project-dir>/.pi/settings.json:
{
"compaction": {
"enabled": true,
"reserveTokens": 16384,
"keepRecentTokens": 20000
}
}| Cài đặt | Mặc định | Sự miêu tả |
|---|---|---|
enabled |
true |
Bật tính năng tự động nén |
reserveTokens |
16384 |
Mã thông báo để dự trữ cho phản hồi LLM |
keepRecentTokens |
20000 |
Mã thông báo gần đây cần giữ (không được tóm tắt) |
Tắt tính năng tự động nén bằng "enabled": false. Bạn vẫn có thể nén thủ công bằng /compact.