07 · 记忆、可观测与成本控制

对应代码:app/agent/memory.py、app/observability/tracer.py

这三件事看起来是"锦上添花",实际决定了 Agent 能不能上生产。

一、记忆:窗口 + 摘要

问题

多轮对话时如果每轮都把全部历史塞进 Prompt:

  • 贵:token 线性增长
  • 慢:首 token 延迟越来越高
  • 笨:研究显示长上下文存在"中间遗忘"(lost in the middle)

方案

markdown 复制代码
最近 12 轮原文(保证指代可解析)
        +
更早内容的 LLM 摘要(保证长期一致性)
python 复制代码
class ConversationMemory:
    def load(self, thread_id):
        return self.backend.summary(thread_id), self.backend.history(thread_id, window)

    def save_turn(self, thread_id, user_msg, assistant_msg):
        self.backend.append(...)
        self._maybe_summarize(thread_id)     # 超过 16 条才触发

    def _maybe_summarize(self, thread_id):
        delta = "\n".join(f"{m['role']}: {m['content'][:300]}" for m in history)
        new_summary = llm.chat([{"role":"user","content":SUMMARY_PROMPT.format(old=old, delta=delta)}])

摘要 Prompt 明确要求保留三件事:

复制代码
1) 用户关注的对象(项目名/时间范围/指标)
2) 已给出的结论
3) 待办或下一步

压缩后只保留最后一轮,避免旧消息重复计入。

三种后端

后端 适用 说明
memory Demo / 单测 进程内
sqlite 单机部署 落盘,重启不丢
redis 在线服务 带 TTL(默认 7 天)

追问:为什么不用向量记忆?

向量记忆适合"检索很久以前的事实",但对"上一步我说了什么 "这种时序指代效果差, 且每轮都要额外做 embedding。 生产上常见做法是两者结合:摘要 + 窗口做主线,向量记忆做事实补充。

二、可观测:自建 Tracer

为什么不用 LangSmith

企业内网通常不允许把 prompt 出网。 所以自建了一套,同时保留适配位(设置环境变量即可并行上报)。

数据模型

python 复制代码
@dataclass
class Span:
    trace_id, span_id, name, parent_id
    start_ms, end_ms, status
    attributes: Dict   # 节点自定义属性:model、tokens、sql、rows...

@dataclass
class Trace:
    id, query, created_at, meta
    spans: List[Span]
    tokens: {"prompt": n, "completion": n, "total": n}
    llm_calls: int

用起来就是一个上下文管理器

python 复制代码
with tracer.span(trace_id, "node.plan"):
    ...
with tracer.span(trace_id, "text2sql", attempt=attempt) as sp:
    sp.attributes["raw_sql"] = raw_sql[:300]

落盘 + 实时推送

  • 落盘 :./.cache/traces/{trace_id}.json,可离线分析、可回放
  • 实时 :tracer.emit(event) → 事件推给 SSE → 前端"执行链路时间线"
python 复制代码
def add_listener(self, fn): self._listeners.append(fn)
def emit(self, event):
    for fn in list(self._listeners):
        try: fn(event)
        except Exception: pass      # 监听器异常不能影响主流程

排查问题时的实际用法

bash 复制代码
答错了 → 打开 /api/traces/{id}
      → 看哪个 span 耗时异常
      → 看 plan 拆得对不对
      → 看检索命中了哪些 chunk(分数多少)
      → 看工具返回了什么
      → 看 reflect 判断"够了"是否合理
      → 看最终 prompt 上下文

没有这套东西,Agent 出问题时只能靠猜。

三、Token 与成本控制

统计

python 复制代码
tracer.record_llm(trace_id, prompt_tokens, completion_tokens, model=resp.model)

前端右上角实时显示累计 token。

六个降本手段(项目里都实现了)

手段 做法
① 检索只喂 top-5 不把整篇文档塞进去
② 记忆压缩 窗口 + 摘要,不塞全量历史
③ 重排默认零调用 heuristic;必要时才上 LLM rerank
④ 迭代上限 防死循环烧钱(最重要!)
⑤ 规划用小模型 Prompt 里已预留模型切换位(LLM_FALLBACK_MODELS)
⑥ 流式输出 用户可提前中断,减少无效生成

一次真实调用的开销

ini 复制代码
问:本月各项目成本排名前 3 是多少?
prompt=3000  completion=509  total=3509
(含 2 次规划 + 2 次 SQL 生成 + 1 次反思 + 1 次回答)

这个数字本身也是面试素材:你能说清一次问答到底花多少 token, 比空谈"我们做了成本优化"有说服力得多。

四、三者的关系

arduino 复制代码
记忆 ──▶ 决定"喂给模型什么"      ──▶ 影响成本
可观测 ─▶ 记录"花了多少、哪里慢"  ──▶ 指导优化
护栏 ──▶ 决定"能不能执行"        ──▶ 控制风险

这三件事 + 前面的 RAG 和编排,才构成一套能上生产的 Agent 系统, 而不是一个"接了 API 的聊天 Demo"。


下一篇:08 · 前端:Vue3 + SSE 流式与执行链路可视化

相关推荐
lsc_blog8 小时前
年终自评别贴提交记录,把技术产出讲成变化
数据库·面试·职场和发展·跳槽·求职招聘
GoAI14 小时前
DeepSeek Harness Memory (一)主流记忆插件总结
后端·算法·面试
Zan16 小时前
SQLite + SQLx 实战:为什么已经发布的数据库迁移文件,绝对不能再修改?
数据库·后端·面试
我叫黑大帅17 小时前
PHP 常驻 Worker 中 MySQL 连接中断问题报告
后端·面试·php
Moment18 小时前
代码都能让 Agent 写了,我们为什么还要学前端?
前端·后端·面试
聪明的笨猪猪21 小时前
实际工程问题:AI生成Redis分布式锁的原子性缺陷导致库存超卖
java·经验分享·面试·ai编程
时间的拾荒人1 天前
Redis 入门详解:核心特性、重要文件与全局命令实战
开发语言·redis·面试
爱敲代码的小黄1 天前
神经网络入门指南:从 CNN、RNN 到 Attention
人工智能·面试·程序员
brave10252 天前
LLMGuide 大模型面试指南
人工智能·ai·面试·ai编程
Rain的Java大神之路2 天前
面试官最烦的答案:“我用 Redis + Token”!接口幂等 8 种方案 + 四层防御,把重复扣款工单干到 0
java·后端·面试