上下文工程进阶
Q:Agent 的 Checkpoint 用什么数据库存?初始化 session 时如何优化加载 Checkpoint 的速度?
来源:AI 工程师面试 【CVTE AI应用工程师一面追问:短期记忆为什么用 sqlite checkpointer / 长期记忆如何实现】【钉学科技 FDE 实习一面追问:字段、一致性与换模型恢复】
新手答:”用 Redis 存,读取快。”
高手答:
Checkpoint 是 Agent 执行状态的持久化快照——包括当前节点、中间变量、已完成步骤、工具返回结果等。选什么数据库存、怎么优化加载速度,取决于数据特征和访问模式。
存储选型:
| 方案 | 适用场景 | 优缺点 |
|---|---|---|
| PostgreSQL(LangGraph 默认) | 生产环境首选 | 事务保证、支持复杂查询、数据持久可靠;延迟略高 |
| SQLite | 本地开发、单用户场景 | 零配置、嵌入式;不支持并发写入 |
| Redis | 高频读写、短期状态 | 极低延迟;但数据持久性弱,重启可能丢失 |
| MongoDB | Checkpoint 结构复杂且多变 | Schema 灵活;但事务支持弱于 PG |
LangGraph 生产部署推荐 PostgreSQL + 异步连接池(AsyncPostgresSaver + asyncpg),兼顾可靠性和性能。
Checkpoint 表结构设计(以 LangGraph 为例):
checkpoints 表:
thread_id -- 会话标识
checkpoint_id -- 检查点版本号(单调递增)
parent_id -- 父检查点 ID(支持版本链)
channel_values -- 序列化的状态数据(JSON/msgpack)
metadata -- 节点名、时间戳、自定义标签通过 thread_id 定位会话,checkpoint_id 定位最新版本,parent_id 支持状态回溯。
生产中的字段通常还要更完整:租户、用户、run_id 与 thread_id;当前节点、下一候选节点、运行状态;State 快照、channel 版本与待处理写入;已完成/待执行任务;工具调用结果及幂等键;中断原因与人工审批状态;创建时间、过期策略;以及 model_id、Prompt、Tool Schema 和运行配置版本。不是所有字段都要塞进一个 JSON,但必须能回答“恢复哪次执行、从哪里继续、依赖哪个运行时版本、哪些副作用已经发生”。
加载速度优化:
初始化 session 时加载 Checkpoint 的瓶颈通常在序列化数据的读取和反序列化。优化分四个方向:
1. 数据库层优化:
- 对 (thread_id, checkpoint_id) 建复合索引,精确命中,避免全表扫描
- 使用连接池(asyncpg pool)复用数据库连接,省掉每次建连的开销
- 对历史 checkpoint 做定期归档,保持主表体积小2. 缓存层:
热点 session 的最新 Checkpoint 缓存到 Redis,TTL 设为 session 超时时间。命中率高时加载延迟从 10ms 级降到 1ms 级。
这里应把 PostgreSQL/MySQL 当作事实源,Redis 只做带版本的缓存。写入时在关系数据库内用 checkpoint_version 做 CAS/乐观锁校验,事务提交成功后再失效或更新 Redis;缓存值也携带版本号,发现版本落后或恢复时的 expected_version 不匹配,就回源并回填。TTL、提交后失效消息和定时 reconciliation 共同修复漏删缓存,避免“数据库已经到 v12,Redis 还让任务从 v11 继续”的并发覆盖。
3. 懒加载(Lazy Loading):
不一次性加载完整 Checkpoint。先加载元数据(当前节点、步骤数),channel_values 中的大字段(如工具返回的长文本、检索结果)按需加载:
第一步:加载 metadata + 当前节点位置(<1ms)
第二步:用户实际交互时,按需加载具体 channel 的值4. 状态压缩:
- 历史步骤的完整工具返回可以压缩为摘要——只保留关键结论,丢弃原始 JSON
- 用 msgpack 替代 JSON 做序列化,体积缩小 30-50%,反序列化速度更快
- 超过一定步数的历史 Checkpoint 只保留最近 N 个完整快照,更早的合并为摘要快照
恢复时切换模型怎么办:Checkpoint 不只保存业务 State,还要绑定模型、Prompt、Tool Schema、结构化输出 schema 和关键配置版本。恢复时优先固定到原版本;若旧版本不可用,先执行显式迁移,重新校验待执行工具参数、状态 schema 和安全策略,再从最近的稳定节点继续。不能把旧模型生成到一半的 tool call 直接交给新 schema 执行,否则“状态能反序列化”并不等于“语义还能安全续跑”。
差距在哪:新手只想到 Redis——快但不可靠,且没考虑加载优化。高手从存储选型(PostgreSQL + 异步连接池)、表结构设计、四层加载优化(索引 + 缓存 + 懒加载 + 压缩)给出了完整方案。面试官考的是你对状态持久化的工程化设计能力——Checkpoint 是 Agent 断点恢复和多轮对话的基石。
Q:Code Agent 的上下文工程,和普通对话 Agent 相比有哪些独特挑战?
来源:蚂蚁集团 Agent 开发一面
新手答:“代码多了上下文放不下,截断就行。”
高手答:
Code Agent 的上下文工程比普通对话 Agent 难得多,因为代码的上下文结构是网状的,不是线性的。
挑战一:依赖关系是隐式的
普通对话的上下文是线性的——前几轮说了什么。但代码的上下文是跨文件、跨模块的依赖图。要修改一个函数,可能需要知道:调用它的地方、它依赖的类型定义、相关的配置文件、对应的测试文件。这些信息分散在整个项目里,不在对话历史中。
普通对话 Agent:上下文 = 最近 N 轮对话(线性)
Code Agent:上下文 = 当前文件 + 依赖文件 + 类型定义 + 测试 + 配置 + git history(网状)挑战二:精度要求极高
自然语言对话“大差不差”也能用,但代码差一个字符就编译不过。上下文里缺少一个类型定义,模型就可能自己编造一个不存在的接口。所以 Code Agent 的上下文不能只求“相关”,必须求精确和完整。
挑战三:上下文随操作动态变化
Code Agent 每做一步操作(写文件、运行测试),项目状态就变了。下一步的上下文必须基于操作后的最新状态,不能用缓存的旧版本。这意味着上下文需要持续刷新,而不是一次组装完就够了。
工程解法:
- 结构化索引:不把整个文件塞进上下文,而是用 AST/LSP 建立项目的结构化索引——函数签名、类型定义、调用关系。需要时按依赖路径精确召回
- 分层加载:当前编辑文件全量加载 → 直接依赖文件加载签名 → 间接依赖只加载类型定义
- 操作后刷新:每次文件修改或命令执行后,增量更新受影响文件的上下文,而非全量重建
- 项目规则注入:用 CLAUDE.md / .cursorrules 等机制注入编码规范和架构约束,让模型在生成代码时遵守项目约定
差距在哪:新手把 Code Agent 的上下文当成普通对话来处理。高手看到了代码上下文的三个独特挑战(网状依赖、精度要求、动态变化),并给出了结构化索引、分层加载、操作后刷新、规则注入四个工程解法。面试官考的是你对 Code Agent 这个垂直场景的深度理解。
Q:做上下文工程最关键的工作是什么?
来源:蚂蚁集团 Agent 开发二面
新手答:“把相关信息尽量多地塞进上下文。”
高手答:
上下文工程最关键的工作不是“塞更多信息”,而是在有限的窗口里,让模型在每一步都能看到恰好需要的信息——不多不少。
这个工作可以拆成三个关键环节:
1. 信息分级——决定“什么信息该在上下文里”
必须在(每轮注入):系统规则、当前任务目标、关键约束条件
按需在(检索注入):相关知识、历史记录、工具返回结果
不该在(外置存储):中间推理过程、已处理的原始数据、过长的工具返回最常见的错误是把所有东西都往上下文里塞——信息越多,模型越容易在噪声中迷失。
2. 注入位置——决定“信息放在上下文的哪个位置”
大模型对上下文不同位置的信息敏感度不同(Lost in the Middle 现象)。关键信息的最佳位置:
System Prompt:系统规则、角色约束、输出格式要求(最稳定的锚点)
对话开头:任务目标、关键约束(不会被中间对话淹没)
最近几轮:工具返回结果、当前步骤信息(和当前决策最相关)3. 生命周期管理——决定“信息什么时候进、什么时候出”
上下文不是只进不出的。最关键的工程决策是信息的退出策略:
- 工具返回的原始 JSON:提取关键字段后,原始数据退出上下文
- 已完成的子任务:压缩成一句摘要,详情落库
- 过时的约束条件:用户改了需求后,旧约束必须显式移除,不能让它残留干扰
差距在哪:新手以为上下文工程就是”塞信息”。高手把它拆成信息分级、注入位置、生命周期管理三个关键环节——重点不是”放什么进去”,而是”什么时候让什么退出”。面试官考的是你对上下文工程的系统性认知,而不只是”会用 RAG 注入知识”。
Q:如何减少无关上下文对模型的干扰?当前上下文有哪些优化思路?
来源:快手 AI Agent 开发一面
新手答:“把不相关的信息删掉。”
高手答:
无关上下文对模型的干扰比“缺少信息”更严重——缺信息模型会说“我不知道”,但噪声信息会让模型自信地给出错误答案。这是 Lost in the Middle 现象的根源。
干扰来源和对应优化:
优化策略一:信息准入控制(入口减噪)
不是所有信息都该进上下文。每类信息设准入条件:
RAG 检索结果:rerank 分数 > 阈值才注入,低于阈值的宁可不给
工具返回值:提取关键字段(如 status、result),丢弃原始 JSON 全文
历史对话:超过 N 轮的压缩为摘要,只保留关键决策和约束条件优化策略二:注入位置优化(利用注意力分布)
大模型对上下文不同位置的关注度不同(开头和结尾关注度高,中间容易被忽略):
System Prompt 区(最稳定):系统规则、安全约束、输出格式
上下文开头:当前任务目标、关键约束
上下文结尾(靠近用户输入):最相关的检索结果
中间区域:次要参考信息(即使被忽略影响也不大)优化策略三:上下文压缩(动态瘦身)
| 压缩手段 | 适用对象 | 效果 |
|---|---|---|
| 摘要压缩 | 早期对话历史 | 保留语义,释放 70%+ token |
| 字段提取 | 工具返回的 JSON | 只保留关键字段,丢弃冗余 |
| 关键信息外置 | 用户约束(预算、日期) | 存入独立状态变量,每轮强制注入,不依赖上下文传递 |
| 去重 | 多轮对话中重复出现的信息 | 同一信息只保留最新版本 |
优化策略四:结构化分隔(防止信息混淆)
用明确的标签/分隔符区分不同来源的信息,让模型清楚知道每段信息的角色:
【系统规则】你是一个客服助手,回答必须基于以下参考资料...
【用户约束】预算:5000元以内,时间:本周末
【检索结果】[来源: 产品手册 v2.3] ...
【当前问题】用户问:...不加分隔符时,模型可能把检索结果当成系统指令,或把历史对话当成当前需求。
差距在哪:新手只想到”删掉不相关的”——但怎么判断”不相关”?高手从四个维度给出了完整方案:准入控制(入口减噪)、位置优化(利用注意力分布)、动态压缩(释放 token)、结构化分隔(防止混淆)。面试官考的是你对”上下文质量”这个 Agent 核心问题的系统性思考。
Q:在电商或导购场景下,用户的请求往往高度模糊,Agent 怎么来精准理解这种需求?
来源:淘天 AI Agent 一面
新手答:”问用户想要什么。”
高手答:
电商场景的模糊需求和普通对话不一样——用户说”推荐个好用的””性价比高的””送女朋友的”,这些表述没有明确的商品属性,但用户期望 Agent 能”懂”。直接追问”您想要什么功能?”体验很差,等于把理解工作丢回给了用户。
精准理解模糊需求需要三层递进的理解管线:
第一层:意图解析
先判断用户的意图类型——是随便逛逛(浏览)、在做对比(比较)、准备下单(购买)、还是来投诉的。不同意图对应完全不同的处理策略。同时提取用户输入中的显式约束:价格范围、品牌偏好、品类限定。”推荐个好用的”意图是浏览/购买,但显式约束几乎为零。
第二层:画像补全
显式约束不够时,从用户历史中补齐:
- 购买记录:过去买过什么品类、什么价位段、什么品牌
- 浏览行为:最近看了哪些商品、在哪些商品页停留时间长
- 退货模式:退过什么、退货原因是什么(”质量差”说明用户重品质,”不好用”说明用户重体验)
这些信号组合起来能大幅缩小候选范围——比如历史数据显示用户习惯买 200-500 元价位的电子产品,品牌偏好国产,那”推荐个好用的”就不需要从全品类全价位去猜了。
第三层:知识落地
模糊表述需要通过商品知识图谱映射到具体的商品属性:
- “好用” → 耐用性评分高 + 用户好评率高 + 操作复杂度低
- “性价比” → 同品类中价格/性能比排名靠前
- “送女朋友” → 品类偏向(美妆/饰品/数码)+ 外观评分高 + 有礼盒包装选项
这一步不是模型自己猜的,而是基于预先构建的品类知识图谱做结构化映射。
消歧策略:当多种理解并存时,不要问开放式问题(”您想要什么?”),而是用知识图谱生成选择式澄清——“您说的‘好用’,是更看重耐用不容易坏,还是操作简单上手快?”把模糊需求转化成二选一或三选一,用户决策成本低,Agent 也能精准收窄范围。
差距在哪:新手只会问开放式问题,把理解工作甩给用户。高手用三层管线(意图解析 → 画像补全 → 知识落地)做多信号融合,在追问之前先把不确定性降到最低,追问时也用选择题而不是开放题。面试官考的是你能不能把”理解用户”从一个模糊的概念变成一个可工程化的管线。
Q:摘要总结往往会丢失关键细节,在长文本 Agent 中一般怎么来处理这一块?
来源:淘天 AI Agent 一面
新手答:”用模型做摘要就行。”
高手答:
一次性摘要是最常见的做法,也是最容易丢信息的做法。问题在于:模型做摘要时会倾向保留”大意”而丢弃”细节”——但在 Agent 场景中,往往细节才是关键:一个具体的数字、一条用户约束、一个边界条件,丢了就会导致后续执行出错。
解决思路是不要让摘要承担所有压缩任务,而是把关键细节和叙事流分开处理:
策略一:分层摘要
不要一次性把长文本压成一段摘要,而是分层递进:
第一层:段落级摘要 —— 每段保留核心论点和关键数据
第二层:章节级摘要 —— 合并同主题段落,保留结论和依据
第三层:全文级摘要 —— 提炼整体结论和核心约束每一层保留不同粒度的信息。需要概览时用第三层,需要细节时回溯到第一层。比单次压缩损失少得多。
策略二:先提取后摘要
在做摘要之前,先用 NER + 规则提取把结构化关键事实抽出来存到独立的 key-value 存储中:
| 提取类型 | 示例 | 存储位置 |
|---|---|---|
| 日期时间 | “截止日期是 4 月 30 日” | 结构化事实库 |
| 数字约束 | “预算不超过 5000 元” | 结构化事实库 |
| 人名/实体 | “负责人是张三” | 结构化事实库 |
| 明确约束 | “必须支持 iOS 和 Android” | 结构化事实库 |
| 叙事内容 | 讨论过程、背景说明 | 交给摘要处理 |
摘要只负责压缩叙事流(讨论了什么、结论是什么),不承担保留具体数字和约束的责任。最终上下文 = 结构化事实 + 叙事摘要,两者合并注入。
策略三:选择性压缩
不是所有内容都需要同等程度的压缩:
| 内容类型 | 压缩策略 | 保留程度 |
|---|---|---|
| 用户约束和决策 | 保留原文 | 100% |
| 错误详情和边界条件 | 保留原文 | 100% |
| 工具返回的关键结果 | 提取关键字段 | 50% |
| 讨论过程和背景 | 摘要压缩 | 20% |
| 寒暄和确认性回复 | 直接丢弃 | 0% |
关键部分不压缩,常规部分重度压缩,无价值内容直接丢弃——这比均匀压缩的信息保真度高得多。
差距在哪:新手用一次性摘要解决所有问题——模型会自动丢弃它认为”不重要”的细节,但在 Agent 场景中这些细节往往是执行的关键。高手把压缩任务拆成结构化提取 + 分层摘要 + 选择性压缩三个策略,确保关键细节不被摘要过程吞掉。面试官考的是你对长文本信息压缩的精细化设计能力——摘要不是”一键总结”,是一个有策略的信息管理流程。