第 3 章 上下文工程

第 3 章 上下文工程

本章要解决的问题

Agent 聊几轮就「失忆」、长文档塞不进提示词------上下文窗口明明够大,为什么效果还是崩?

章节大纲

  • 3.1 上下文窗口:空间、成本与策略
  • 3.2 对话历史管理:压缩、摘要、滑动窗口
  • 3.3 长文本处理:RAG 前置与上下文编排
  • 3.4 状态持久化与跨会话记忆
  • 🛠 解决方案:Token 预算分配模板 + 上下文溢出排错指南

3.1 上下文窗口:空间、成本与策略

3.1.1 为什么"窗口够大"还是崩

很多人的困惑:"模型支持 128K 窗口,为什么我塞 20K 就效果崩了?"

答案有三个层面:

层面 原因 缓解
注意力稀释 窗口越大,模型对关键信息的"注意力密度"越低(第 2 章注意力机制) 关键信息前置、冗余裁剪
指令被淹没 系统提示词淹没在长历史里,模型"忘记"了任务要求 系统提示词放在消息最前
上下文污染 无关内容(历史闲聊、工具噪音)干扰当前判断 按需裁剪、摘要化

窗口是"容量上限",不是"推荐用量"。 就像桌子能放 100 本书,但你只需要 5 本------把桌子堆满反而找不到要用的那本。上下文工程的核心:在窗口内,用最少的 token 装下最关键的信息。

3.1.2 Token 预算分配模板

生产级 Agent 应该在系统层面对窗口做预算分配:

图 1:Token 预算分配(32K 例)

erlang 复制代码
窗口总预算(以 32K 为例)
├── 系统提示词(角色/任务/规则)   ~2K   (6%)
├── 工具定义(Function Schema)  ~4K   (12%)
├── 对话历史(用户+助手消息)    ~8K   (25%)
├── 当前输入(本次请求)         ~8K   (25%)
├── 工具执行结果(观察)         ~4K   (12%)
└── 输出预留(max_tokens)      ~6K   (20%,必须留!)

输出预留是最容易被忽视的一环 ------不留足输出空间,模型会"生成到一半被截断",这是很多"答案不完整"问题的真正原因(呼应第 24 章 max_tokens 设置)。

3.1.3 预算的硬性纪律

  1. 总量设上限总输入 + 输出预留 ≤ 窗口,超了就触发裁剪策略(3.2 节)。
  2. 优先级排序:系统提示词 > 当前输入 > 工具定义 > 最近历史 > 旧历史。裁剪从低优先级开始。
  3. 量化意识 :1 个汉字 ≈ 1~2 token,1 个英文单词 ≈ 1.3 token,心里要有数。不同模型 tokenizer 差异很大,生产环境应以 API 返回的 token 统计为准,而非字符估算。

3.2 对话历史管理

3.2.1 三大策略:截断、滑动窗口、摘要

策略 做法 优点 缺点 适用
硬截断 只保留最近 N 条消息 零成本 丢失早期关键信息 简单对话
滑动窗口 保留最近 N 条 + 固定系统提示词 实现简单、状态新鲜 早期信息全丢 大多数场景
摘要压缩 旧历史压缩成摘要 + 保留最近原文 保留长期信息 摘要损失细节 长会话

图 2:历史管理三策略

3.2.2 摘要压缩的实现(推荐组合)

生产推荐"摘要 + 滑动窗口"组合:超龄历史转摘要,近期保留原文。

图 3:增量摘要压缩

python 复制代码
import json
from openai import OpenAI

client = OpenAI(base_url="https://api.deepseek.com", api_key="<你的Key>")

