返回 DeepSeek-Reasonix
output_budget.go
根目录 / internal / agent / output_budget.go
1 package agent
2
3 import (
4 "fmt"
5 "math"
6 "strings"
7 "sync"
8 "sync/atomic"
9 "time"
10 "unicode/utf8"
11
12 "reasonix/internal/nilutil"
13 "reasonix/internal/provider"
14 )
15
16 const (
17 outputBudgetReserve = 8 * 1024
18 minOutputBudgetReserve = protocolReserveTokens
19 )
20
21 const learnedOutputBudgetTTL = 24 * time.Hour
22
23 type learnedOutputBudgetCacheEntry struct {
24 completionBudget int
25 expiresAt time.Time
26 }
27
28 var learnedOutputBudgetCache = struct {
29 sync.Mutex
30 entries map[string]learnedOutputBudgetCacheEntry
31 }{entries: make(map[string]learnedOutputBudgetCacheEntry)}
32
33 type outputBudgetState struct {
34 outputBudget int
35 // lastUsage caches the latest provider telemetry for per-turn readouts.
36 // The run loop writes it while a frontend reads it, so it is atomic.
37 lastUsage atomic.Pointer[provider.Usage]
38 activeReqShape atomic.Pointer[requestCalibrationShape]
39 promptCalibration atomic.Pointer[promptTokenCalibration]
40 contextUsage atomic.Pointer[contextUsage] // gauge's memoised prompt size
41 learned atomic.Pointer[learnedContextBudget]
42 admission atomic.Pointer[contextAdmission]
43 }
44
45 // learnedContextBudget is an Agent-local observation of the live provider/model
46 // window. Completion limits are additionally shared through the short-lived
47 // provider/model cache below so a model rebuild does not immediately repeat a
48 // known over-limit request. The cache is deliberately in-memory and expires
49 // after one day; it is not a persisted global provider limit.
50 type learnedContextBudget struct {
51 windowTokens int
52 completionBudget int
53 }
54
55 const (
56 contextRecoveryNone = "none"
57 contextRecoveryProactiveClip = "proactive_clip"
58 contextRecoveryLearnedRetry = "learned_retry"
59 contextRecoveryCompacted = "compacted"
60 contextRecoveryFailed = "failed"
61 )
62
63 type contextAdmission struct {
64 WindowMode string
65 LimitMode string
66 Source string
67 WindowTokens int
68 PromptTokens int
69 AutoOutputTokens int
70 MaxOutputTokens int
71 RequestedOutputTokens int
72 EffectiveOutputTokens int
73 ReserveTokens int
74 PhysicalRemaining int
75 Clipped bool
76 ApplyMaxTokens bool
77 LastRecovery string
78 ObservedWindow int
79 ObservedPrompt int
80 ObservedCompletion int
81 }
82
83 type promptTokenCalibration struct {
84 promptTokens int
85 requestChars int64
86 compactChars int64
87 cjkRunes int64
88 cjkBytes int64
89 }
90
91 // requestCalibrationShape pairs the conservative provider-visible text and CJK
92 // composition used for overflow protection with the legacy content-only shape
93 // used by fold economics. Keeping them in one immutable pointer ensures readers
94 // never combine calibration fields from different prepared requests.
95 type requestCalibrationShape struct {
96 requestChars int64
97 compactChars int64
98 cjkRunes int64
99 cjkBytes int64
100 }
101
102 // reset drops what belongs to the transcript being replaced. Prompt-token
103 // calibration and the learned window are properties of the bound model/provider,
104 // and a model switch rebuilds the Agent, so they outlive SetSession. Admission
105 // describes one transcript's latest request and must never bleed into the next.
106 func (o *outputBudgetState) reset() {
107 o.lastUsage.Store(nil)
108 o.activeReqShape.Store(nil)
109 o.admission.Store(nil)
110 }
111
112 func (a *Agent) setPromptTokenCalibration(promptTokens int, shape requestCalibrationShape) {
113 if a == nil || promptTokens <= 0 || shape.requestChars <= 0 {
114 return
115 }
116 a.sess.output.promptCalibration.Store(&promptTokenCalibration{
117 promptTokens: promptTokens,
118 requestChars: shape.requestChars,
119 compactChars: shape.compactChars,
120 cjkRunes: shape.cjkRunes,
121 cjkBytes: shape.cjkBytes,
122 })
123 }
124
125 func (a *Agent) setPromptTokenCalibrationFromActive(promptTokens int) {
126 if a == nil {
127 return
128 }
129 if shape := a.sess.output.activeReqShape.Load(); shape != nil {
130 a.setPromptTokenCalibration(promptTokens, *shape)
131 }
132 }
133
134 // setPromptTokenCalibrationFromUsage trusts provider telemetry only;
135 // reconstructed usage remains available for accounting but not admission.
136 func (a *Agent) setPromptTokenCalibrationFromUsage(usage *provider.Usage) {
137 if a == nil || usage == nil || usage.Estimated {
138 return
139 }
140 a.setPromptTokenCalibrationFromActive(usage.LatestPromptTokens())
141 }
142
143 func outputBudgetOf(p provider.Provider) int {
144 if nilutil.IsNil(p) {
145 return 0
146 }
147 if budget, ok := p.(provider.OutputBudgetProvider); ok {
148 return budget.OutputBudget()
149 }
150 return 0
151 }
152
153 func sharesContextWindow(p provider.Provider) bool {
154 return contextBudgetPolicyOf(p).WindowMode == provider.ContextWindowShared
155 }
156
157 func contextBudgetPolicyOf(p provider.Provider) provider.ContextBudgetPolicy {
158 if nilutil.IsNil(p) {
159 return provider.ContextBudgetPolicy{}
160 }
161 return provider.ResolveContextBudgetPolicy(p)
162 }
163
164 func (a *Agent) learnOutputBudget(limit int) {
165 if a == nil || limit <= 0 {
166 return
167 }
168 cacheLearnedOutputBudget(outputBudgetCacheKey(a), limit)
169 current := a.sess.output.learned.Load()
170 if current != nil && current.completionBudget > 0 && current.completionBudget <= limit {
171 return
172 }
173 learned := &learnedContextBudget{completionBudget: limit}
174 if current != nil {
175 learned.windowTokens = current.windowTokens
176 }
177 a.sess.output.learned.Store(learned)
178 }
179
180 func outputBudgetCacheKey(a *Agent) string {
181 if a == nil {
182 return ""
183 }
184 providerName := ""
185 if !nilutil.IsNil(a.svc.prov) {
186 providerName = strings.TrimSpace(a.svc.prov.Name())
187 }
188 modelRef := strings.TrimSpace(a.modelRef)
189 if providerName == "" && modelRef == "" {
190 return ""
191 }
192 // Provider names are route-specific for the built-in OpenCode Go entries;
193 // retaining modelRef as a second component keeps custom routes isolated too.
194 return providerName + "|" + modelRef
195 }
196
197 func cacheLearnedOutputBudget(key string, limit int) {
198 if strings.TrimSpace(key) == "" || limit <= 0 {
199 return
200 }
201 now := time.Now()
202 learnedOutputBudgetCache.Lock()
203 defer learnedOutputBudgetCache.Unlock()
204 if current, ok := learnedOutputBudgetCache.entries[key]; ok && current.expiresAt.After(now) && current.completionBudget > 0 && current.completionBudget <= limit {
205 return
206 }
207 learnedOutputBudgetCache.entries[key] = learnedOutputBudgetCacheEntry{
208 completionBudget: limit,
209 expiresAt: now.Add(learnedOutputBudgetTTL),
210 }
211 }
212
213 func cachedLearnedOutputBudget(key string) int {
214 if strings.TrimSpace(key) == "" {
215 return 0
216 }
217 now := time.Now()
218 learnedOutputBudgetCache.Lock()
219 defer learnedOutputBudgetCache.Unlock()
220 entry, ok := learnedOutputBudgetCache.entries[key]
221 if !ok {
222 return 0
223 }
224 if !entry.expiresAt.After(now) {
225 delete(learnedOutputBudgetCache.entries, key)
226 return 0
227 }
228 return entry.completionBudget
229 }
230
231 func sharedWindowInputPolicyOf(p provider.Provider) provider.SharedWindowInputPolicy {
232 if nilutil.IsNil(p) {
233 return provider.SharedWindowInputPolicy{}
234 }
235 policy, ok := p.(provider.SharedWindowInputPolicyProvider)
236 if !ok {
237 return provider.SharedWindowInputPolicy{}
238 }
239 return policy.SharedWindowInputPolicy()
240 }
241
242 func requestCalibrationShapeOf(req provider.Request) requestCalibrationShape {
243 return requestCalibrationShapeWithPolicy(req, provider.SharedWindowInputPolicy{})
244 }
245
246 func (a *Agent) requestCalibrationShape(req provider.Request) requestCalibrationShape {
247 return requestCalibrationShapeWithPolicy(req, sharedWindowInputPolicyOf(a.svc.prov))
248 }
249
250 func requestCalibrationShapeWithPolicy(req provider.Request, policy provider.SharedWindowInputPolicy) requestCalibrationShape {
251 requestChars, cjkRunes, cjkBytes := requestCalibrationTextShape(req, policy)
252 return requestCalibrationShape{
253 requestChars: requestChars,
254 compactChars: int64(charsOfMessages(req.Messages)),
255 cjkRunes: cjkRunes,
256 cjkBytes: cjkBytes,
257 }
258 }
259
260 // requestCalibrationTextShape counts common shared-window text plus only the
261 // adapter-specific replay fields declared by the active provider. This keeps
262 // omitted bytes out of the ratio without missing newly appended wire content.
263 func requestCalibrationTextShape(req provider.Request, policy provider.SharedWindowInputPolicy) (chars, cjkRunes, cjkBytes int64) {
264 add := func(s string) {
265 chars += int64(len(s))
266 for _, r := range s {
267 if isCJKRune(r) {
268 cjkRunes++
269 cjkBytes += int64(utf8.RuneLen(r))
270 }
271 }
272 }
273 for _, msg := range req.Messages {
274 if msg.LocalOnly {
275 continue
276 }
277 chars += 4
278 add(string(msg.Role))
279 add(msg.Content)
280 if msg.Role == provider.RoleAssistant && (len(msg.ToolCalls) > 0 || policy.ReplaysOrdinaryReasoning) {
281 add(msg.ReasoningContent)
282 }
283 add(msg.Name)
284 add(msg.ToolCallID)
285 for _, call := range msg.ToolCalls {
286 chars += 8
287 add(call.ID)
288 add(call.Name)
289 add(call.Arguments)
290 }
291 if policy.ReplaysResponsesItems {
292 for _, item := range msg.ResponsesItems {
293 add(string(item))
294 }
295 }
296 for _, search := range msg.ServerSearch {
297 provider.WalkServerSearchEstimate(search, add)
298 }
299 }
300 for _, schema := range req.Tools {
301 chars += 8
302 add(schema.Name)
303 add(schema.Description)
304 add(string(schema.Parameters))
305 }
306 return chars, cjkRunes, cjkBytes
307 }
308
309 func (a *Agent) calibratedPromptTokens(shape requestCalibrationShape) (int, bool) {
310 if shape.requestChars <= 0 {
311 return 0, false
312 }
313 if cal := a.sess.output.promptCalibration.Load(); cal != nil && cal.requestChars > 0 {
314 ratio := float64(cal.promptTokens) / float64(cal.requestChars)
315 if ratio > 0.05 && ratio < 2 {
316 trustedChars := shape.requestChars
317 excessCJKBytes := int64(0)
318 // A higher CJK share cannot safely reuse the aggregate ratio. Scale its
319 // represented share and price only the excess at the cold rate,
320 // preserving exact calibration for stable CJK sessions.
321 if shape.cjkRunes*cal.requestChars > cal.cjkRunes*shape.requestChars {
322 trustedCJKBytes := min(cal.cjkBytes*shape.requestChars/cal.requestChars, shape.cjkBytes)
323 excessCJKBytes = shape.cjkBytes - trustedCJKBytes
324 trustedChars -= excessCJKBytes
325 }
326 cold := math.Ceil(float64(excessCJKBytes) * fallbackTokPerChar)
327 return int(math.Ceil(float64(trustedChars)*ratio) + cold), true
328 }
329 }
330 return 0, false
331 }
332
333 // estimatedPromptTokens sizes the provider-visible messages in real tokens —
334 // the only unit comparable against the context window. Same-session usage
335 // calibrates it; before that the wire character count carries the ~4 chars per
336 // token shape. estimateMessagesTokens counts characters and is for internal
337 // planning budgets only; against the window it would compact 4x early.
338 func (a *Agent) estimatedPromptTokens(msgs []provider.Message) int {
339 return a.estimatedShapeTokens(a.requestCalibrationShape(provider.Request{Messages: msgs}))
340 }
341
342 func (a *Agent) estimatedRequestTokens(req provider.Request) int {
343 return a.estimatedShapeTokens(a.requestCalibrationShape(req))
344 }
345
346 func (a *Agent) estimatedShapeTokens(shape requestCalibrationShape) int {
347 if shape.requestChars <= 0 {
348 return 0
349 }
350 if calibrated, ok := a.calibratedPromptTokens(shape); ok {
351 return calibrated
352 }
353 return int(float64(shape.requestChars) * fallbackTokPerChar)
354 }
355
356 func isCJKRune(r rune) bool {
357 return (r >= 0x4E00 && r <= 0x9FFF) ||
358 (r >= 0x3400 && r <= 0x4DBF) ||
359 (r >= 0x3040 && r <= 0x30FF) ||
360 (r >= 0xAC00 && r <= 0xD7AF)
361 }
362
363 func (a *Agent) effectiveContextWindow() int {
364 if a == nil {
365 return 0
366 }
367 cfg := a.contextWindow
368 learned := 0
369 if snap := a.sess.output.learned.Load(); snap != nil {
370 learned = snap.windowTokens
371 }
372 switch {
373 case cfg > 0 && learned > 0:
374 return min(cfg, learned)
375 case learned > 0:
376 return learned
377 default:
378 return cfg
379 }
380 }
381
382 func (a *Agent) learnedCompletionBudget() int {
383 if a == nil {
384 return 0
385 }
386 cached := cachedLearnedOutputBudget(outputBudgetCacheKey(a))
387 if snap := a.sess.output.learned.Load(); snap != nil {
388 if cached > 0 && (snap.completionBudget <= 0 || cached < snap.completionBudget) {
389 a.learnOutputBudget(cached)
390 return cached
391 }
392 return snap.completionBudget
393 }
394 if cached > 0 {
395 a.learnOutputBudget(cached)
396 return cached
397 }
398 return 0
399 }
400
401 func (a *Agent) learnContextBudget(window, completion int, omittedOutput bool) {
402 if a == nil {
403 return
404 }
405 cur := learnedContextBudget{}
406 if prev := a.sess.output.learned.Load(); prev != nil {
407 cur = *prev
408 }
409 if window > 0 {
410 if cur.windowTokens <= 0 || window < cur.windowTokens {
411 cur.windowTokens = window
412 }
413 }
414 if omittedOutput && completion > 0 {
415 cur.completionBudget = completion
416 }
417 next := cur
418 a.sess.output.learned.Store(&next)
419 }
420
421 func (a *Agent) storeAdmission(adm contextAdmission) {
422 if a == nil {
423 return
424 }
425 cp := adm
426 a.sess.output.admission.Store(&cp)
427 }
428
429 func (a *Agent) lastAdmission() contextAdmission {
430 if a == nil {
431 return contextAdmission{LastRecovery: contextRecoveryNone}
432 }
433 if snap := a.sess.output.admission.Load(); snap != nil {
434 return *snap
435 }
436 return contextAdmission{LastRecovery: contextRecoveryNone}
437 }
438
439 func (a *Agent) setLastRecovery(kind string) {
440 if a == nil {
441 return
442 }
443 adm := a.lastAdmission()
444 adm.LastRecovery = kind
445 a.storeAdmission(adm)
446 }
447
448 func admissionSource(userMax int, policy provider.ContextBudgetPolicy, learnedWindow bool) string {
449 if learnedWindow {
450 return provider.ContextBudgetSourceLearned
451 }
452 if userMax > 0 {
453 return provider.ContextBudgetSourceExplicit
454 }
455 switch {
456 case policy.AutoOutputTokens == provider.DeepSeekMaxOutputTokens && policy.LimitMode == provider.OutputLimitOmitWhenSafe:
457 return provider.ContextBudgetSourceOfficial
458 case policy.LimitMode == provider.OutputLimitAlways && policy.MaxOutputTokens > 0:
459 return provider.ContextBudgetSourceOpenCode
460 case policy.WindowMode == provider.ContextWindowUnknown || policy.AutoOutputTokens <= 0:
461 return provider.ContextBudgetSourceUnknown
462 default:
463 return provider.ContextBudgetSourceOfficial
464 }
465 }
466
467 // effectiveOutputBudget clips completion tokens at send time only; it never
468 // moves compact_ratio. Calibrated exhausted windows fail locally; a cold
469 // estimate that differs from the provider tokenizer uses bounded 400 recovery.
470 func (a *Agent) effectiveOutputBudget(req provider.Request) (int, bool, error) {
471 adm, err := a.admitOutputBudget(req)
472 if err != nil {
473 return 0, false, err
474 }
475 if !adm.ApplyMaxTokens || !adm.Clipped {
476 if adm.ApplyMaxTokens && adm.EffectiveOutputTokens > 0 && !adm.Clipped {
477 return adm.EffectiveOutputTokens, false, nil
478 }
479 return 0, false, nil
480 }
481 return adm.EffectiveOutputTokens, true, nil
482 }
483
484 func (a *Agent) admitOutputBudget(req provider.Request) (contextAdmission, error) {
485 return a.admitOutputBudgetWithReserve(req, outputBudgetReserveForWindow(a.effectiveContextWindow()), false)
486 }
487
488 // outputBudgetReserveForWindow preserves the original safety ratio: the 8K
489 // tokenizer/protocol cushion was chosen for a 1M-token window, so smaller
490 // windows reserve roughly the same 1/128 share. A 256-token floor covers
491 // framing, while the 8K cap keeps larger windows byte-stable.
492 func outputBudgetReserveForWindow(window int) int {
493 if window <= 0 {
494 return outputBudgetReserve
495 }
496 return min(outputBudgetReserve, max(minOutputBudgetReserve, window/128))
497 }
498
499 // admitSummaryOutputBudget uses the summary request's dedicated protocol
500 // reserve instead of the ordinary-turn reserve. Unknown gateways are treated
501 // as shared when an effective window exists: summary planning already makes
502 // that conservative assumption, so execution must enforce the same contract.
503 func (a *Agent) admitSummaryOutputBudget(req provider.Request) (contextAdmission, error) {
504 return a.admitOutputBudgetWithReserve(req, protocolReserveTokens, true)
505 }
506
507 func shouldUseSharedWindowForAdmission(mode provider.ContextWindowMode, observedWindow int, conservativeUnknown bool) bool {
508 return mode == provider.ContextWindowUnknown && (observedWindow > 0 || conservativeUnknown)
509 }
510
511 func (a *Agent) admitOutputBudgetWithReserve(req provider.Request, reserveTokens int, conservativeUnknown bool) (contextAdmission, error) {
512 adm := contextAdmission{
513 ReserveTokens: reserveTokens,
514 LastRecovery: a.lastAdmission().LastRecovery,
515 Source: provider.ContextBudgetSourceUnknown,
516 }
517 if adm.LastRecovery == "" {
518 adm.LastRecovery = contextRecoveryNone
519 }
520 if a == nil {
521 return adm, nil
522 }
523 if learned := a.sess.output.learned.Load(); learned != nil {
524 adm.ObservedWindow = learned.windowTokens
525 adm.ObservedCompletion = learned.completionBudget
526 }
527 policy := contextBudgetPolicyOf(a.svc.prov)
528 if shouldUseSharedWindowForAdmission(policy.WindowMode, adm.ObservedWindow, conservativeUnknown) {
529 policy.WindowMode = provider.ContextWindowShared
530 }
531 if policy.AutoOutputTokens <= 0 && a.learnedCompletionBudget() > 0 {
532 policy.AutoOutputTokens = a.learnedCompletionBudget()
533 }
534 if learned := a.learnedCompletionBudget(); learned > 0 {
535 if policy.AutoOutputTokens <= 0 || learned < policy.AutoOutputTokens {
536 policy.AutoOutputTokens = learned
537 }
538 if policy.MaxOutputTokens <= 0 || learned < policy.MaxOutputTokens {
539 policy.MaxOutputTokens = learned
540 }
541 }
542 adm.WindowMode = policy.WindowMode.String()
543 adm.LimitMode = policy.LimitMode.String()
544 adm.AutoOutputTokens = policy.AutoOutputTokens
545 adm.MaxOutputTokens = policy.MaxOutputTokens
546 window := a.effectiveContextWindow()
547 adm.WindowTokens = window
548 learnedWindow := window > 0 && (a.contextWindow <= 0 || window < a.contextWindow)
549 adm.Source = admissionSource(req.MaxTokens, policy, learnedWindow)
550 if window <= 0 {
551 a.storeAdmission(adm)
552 return adm, nil
553 }
554 est := a.estimatedRequestTokens(req)
555 adm.PromptTokens = est
556 physical := window - est - reserveTokens
557 adm.PhysicalRemaining = physical
558 shared := policy.WindowMode == provider.ContextWindowShared
559 if !shared {
560 a.applyLimitMode(&adm, req.MaxTokens, policy, physical)
561 a.storeAdmission(adm)
562 return adm, nil
563 }
564 if physical <= 0 {
565 a.storeAdmission(adm)
566 return adm, fmt.Errorf("%w: estimated prompt %d leaves no shared-window output budget", ErrCompactionRequired, est)
567 }
568 requested := 0
569 switch {
570 case req.MaxTokens > 0:
571 requested = req.MaxTokens
572 default:
573 requested = policy.AutoOutputTokens
574 }
575 if policy.MaxOutputTokens > 0 && requested > policy.MaxOutputTokens {
576 requested = policy.MaxOutputTokens
577 }
578 adm.RequestedOutputTokens = requested
579 if req.MaxTokens < 0 {
580 if requested > 0 && requested > physical {
581 a.storeAdmission(adm)
582 return adm, fmt.Errorf("%w: estimated prompt %d leaves no room for omitted auto output %d", ErrCompactionRequired, est, requested)
583 }
584 a.storeAdmission(adm)
585 return adm, nil
586 }
587 if requested <= 0 {
588 a.applyLimitMode(&adm, req.MaxTokens, policy, physical)
589 a.storeAdmission(adm)
590 return adm, nil
591 }
592 effective := requested
593 if effective > physical {
594 effective = physical
595 adm.Clipped = true
596 }
597 adm.EffectiveOutputTokens = effective
598 a.applyLimitMode(&adm, req.MaxTokens, policy, physical)
599 if adm.Clipped {
600 adm.ApplyMaxTokens = req.MaxTokens >= 0 && policy.LimitMode != provider.OutputLimitUnsupported
601 adm.EffectiveOutputTokens = effective
602 }
603 if adm.Clipped && adm.LastRecovery == contextRecoveryNone {
604 adm.LastRecovery = contextRecoveryProactiveClip
605 }
606 a.storeAdmission(adm)
607 return adm, nil
608 }
609
610 func (a *Agent) applyAdmissionToRequest(req *provider.Request) error {
611 if a == nil || req == nil {
612 return nil
613 }
614 adm, err := a.admitOutputBudget(*req)
615 if err != nil {
616 return err
617 }
618 if adm.ApplyMaxTokens && adm.EffectiveOutputTokens > 0 {
619 req.MaxTokens = adm.EffectiveOutputTokens
620 }
621 return nil
622 }
623
624 func (a *Agent) applySummaryAdmissionToRequest(req *provider.Request) error {
625 if a == nil || req == nil {
626 return nil
627 }
628 adm, err := a.admitSummaryOutputBudget(*req)
629 if err != nil {
630 return err
631 }
632 if adm.ApplyMaxTokens && adm.EffectiveOutputTokens > 0 {
633 req.MaxTokens = adm.EffectiveOutputTokens
634 }
635 return nil
636 }
637
638 func (a *Agent) applyLimitMode(adm *contextAdmission, userMax int, policy provider.ContextBudgetPolicy, physical int) {
639 if userMax < 0 || policy.LimitMode == provider.OutputLimitUnsupported {
640 adm.ApplyMaxTokens = false
641 return
642 }
643 effective := adm.EffectiveOutputTokens
644 if effective <= 0 {
645 if userMax > 0 {
646 effective = userMax
647 } else {
648 effective = policy.AutoOutputTokens
649 }
650 if policy.MaxOutputTokens > 0 && effective > policy.MaxOutputTokens {
651 effective = policy.MaxOutputTokens
652 }
653 if policy.WindowMode == provider.ContextWindowShared && physical > 0 && effective > physical {
654 effective = physical
655 adm.Clipped = true
656 }
657 }
658 switch policy.LimitMode {
659 case provider.OutputLimitAlways, provider.OutputLimitRequired:
660 if effective > 0 {
661 adm.ApplyMaxTokens = true
662 adm.EffectiveOutputTokens = effective
663 }
664 case provider.OutputLimitOmitWhenSafe:
665 if userMax > 0 || adm.Clipped {
666 adm.ApplyMaxTokens = true
667 adm.EffectiveOutputTokens = effective
668 }
669 }
670 }
671
671 lines GO