线上 Agent 一跑久了就会遇到同一个坑:上下文越来越长,延迟飙升、费用失控,最后还更容易答偏。

这不是模型“变笨”了,通常是上下文治理没做:该留的没留、该删的没删、该摘要的摘要坏了。

症状先对齐(你是不是也这样)

  • p95 延迟随会话轮次持续上升
  • 单会话 token 成本后期指数增长
  • 同类问题回答不稳定,幻觉上升
  • 高峰期 429/超时明显增加

一套能落地的治理策略(先做这 3 层)

1) 硬截断:窗口上限 + 重要消息白名单

核心原则:永远给模型一个可控上限

  • 设定 max_context_tokens(比如 24k)
  • 系统提示词、策略约束、用户画像放白名单,永不丢
  • 普通历史消息按“最近优先”滑窗保留

Go 伪代码:

type Msg struct {
  Role      string
  Content   string
  Important bool
  Tokens    int
}

func TrimByBudget(msgs []Msg, budget int) []Msg {
  keep := make([]Msg, 0, len(msgs))
  used := 0

  // 1) 先保留重要消息
  for _, m := range msgs {
    if m.Important {
      keep = append(keep, m)
      used += m.Tokens
    }
  }

  // 2) 再从后往前补最近消息
  for i := len(msgs)-1; i >= 0; i-- {
    m := msgs[i]
    if m.Important { continue }
    if used+m.Tokens > budget { continue }
    keep = append([]Msg{m}, keep...)
    used += m.Tokens
  }
  return keep
}

2) 摘要回填:把“远历史”压缩成结构化记忆

不要用一段散文摘要,后续会越来越失真。建议固定结构:

  • 已确认事实(Facts)
  • 未解决问题(Open Issues)
  • 用户偏好(Preferences)
  • 决策与原因(Decisions + Why)

这样下一轮可做增量更新,不用每次全量重算。

3) 成本上限:会话级预算 + 降级开关

给每个会话加预算(比如 $0.5/session):

  • 超 70%:降低 max_output_tokens
  • 超 90%:禁用高成本工具调用
  • 超 100%:只保留“摘要 + 最新问题”,进入降级模式

Responses API 在 Go 里的实践建议

  • 把“历史构建器”做成独立模块,别散落在 handler
  • 每次请求都打点:input_tokens/output_tokens/latency/cost
  • 给摘要器单独版本号(summary_schema=v2),方便灰度
  • 工具调用结果不要原样全塞回上下文,先压缩成结论

故障排查清单(最容易漏)

  1. 重复拼接历史:中间件和业务层都 append 一次
  2. 工具输出过长:日志/HTML 原文直接回灌
  3. 摘要回填失真:把猜测当事实写入 Facts
  4. 预算只看 token 不看价格:不同模型单价差异很大

最小可用配置(建议起步值)

  • max_context_tokens: 24k
  • summary_trigger_tokens: 16k
  • max_output_tokens: 1k
  • session_cost_cap_usd: 0.5
  • tool_output_hard_cap_chars: 4000

总结

上下文爆炸本质是工程问题,不是玄学调参。

先把截断、摘要、预算三件事做扎实,通常就能把延迟和成本压回可控区间,再谈效果优化才有意义。