class ConversationManager:
    def __init__(self, system_prompt, max_recent=8, max_tokens=12000):
        self.system_prompt = system_prompt
        self.max_recent = max_recent      # 保留的最近消息数
        self.max_tokens = max_tokens
        self.summary = ""                 # 长期摘要
        self.history = []                 # 近期消息

    def add(self, user_msg, assistant_msg):
        self.history.append({"role": "user", "content": user_msg})
        self.history.append({"role": "assistant", "content": assistant_msg})
        self._maybe_compress()

    def _estimate_tokens(self, messages):
        """粗略估算 token 数(1 个汉字≈1~2 token,1 个英文单词≈1.3 token)。
        生产环境应以模型 tokenizer 或 API 返回的 token 统计为准。"""
        total = sum(len(m["content"]) for m in messages if isinstance(m.get("content"), str))
        return int(total * 1.5)  # 中文偏多的粗略系数

    def _maybe_compress(self):
        # 双阈值触发:条数超限 OR token 估算超预算 70%
        if len(self.history) <= self.max_recent * 2 and \
           self._estimate_tokens(self.history) < self.max_tokens * 0.7:
            return
        oldest = self.history[: self.max_recent]
        self.summary = self._summarize(oldest, self.summary)  # 增量摘要
        self.history = self.history[self.max_recent:]

    def _summarize(self, messages, old_summary):
        resp = client.chat.completions.create(
            model="deepseek-chat",
            messages=[{"role": "user", "content":
                f"把以下对话合并进已有摘要,输出更新后的摘要。"
                f"已有摘要:{old_summary}\n新对话:{json.dumps(messages, ensure_ascii=False)}"}],
            temperature=0.2, max_tokens=1000,
        )
        return resp.choices[0].message.content

    def build_messages(self):
        msgs = [{"role": "system", "content": self.system_prompt}]
        if self.summary:
            msgs.append({"role": "system",
                         "content": f"【历史摘要】{self.summary}"})
        msgs.extend(self.history)
        return msgs

工程要点

  1. 摘要增量更新:旧摘要 + 新消息 → 新摘要,避免每次全量重摘要(省 token)。
  2. 摘要放 system:以"历史摘要"身份注入,不污染近期对话流。
  3. 只压缩最旧:近期原文保留,保证当前话题的完整上下文。

3.2.3 何时触发压缩:两个阈值

  • 条数阈值:消息超过 N 条(如 16 条)触发。
  • Token 阈值:估算总 token 超过窗口预算的 70% 触发。
  • 双阈值取先到者:任一触发即压缩,防"对话太长直接爆窗"。

3.3 长文本处理:RAG 前置与上下文编排

3.3.1 两条路线:全量塞入 vs 检索式

面对长文档(一本书、万行代码),两条路线:

路线 做法 适用
全量塞入(大窗口) 直接放窗口(Qwen 1M 等大窗口模型) 单次分析、文档不长于窗口
检索式(RAG,第 8 章) 分块 + 向量检索,只取相关块 长文档、多文档、频繁问答

经验法则:文档 ≤ 窗口的 50% 且只问一次 → 全量塞;否则 → RAG。 全量塞简单但贵(所有 token 都计费),RAG 省 token 但引入检索质量变量(第 8 章详述)。

3.3.2 上下文编排:把"喂什么"当架构设计

上下文工程的高级形态是编排(Context Engineering)------不是被动管理历史,而是主动设计"模型每一步看到什么":

编排动作 说明 呼应章节
按需投喂 只给当前子任务需要的信息(多 Agent 场景) 第 16 章
关键信息前置 任务指令放最前,避免被淹没 本章
工具结果精简 工具返回只回填必要字段 第 14 章
信息分级 系统级(不变)/会话级(变)/临时级(一次) 第 9 章

核心思想:上下文不是"记录",是"注意力预算"------模型的注意力有限(3.1.1),编排就是决定"把预算花在哪"。

3.4 状态持久化与跨会话记忆

3.4.1 会话内 vs 跨会话:记忆的分层

存活期 存储 例子 呼应
会话上下文 单次会话 内存/请求参数 本次对话历史 本章
工作记忆 任务期间 服务内存/Redis 中间计算结果 第 6 章
长期记忆 跨会话 数据库/向量库 用户偏好、历史结论 第 9 章

图 4:记忆三层架构

只靠"上下文窗口"做不了跨会话记忆------会话结束窗口就清空。要记住用户,需要把关键信息写入持久化存储(第 9 章完整展开),本章先建立分层认知。

3.4.2 持久化最小实现

python 复制代码
import redis, json

r = redis.Redis(host="localhost", port=6379)

