返回 DeepSeek-Reasonix
fold_ladder.go
根目录 / internal / agent / fold_ladder.go
1 package agent
2
3 import "reasonix/internal/provider"
4
5 // foldRequest is what one fold attempt asks of the summarizer. force shrinks
6 // the verbatim tail, mustFree caps the summary input to the safe prefix, and
7 // allowChunked permits the multi-request fragment path after a size failure.
8 type foldRequest struct {
9 force, mustFree, allowChunked bool
10 // slim renders the fold as one bounded transcript instead of the
11 // cache-aligned replay. It is a rung on the overflow ladder, never a default.
12 slim bool
13 }
14
15 // summaryInputModeFor labels the summarizer input for telemetry and the
16 // chunked-fallback gate. The slim rung overrides the replay-shape labels.
17 func summaryInputModeFor(req foldRequest, pinned, rewritten bool) string {
18 switch {
19 case req.slim:
20 return SummaryInputSlim
21 case pinned:
22 return SummaryInputNonPrefix
23 case rewritten:
24 return SummaryInputExtensionRewritten
25 default:
26 return SummaryInputCachePrefix
27 }
28 }
29
30 // Overflow ladder for one maintenance transaction: replay-form summaries first
31 // (each re-planned on the calibration a provider overflow just corrected),
32 // then one transcript-form summary, then the fragment path when allowed.
33 const maxSummaryReplans = 2
34
35 // summaryLadder paces the fold attempts of one maintenance transaction and
36 // absorbs provider overflows by moving to the next rung instead of failing.
37 type summaryLadder struct {
38 remaining int // successful summaries still allowed by the trigger's policy
39 replans int // overflow re-plans consumed
40 slim bool
41 }
42
43 func newSummaryLadder(maxSummaries int) *summaryLadder {
44 return &summaryLadder{remaining: maxSummaries}
45 }
46
47 // next reports whether another fold attempt may start and consumes one slot.
48 func (l *summaryLadder) next() bool {
49 if l.remaining <= 0 {
50 return false
51 }
52 l.remaining--
53 return true
54 }
55
56 func (l *summaryLadder) request(force, mustFree, allowChunked bool) foldRequest {
57 return foldRequest{force: force, mustFree: mustFree, allowChunked: allowChunked, slim: l.slim}
58 }
59
60 // absorbOverflow moves to the next rung after a provider overflow and returns
61 // the slot it consumed, so the caller retries without spending a summary. A
62 // re-plan is only worth a request when the reply carried the prompt count
63 // that recalibrates it; otherwise the transcript form is the next rung.
64 func (l *summaryLadder) absorbOverflow(err error) bool {
65 limit := provider.AsContextLimitError(err)
66 if limit == nil {
67 return false
68 }
69 switch {
70 case limit.PromptTokens > 0 && l.replans < maxSummaryReplans && !l.slim:
71 l.replans++
72 case !l.slim:
73 l.slim = true
74 default:
75 return false
76 }
77 l.remaining++
78 return true
79 }
80
80 lines GO