Files
Agent-System-Backend/internal/runtime/material_final_reserve.go
T

81 lines
3.2 KiB
Go

package runtime
import agenttools "agent-native/runtime/tools"
// materialBudgetState describes only usage returned by the provider for a
// material subagent. A response with no reported usage cannot authorize a
// further request. LastPrompt carries the size estimate of the next provider
// prompt, which is the previous prompt plus the previous completion because
// the next request always resends the prior assistant output.
type materialBudgetState struct {
Limit, Used, LastPrompt, OutputCap, SavedCheckpoints, Responses int
UsageKnown bool
}
// materialFinalDecision reserves room for an answer before allowing another
// checkpoint request. It is deliberately an estimate, not a billing limit for
// an already in-flight request.
func materialFinalDecision(s materialBudgetState) (finalOnly, stop bool) {
if s.Responses == 0 {
return false, false
}
if !s.UsageKnown {
return false, true
}
margin := s.LastPrompt / 4
if margin < 1024 {
margin = 1024
}
remaining := s.Limit - s.Used
if remaining <= s.LastPrompt+margin {
return false, true
}
reserve := s.LastPrompt + s.OutputCap + margin
return s.SavedCheckpoints >= 2 || remaining <= 2*reserve, false
}
const materialFinalOnlyNotice = "后续不再接受检查点:下一步直接用现有材料写出最终结论与未决项,不要再调用 publish_work_checkpoint。"
// materialOutputCap is the per-request output cap the material reserve
// assumes: the request cap clamped by models.chat.maxOutputTokens.
func materialOutputCap(rc RunCtx) int {
outputCap := outputTokenCap(rc.Profile().Budget, rc.Settings(), "models.chat.maxOutputTokens")
if configured := int(rc.Settings().GetNumber("models.chat.maxOutputTokens", 0)); configured > 0 && configured < outputCap {
outputCap = configured
}
return outputCap
}
// withFinalOnlyNotice announces the final-only transition on a tool result the
// model actually reads. The reserve can be reached by a round of ordinary tool
// calls, so the notice belongs on every result handed back after the decision
// flips — not only on checkpoint results. IsError is never changed.
func (st *subRun) withFinalOnlyNotice(res agenttools.Result) agenttools.Result {
if st.materialLimit <= 0 {
return res
}
if finalOnly, stop := st.materialDecision(); !finalOnly && !stop {
return res
}
if res.Content != "" {
res.Content += "\n\n"
}
res.Content += materialFinalOnlyNotice
return res
}
// materialDecision is the single source of the final-only/stop decision for
// both the next request and the checkpoint tool result. Only meaningful when
// materialLimit > 0. The next request always carries the previous assistant
// output, so the reserve estimates next-prompt size as
// LastPromptTokens+LastCompletionTokens rather than the raw provider prompt.
func (st *subRun) materialDecision() (finalOnly, stop bool) {
estimatedNextPrompt := st.usageTotal.LastPromptTokens + st.usageTotal.LastCompletionTokens
return materialFinalDecision(materialBudgetState{
Limit: st.materialLimit, Used: st.usageTotal.TotalTokens,
LastPrompt: estimatedNextPrompt, OutputCap: st.materialOutputCap,
SavedCheckpoints: len(st.savedCheckpointIDs), Responses: st.responses,
UsageKnown: st.usageKnown,
})
}