def save_user_pref(user_id, pref):
    """把用户偏好写入长期记忆"""
    key = f"user:{user_id}:pref"
    r.set(key, json.dumps(pref, ensure_ascii=False))

def load_user_pref(user_id):
    """新会话开始时,把长期记忆注入上下文"""
    raw = r.get(f"user:{user_id}:pref")
    if not raw:
        return ""
    try:
        pref = json.loads(raw)
    except json.JSONDecodeError:
        return ""  # 记忆数据损坏时降级为空
    return f"【用户已知偏好】{json.dumps(pref, ensure_ascii=False)}"

3.4.3 记忆的时效问题

持久化记忆有"过期"风险:用户偏好变了、历史结论失效了。对策:

  1. 时间戳:记忆带写入时间,超期降权。
  2. 主动验证:关键时刻问一句"还是按上次的偏好来吗?"(呼应第 18 章主动澄清)。
  3. 记忆版本化:可回滚(呼应第 24 章 G4 配置版本化)。

🛠 解决方案:Token 预算分配模板 + 上下文溢出排错指南

常见问题

  1. "窗口没满,但模型答非所问":注意力稀释 + 指令淹没(3.1.1)。对策:关键指令前置、裁剪冗余、系统提示词精简。
  2. "聊几轮就失忆":会话内失忆→历史被截断但未摘要;跨会话失忆→没有持久化(3.4)。对策:摘要压缩 + 长期记忆。
  3. "答案总是不完整":输出预留不足,生成被 max_tokens 截断(3.1.2)。对策:输出预留 ≥20%,检查 max_tokens。
  4. "工具结果太长,把上下文撑爆":全量回填工具响应(3.3.2)。对策:工具结果精简回填(第 14 章)。
  5. "长文档塞不进":全量塞超限。对策:转 RAG 检索式(3.3.1,第 8 章)。

解决方案速查表

现象 根因 解决方案
答非所问 注意力稀释 指令前置 + 裁剪
聊几轮失忆 无压缩/无持久化 摘要 + 长期记忆
答案不完整 输出预留不足 预留 20% + max_tokens
上下文爆炸 工具结果全量回填 精简回填
长文档塞不进 全量路线超限 转 RAG

实战提示

  1. 预算先行:任何 Agent 先做 token 预算分配(3.1.2 模板),再写逻辑。
  2. 输出预留尽量留:不留输出空间,容易导致模型"答题到一半交卷"。
  3. 摘要用增量:旧摘要+新消息→新摘要,别全量重摘要(省一半 token)。
  4. 上下文是注意力预算:设计"模型看到什么",而不是被动记录一切。
  5. 记忆分层落地:会话内→压缩,跨会话→持久化(第 9 章),别混为一谈。
相关推荐
智恒百亿1 小时前
AI服务器涨价15%背后:5090八卡智算服务器的选型与成本分析
大数据·服务器·人工智能·github
中科同志科技1 小时前
元器件拆焊真空焊接设备深度解读:工艺流程与优化策略
大数据·人工智能·机器人·汽车
政安晨1 小时前
政安晨【人工智能随笔】— 从像素到星际:游戏如何塑造了现代AI的二十年演进史 (读DeepMind的EVE宇宙AI研究有感)
人工智能·游戏·ai·智能体·deepmind·人工智能与游戏·智能体与游戏
zww89491111 小时前
AI导购线上商城搭建实战:从系统架构到部署全流程指南
人工智能·系统架构
leeyi1 小时前
Eino Callbacks 回调机制:在 Agent 执行每个环节注入自定义逻辑(第95篇-E81)
aigc·agent·ai编程
空堂与归1 小时前
英伟达AI服务器涨价超15%:HBM存储成本飙升,AI算力经济重构开始
服务器·人工智能·重构·英伟达
IT_陈寒1 小时前
Python多进程池的坑:子进程竟然不会退出
前端·人工智能·后端
苏子寒1 小时前
Nano-VLLM全代码解析笔记(2)-block_manager
人工智能·笔记·python·机器学习·nlp·vllm
大鹏说大话1 小时前
用 Semantic Kernel 在 .NET 中构建你的第一个 AI 智能体(Agent)
人工智能·.net