返回 DeepSeek-Reasonix
compact_safe_prefix.go
根目录 / internal / agent / compact_safe_prefix.go
1 package agent
2
3 import (
4 "fmt"
5
6 "reasonix/internal/provider"
7 )
8
9 // summaryPlanMarginRatio is the planning headroom left under the window for
10 // estimator error. The fixed protocol reserve alone is under 1% of a 1M
11 // window, thinner than the tokenizer drift real sessions show (#9818).
12 const summaryPlanMarginRatio = 0.05
13
14 func summaryPlanReserve(window int) int {
15 return max(protocolReserveTokens, int(float64(window)*summaryPlanMarginRatio))
16 }
17
18 // maximumSafeSummaryPrefixEnd returns the largest balanced contiguous prefix
19 // whose exact summary request leaves the collector's minimum output budget.
20 // The remaining middle and tail stay verbatim in the projection.
21 func (a *Agent) maximumSafeSummaryPrefixEnd(msgs []provider.Message, head, end int, instructions string) int {
22 if head < 0 || end <= head || end > len(msgs) {
23 return end
24 }
25 maxPromptTokens, enforce := a.safeSummaryPromptTokenLimit()
26 if !enforce {
27 return end
28 }
29 if maxPromptTokens <= 0 {
30 return head
31 }
32 fits := func(candidate int) bool {
33 fold, _ := withoutPinnedContextRevisions(msgs[head:candidate])
34 request := a.summaryRequest(fold, instructions)
35 return a.estimatedRequestTokens(request) <= maxPromptTokens
36 }
37 if fits(end) {
38 return end
39 }
40
41 low, high, best := head+1, end-1, head
42 for low <= high {
43 mid := low + (high-low)/2
44 if fits(mid) {
45 best = mid
46 low = mid + 1
47 } else {
48 high = mid - 1
49 }
50 }
51 // A tail beginning with a tool result would split it from the assistant
52 // tool-call message. Move the fold boundary back across the whole result
53 // group; the assistant call and all of its results then remain together.
54 for best > head && best < len(msgs) && msgs[best].Role == provider.RoleTool {
55 best--
56 }
57 return best
58 }
59
60 // safeSummaryPromptTokenLimit is shared by prefix planning and the final
61 // post-extension guard. Unknown gateways conservatively honor the configured
62 // or learned window; explicitly independent providers retain the full fold.
63 func (a *Agent) safeSummaryPromptTokenLimit() (int, bool) {
64 window := a.effectiveContextWindow()
65 if window <= 0 || contextBudgetPolicyOf(a.svc.prov).WindowMode == provider.ContextWindowIndependent {
66 return 0, false
67 }
68 return window - a.summaryOutputBudget() - summaryPlanReserve(window), true
69 }
70
71 // validateSafeSummaryRequest guards the final fold in the request form that
72 // will actually be sent.
73 func (a *Agent) validateSafeSummaryRequest(fold []provider.Message, instructions string, slim bool) error {
74 maxPromptTokens, enforce := a.safeSummaryPromptTokenLimit()
75 if !enforce {
76 return nil
77 }
78 request := a.summaryRequest(fold, instructions)
79 if slim {
80 request = a.slimSummaryRequest(fold, instructions)
81 }
82 requestTokens := a.estimatedRequestTokens(request)
83 if maxPromptTokens <= 0 || requestTokens > maxPromptTokens {
84 return fmt.Errorf("%w: prepared summary request (%d tokens) exceeds safe prompt budget (%d)",
85 errCheckpointRejected, requestTokens, maxPromptTokens)
86 }
87 return nil
88 }
89
89 lines GO