返回 DeepSeek-Reasonix
context_recovery.go
根目录 / internal / agent / context_recovery.go
1 package agent
2
3 import (
4 "context"
5 "fmt"
6
7 "reasonix/internal/event"
8 "reasonix/internal/i18n"
9 "reasonix/internal/provider"
10 )
11
12 type contextRecoveryBudget struct {
13 retries int
14 }
15
16 func (a *Agent) recoverContextLimit(ctx context.Context, frozen samplingRequest, err error, budget *contextRecoveryBudget) (samplingRequest, bool, string) {
17 limit := provider.AsContextLimitError(err)
18 if a == nil || limit == nil || budget == nil {
19 return samplingRequest{}, false, contextRecoveryFailed
20 }
21 omitted := frozen.req.MaxTokens == 0
22 if limit.PromptTokens > 0 {
23 a.setPromptTokenCalibrationFromActive(limit.PromptTokens)
24 }
25 a.learnContextBudget(limit.WindowTokens, limit.CompletionTokens, omitted)
26 adm := a.lastAdmission()
27 adm.ObservedWindow = limit.WindowTokens
28 adm.ObservedPrompt = limit.PromptTokens
29 adm.ObservedCompletion = limit.CompletionTokens
30 a.storeAdmission(adm)
31
32 window := a.effectiveContextWindow()
33 prompt := limit.PromptTokens
34 if prompt <= 0 {
35 prompt = a.estimatedRequestTokens(frozen.req)
36 }
37 physical := window - prompt - outputBudgetReserve
38 // An overflow without token numbers cannot size a retry: the estimate that
39 // admitted the request is the number the provider just rejected.
40 if limit.PromptTokens <= 0 && limit.WindowTokens <= 0 {
41 physical = 0
42 }
43 if physical > 0 && budget.retries == 0 {
44 next := freezeProviderRequest(frozen.req)
45 next.MaxTokens = physical
46 if frozen.req.MaxTokens > 0 && frozen.req.MaxTokens < physical {
47 next.MaxTokens = frozen.req.MaxTokens
48 }
49 budget.retries++
50 // Publish the request that will actually be retried, not the stale
51 // pre-error admission. The Context Panel reads this atomic snapshot while
52 // the turn is still active and after it completes.
53 adm.WindowMode = provider.ContextWindowShared.String()
54 adm.Source = provider.ContextBudgetSourceLearned
55 adm.WindowTokens = window
56 adm.PromptTokens = prompt
57 adm.PhysicalRemaining = physical
58 if adm.RequestedOutputTokens <= 0 {
59 adm.RequestedOutputTokens = limit.CompletionTokens
60 }
61 if omitted && adm.AutoOutputTokens <= 0 {
62 adm.AutoOutputTokens = limit.CompletionTokens
63 }
64 adm.EffectiveOutputTokens = next.MaxTokens
65 adm.Clipped = adm.RequestedOutputTokens > 0 && next.MaxTokens < adm.RequestedOutputTokens
66 adm.ApplyMaxTokens = next.MaxTokens > 0
67 adm.LastRecovery = contextRecoveryLearnedRetry
68 a.storeAdmission(adm)
69 a.emitContextRecoveryNotice(contextRecoveryLearnedRetry, limit, next.MaxTokens)
70 shape := a.requestCalibrationShape(next)
71 a.sess.output.activeReqShape.Store(&shape)
72 return samplingRequest{req: next}, true, contextRecoveryLearnedRetry
73 }
74 if physical <= 0 && budget.retries == 0 {
75 startProjectionVersion := a.currentProjectionVersion()
76 if _, perr := a.contextManager().Prepare(ctx, ContextPreparePolicy{
77 Trigger: CompactionTriggerOverflow,
78 Force: true,
79 }); perr != nil {
80 a.setLastRecovery(contextRecoveryFailed)
81 return samplingRequest{}, false, contextRecoveryFailed
82 }
83 if a.currentProjectionVersion() <= startProjectionVersion {
84 a.setLastRecovery(contextRecoveryFailed)
85 return samplingRequest{}, false, contextRecoveryFailed
86 }
87 rebuilt, rerr := a.buildSamplingRequest(ctx, CompactionTriggerPressure)
88 if rerr != nil {
89 a.setLastRecovery(contextRecoveryFailed)
90 return samplingRequest{}, false, contextRecoveryFailed
91 }
92 if aerr := a.applyAdmissionToRequest(&rebuilt.req); aerr != nil {
93 a.setLastRecovery(contextRecoveryFailed)
94 return samplingRequest{}, false, contextRecoveryFailed
95 }
96 budget.retries++
97 a.setLastRecovery(contextRecoveryCompacted)
98 a.emitContextRecoveryNotice(contextRecoveryCompacted, limit, rebuilt.req.MaxTokens)
99 shape := a.requestCalibrationShape(rebuilt.req)
100 a.sess.output.activeReqShape.Store(&shape)
101 return samplingRequest{req: freezeProviderRequest(rebuilt.req)}, true, contextRecoveryCompacted
102 }
103 a.setLastRecovery(contextRecoveryFailed)
104 return samplingRequest{}, false, contextRecoveryFailed
105 }
106
107 func (a *Agent) emitContextRecoveryNotice(kind string, limit *provider.ContextLimitError, nextOutput int) {
108 if a == nil || a.svc.sink == nil {
109 return
110 }
111 text := i18n.M.ContextRecoveryAdjustBudget
112 if kind == contextRecoveryCompacted {
113 text = i18n.M.ContextRecoveryCompacted
114 }
115 detail := fmt.Sprintf("recovery=%s next_output=%d", kind, nextOutput)
116 if limit != nil {
117 detail = fmt.Sprintf("%s window=%d prompt=%d completion=%d requested=%d",
118 detail, limit.WindowTokens, limit.PromptTokens, limit.CompletionTokens, limit.RequestedTokens)
119 }
120 a.svc.sink.Emit(event.Event{Kind: event.Notice, Level: event.LevelInfo, Text: text, Detail: detail})
121 }
122
122 lines GO