☰
上下文工程实战:从提示词到Agent记忆、压缩与隔离的完整指南
2026/10/1 10:58:08 网站建设 项目流程

1. 先搞清楚:上下文工程到底是什么

先说个我踩过的坑。早期我做 Agent,以为把系统提示词写得足够详细,Agent 就能稳定干活。结果上线没多久就翻车:用户问了三四轮之后,Agent 开始答非所问,甚至把前面几轮对话里的信息张冠李戴。我查了很久,最后发现根本不是提示词写得不好,而是整个上下文的组织方式出了问题——模型能"看到"的信息被我又塞又截,塞到最后它连自己刚说过什么都分不清了。

那段时间我反复验证,最终得出一个结论:在 Agent 开发里,提示词工程决定的是"模型怎么回答",而上下文工程决定的是"模型到底能回答什么"。前者管嘴巴,后者管眼睛和耳朵。模型再聪明,你给它看的上下文是残缺的、混乱的、超出预算的,它照样给你输出灾难级别的结果。这就是为什么现在圈内越来越多人提"上下文工程"这个词——它不是一个唬人的新概念,而是 Agent 稳定性问题背后真正的答案。

1.1 从提示词工程到上下文工程:一次范式转移

提示词工程的核心是"把话说清楚",它面向的是单次问答。你写一段 instruction,把任务说明白,把约束列清楚,模型按你说的做,完事。但 Agent 不一样,Agent 是多轮交互 + 多工具调用 + 多任务规划的组合体,它一跑起来就是几十轮对话、几十次工具返回值、成百上千条中间推理过程。这些信息全都要通过上下文窗口喂给模型。这时候上下文就成了一条"传送带",你往传送带上放什么、按什么顺序放、放多少,直接决定了 Agent 的表现。

我把这两者做过一个对比:

维度提示词工程上下文工程
关注对象一段提示词的措辞与结构模型可读到的全部信息的组织方式
作用范围单次或单轮生成多轮对话、工具调用、状态维护全流程
典型手段角色设定、few-shot、指令细化窗口裁剪、记忆压缩、证据链维护、动态注入
失败形态输出跑偏、格式不对上下文溢出、记忆丢失、行为漂移、幻觉加重
调试难度较低,改一段话就行较高,要追踪整个消息序列的生命周期

说句实在话,提示词工程的经验在 Agent 场景下依然有用,但它只是上下文工程里的一小块。真正的 Agent 开发难点,在于你如何让模型在"信息有限的窗口"里始终掌握"最关键的信息",同时保证对话连续性、工具调用正确性、长期记忆不丢。这才是上下文工程要解决的问题。

1.2 Agent 场景下上下文的三层角色:记忆、约束和证据链

在 Agent 里,上下文不是简单的一堆文本拼接。我把它的作用拆成三个层面来理解,这样调试的时候思路会清晰很多。

第一层是记忆(Memory)。模型没有真正的记忆,它每次生成都是"无状态"的,你给它什么它看什么。所谓记忆,本质上是你在上下文里复述了过去的对话和结论。比如用户说自己"人在北京,想去杭州玩三天",下一轮你问"预算多少",Agent 如果看不到上一轮信息,就会忘了用户在北京这个前提。上下文管理的首要任务,就是把该记住的对话信息持续保留在窗口里。

第二层是约束(Constraint)。系统提示词和任务边界就是靠上下文注入的。比如你做一个客服 Agent,系统提示里写了"只允许回答售后问题,不许做价格承诺",模型每轮都必须看到这段话,否则它就可能放飞自我。这个层面的核心问题是:约束信息不能丢,也不能被后面的信息淹没。

第三层是证据链(Evidence Chain)。Agent 每次调用工具拿到结果,这段"结果 + 结论"的组合就是证据链。比如 Agent 查了天气 API 拿到了"明天杭州下雨",然后基于这个数据回答用户"建议带伞"。如果上下文里丢弃了 API 返回值,模型下一步就可能凭空编造天气数据。很多 Agent 跑着跑着开始"一本正经地胡说八道",就是证据链断裂了。

所以上下文工程真正要做的事,可以概括成一句话:在有限的窗口里,动态维护一套包含记忆、约束和证据链的完整信息结构,让模型每时每刻都站在正确信息的地基上做推理。接下来我要讲的,就是这套结构的具体做法。

2. 上下文工程的五大核心模块

既然上下文在 Agent 里的角色这么重要,工程上该怎么落地?我这边实践下来,一个健壮的上下文体系至少包含五个模块:消息窗口、系统提示与动态注入、工具调用记录、外部知识注入、会话记忆与长期存储。逐个拆开讲。

2.1 消息窗口:Agent 的临时工作台

消息窗口是上下文的最底层载体。目前主流 LLM API 都采用"消息列表"的结构,不同 role 的消息按顺序组成一次完整请求。用的角色主要有四个:

  • system:系统指令,设定模型行为和边界
  • user:用户的输入
  • assistant:模型的回复
  • tool:工具调用的返回值

这个结构本身就是上下文工程的起点。很多新手犯的错是:把一大段历史记录全塞进user或assistant消息里,完全没有区分角色和来源。这样做的问题很明显——模型无法区分"这是别人说的"和"这是系统要求你的",指令冲突时它就不知道听谁的。

我建议所有消息都遵循"来源清晰、职责单一"的原则。系统约束只放system,用户原话只放user,模型生成的内容只放assistant,工具返回只放tool。别为了省事把什么都拼接成一个字符串丢进去。这个习惯养成了,后面做裁剪、压缩、回溯都会轻松很多。

2.2 系统提示词与动态注入:持续稳定的"世界观"

系统提示词是 Agent 的"世界观底座"。但这里有个很容易忽略的点:系统提示词不该是一篇静态的作文,而应该是一个可动态变化的模板。

为什么?因为 Agent 运行过程中很多关键信息是变化的。比如:

  • 当前时间:用户问"今天天气怎么样",模型需要知道"今天"是哪天
  • 用户身份:登录用户的昵称、会员等级、历史订单摘要
  • 环境信息:当前所在页面、设备类型、区域设置
  • 会话目标:用户本次进入对话的意图分类结果

这些信息如果在每轮请求时以静态文本写死,那模型拿到的就是过期信息。我的做法是把系统提示词拆成"固定骨架 + 动态插槽":固定骨架写死行为规范和通用边界,动态插槽在每次构造请求时用代码填充实时数据。来看一段简化示意的 Python 写法:

def build_system_prompt(user_profile: dict, current_time: str, session_intent: str) -> str: return f""" 你是一个在线购物平台的客服助理,负责处理售前咨询和售后问题。 【规则边界】 - 只回答与平台商品、订单、物流相关的问题 - 不承诺价格变动,涉及优惠活动时引导用户查看官方页面 - 遇到无法确认的信息,明确告知用户并建议转人工 【当前会话信息】 - 当前时间:{current_time} - 用户昵称:{user_profile.get('nickname', '未登录用户')} - 会员等级:{user_profile.get('vip_level', '普通用户')} - 近期订单数:{user_profile.get('recent_order_count', 0)} - 会话意图:{session_intent} 请基于以上信息,用友好、专业的语气回答用户问题。 """

这套写法最核心的作用是:让模型每一轮都能感知到实时环境,而不是活在自己的静态设定里。动态注入不是锦上添花,它是防止 Agent 产生"时间错乱"和"身份错乱"这类低级错误的关键手段。

2.3 工具调用记录:Agent 的手脚是如何串联起来的

工具调用是 Agent 区别于普通聊天机器人的核心能力,而工具调用的上下文管理是最容易翻车的地方。我见过太多人在这里栽跟头。

大模型厂商的 API 一般支持tool_calls参数,模型会输出一个结构化指令,比如:

{ "tool_calls": [ { "id": "call_123", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\": \"杭州\", \"date\": \"明天\"}" } } ] }

模型并不真的执行函数,它只是"提出请求"。真正的执行要由你的代码来完成,然后把执行结果以tool角色回填给模型。这个过程中,tool_call_id必须一一对应,否则模型不知道这个返回结果属于哪次调用,整个推理链就断了。

我习惯的做法是每轮工具调用后,把完整的调用链路放进上下文,包括:

  1. 模型发出的工具调用指令(tool_calls)
  2. 我执行的函数参数
  3. 函数返回的原始结果
  4. 我可能做的后处理结果(比如格式化、截断)

这些内容合在一起,构成了模型推理下一步行动的证据基础。还有一个细节:工具返回结果如果太长,不能原样全塞进上下文,要先做裁剪。比如天气 API 返回 3000 字的 JSON,模型其实只需要"城市+日期+天气+温度+建议"这几个字段,你要在注入前过滤掉无用字段。这个"结果预处理"机制能极大节省 token 空间,也避免无关字段干扰模型判断。

2.4 外部知识注入与检索上下文(RAG)

Agent 在处理专业领域问题时,光靠模型内化的知识远远不够,需要把外部知识动态注入上下文。这就要用到 RAG(检索增强生成)。

RAG 的基本链路是:把用户问题转化为检索向量,从知识库里召回 Top-K 相关片段,然后把片段拼进上下文作为参考信息。听起来简单,但这里有一个关键点经常被忽略:检索结果不是越多越好,而是越精越好。

你塞进去十段相关文档,模型反而不知道该以哪段为准,甚至可能出现"从文档里挑一句最顺眼的来答"这种随机行为。我的经验是把 Top-K 控制在 3~5 条以内,每条片段控制在 300~500 字,并且在知识片段前加一个明确的边界标识,比如:

【参考文档 1】来源:《产品使用手册》第 3.2 节 内容:xxx 【参考文档 2】来源:《常见问题 FAQ》第 7 条 内容:xxx 请优先参考以上文档回答,如果文档中找不到答案,请明确告知用户。

这个边界很重要,它能让模型识别出"这是参考材料,不是用户原话,也不是系统指令",从而避免信息源混淆。更重要的是,你要告诉模型"文档找不到就承认找不到",这样能有效减少 Agent 强行编造回答的情况。

2.5 会话记忆与长期存储

短期的消息窗口装不下所有历史,这时候就需要"记忆系统"出场。记忆系统分两层:

短期工作记忆:保留最近 N 轮完整对话,保证 Agent 能顺畅衔接当前任务。这里的 N 取决于你的 token 预算,一般保留最近 5~10 轮是比较稳妥的范围。

长期事实记忆:把关键信息抽取出来,存到独立的地方(数据库、向量库、KV 存储),下次需要时再注入。比如用户说"我家住在杭州西湖区",这个信息你不需要每轮都放在上下文里,但你可以在对话开始时注入一句"用户常住地:杭州西湖区",让模型记住这个事实。

这里有个经验:长期记忆不是把聊天记录原封不动存下来,而是抽取事实、去重、结构化。比如用户说"我上个月买的耳机坏了,想售后",抽取的事实是"用户有一副上个月购买的耳机、出现故障、有售后诉求"。下次对话时,你只需要注入这句结构化摘要,模型就能理解来龙去脉,而不用看完整聊天记录。这个"事实抽取 + 结构化存储 + 选择性注入"的模式,是 Agent 记忆系统的核心设计思路。

3. 实操:从 0 到 1 搭一个带上下文管理的 Agent

理论讲了这么多,直接上一个可以跑通的骨架。这个例子我用 FastAPI + LangChain + LangGraph 搭一个最小可运行的 Agent,重点展示上下文是怎么被构建、传递、裁剪的。这个架构我在几个练手项目里反复用过,稳定性和扩展性都还不错。

3.1 架构选型:为什么是 FastAPI + LangChain + LangGraph

先解释一下我为什么选这套组合,这涉及一个很现实的工程问题。

FastAPI 负责 HTTP 接口层,它轻量、异步支持好,做 Agent 服务端再合适不过。LangChain 负责对接各种大模型 API 和工具接口,省去自己封装 provider 的麻烦。LangGraph 是 LangChain 生态里的编排框架,它的核心优势是有向图状态机:每个节点处理一类任务(比如"规划"、"调用工具"、"生成回复"),节点之间通过状态对象传递数据。这个状态对象天然就可以作为上下文的载体,你不用自己去维护一个全局的消息列表,LangGraph 会帮你管。

这套组合最打动我的一点是:调试链路清晰。你可以单独把某个节点的输入输出打出来看,上下文在哪个环节丢了、哪个环节重复了,一目了然。相比之下,如果自己手写一个循环调 LLM 的 Agent,一旦出问题,你只能翻日志猜。

3.2 代码骨架:上下文构建、工具调用与状态流转

下面是我常用的一个 Agent 骨架,去掉业务细节,只保留核心结构。你可以把它当成模板直接改。

from typing import TypedDict, Literal from fastapi import FastAPI from langchain_openai import ChatOpenAI from langgraph.graph import StateGraph, END from langgraph.checkpoint.memory import MemorySaver # ---------- 1. 定义状态(它就是上下文的载体) ---------- class AgentState(TypedDict): messages: list # 完整消息列表,是上下文的主体 user_profile: dict # 动态注入的用户信息 current_time: str # 动态注入的时间信息 memory: dict # 长期记忆的临时载体 final_answer: str # 最终回复 # ---------- 2. 构建 LLM,绑定工具 ---------- def get_weather(city: str, date: str) -> str: """查询城市某日天气。真实场景中替换为天气 API 调用。""" # 模拟返回 return f"{city} 在 {date} 的天气:多云,22~28℃,适合出行。建议带伞。" tools = [get_weather] llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3) llm_with_tools = llm.bind_tools(tools) # ---------- 3. 定义系统提示词(固定骨架 + 动态插槽) ---------- def build_system_prompt(state: AgentState) -> str: profile = state.get("user_profile", {}) return f""" 你是一个智能生活助手,可以查询天气、提供建议。 【当前时间】:{state.get('current_time', '未知')} 【用户信息】:昵称 {profile.get('nickname', '未知')},常居城市 {profile.get('city', '未知')} 回答要简洁、准确,涉及数据引用时注明来源。 """ # ---------- 4. 节点 1:调用模型生成回复或工具请求 ---------- def call_model(state: AgentState): messages = state["messages"] system_msg = {"role": "system", "content": build_system_prompt(state)} response = llm_with_tools.invoke([system_msg] + messages) return {"messages": messages + [response]} # ---------- 5. 节点 2:执行工具调用 ---------- def execute_tools(state: AgentState): messages = state["messages"] last_msg = messages[-1] # 收集所有工具调用 tool_results = [] for tool_call in last_msg.tool_calls: tool_name = tool_call["name"] args = tool_call["args"] if tool_name == "get_weather": result = get_weather(**args) else: result = f"未找到工具:{tool_name}" # 关键:把结果包装成 tool 角色消息,带对应 tool_call_id tool_results.append({ "role": "tool", "tool_call_id": tool_call["id"], "content": result, }) # 把工具结果追加到消息列表,供模型下一步推理 return {"messages": messages + tool_results} # ---------- 6. 节点 3:判断是否继续调用工具 ---------- def should_continue(state: AgentState): last_msg = state["messages"][-1] if last_msg.tool_calls: return "continue" return "end" # ---------- 7. 组装图 ---------- graph = StateGraph(AgentState) graph.add_node("agent", call_model) graph.add_node("tools", execute_tools) graph.set_entry_point("agent") graph.add_conditional_edges("agent", should_continue, { "continue": "tools", "end": END, }) graph.add_edge("tools", "agent") app_graph = graph.compile(checkpointer=MemorySaver()) # ---------- 8. FastAPI 入口 ---------- app = FastAPI() @app.post("/chat") def chat(session_id: str, user_input: str): config = {"configurable": {"thread_id": session_id}} initial_state = { "messages": [{"role": "user", "content": user_input}], "user_profile": {"nickname": "张三", "city": "杭州"}, "current_time": "2026-01-15 09:30", } result = app_graph.invoke(initial_state, config=config) return {"reply": result["messages"][-1].content}

这套骨架的核心价值在于:上下文以messages字段在状态里流转,系统提示词每轮动态构建,工具结果带tool_call_id回填。你跑起来后,用 Postman 连续发几条消息,观察 LangGraph 的日志,就能清楚看到每轮模型的输入是什么、工具返回了什么、模型基于什么做出的最终回答。

3.3 上下文预算的分配与计算

有了骨架,接下来要面对一个现实问题:上下文窗口装不下无限信息,你得学会"精打细算"。我以gpt-4o-mini(128k 上下文窗口)为例,讲讲怎么分配预算。

假设你的 Agent 需要支持 10 轮对话 + 3 次工具调用,粗略估算:

组成部分预估 token 量说明
系统提示词800固定骨架 + 动态注入
最近 6 轮对话(user + assistant)3000平均每轮 500 token
3 次工具调用(请求 + 返回)1500平均每次 500 token
RAG 检索片段(3 段 × 400 字)1800中文场景 1 字 ≈ 1.5 token
预留 buffer(模型输出 + 意外增长)2000防止超限报错
合计9100远低于 128k,安全

这里我故意留了很大余量,为什么?因为实际使用中 token 消耗往往超出你的预估,尤其是工具返回结果和用户粘贴的大段文本,经常瞬间吃掉大量预算。我建议把预算使用率控制在理想上限的 70% 以内,超出就触发压缩或裁剪逻辑。别等到 API 报 context length exceeded 才开始补救,到那一步已经晚了。

关于 token 计算,你可以在开发环境用tiktoken做离线预判:

import tiktoken enc = tiktoken.encoding_for_model("gpt-4o-mini") text = "你要估算的文本内容" token_count = len(enc.encode(text)) print(f"token 数: {token_count}")

但要注意:不同编码器之间 token 数有差异,生产环境最好以真实 API 请求的usage字段为准。预判只是让你在开发阶段心里有数。

4. 上下文工程的关键机制:压缩、持久化与并发隔离

骨架能跑起来只是第一步。真实业务场景里,你会遇到三个绕不开的问题:上下文太长怎么办、重启后记忆怎么恢复、并发用户怎么保证上下文不串。这三个问题不解决,Agent 根本没法上生产。

4.1 上下文压缩:让 token 花在刀刃上

我一直把上下文比作一个行李箱——空间有限,装什么不装什么,全看你怎么取舍。压缩策略是上下文工程的核心技巧,我常用的有三种:

方案一:滚动窗口裁剪。最朴素的做法,只保留最近 N 轮消息,更早的直接丢弃。优点是简单,缺点是会丢失早期关键信息。适合业务逻辑简单、不需要长期记忆的场景。

方案二:摘要压缩。对早期对话调用一次 LLM,让它生成一段摘要,然后把摘要作为一条新的user或system消息放入上下文,替代原始多条消息。比如用户聊了 8 轮终于说清楚了需求,你把这 8 轮压缩成一句"用户需求:买一台预算 5000 以内的游戏本,优先考虑 RTX 4060 显卡的型号"。这样后续轮次模型仍然"记得"用户需求,但 token 消耗大幅降低。

方案三:结构化记忆 + 向量检索。把对话中的关键事实抽取成结构化条目存入向量库,每轮对话前根据当前用户输入检索最相关的 3~5 条记忆注入上下文。这个方案最复杂,但效果最好,适合长期陪伴型 Agent。

我个人的经验是三种方案组合使用:滚动窗口打底,摘要压缩处理"过时但仍有价值"的对话,结构化记忆负责长期事实。没必要一上来就上最复杂的方案,先评估你的场景是否是"长时多轮记忆敏感型",再做选择。

4.2 持久化与恢复:Agent 重启了,记忆不能丢

本地用MemorySaver跑没问题,但生产环境服务一重启,保存在内存里的会话状态就全没了。用户发现 Agent 忘记了自己几分钟前说过的话,体验会非常糟糕。

这时候要实现会话状态的持久化。LangGraph 提供了 checkpointer 机制,你可以把状态存到 Redis、PostgreSQL 或专门的向量数据库里。核心逻辑是:每个会话(session_id)对应一份状态快照,每当 Agent 完成一轮执行,把最新的状态序列化后写入存储;下一次用户发消息时,先从存储里加载该会话的历史状态,再追加新的用户输入。

这里有一个非常关键的工程细节:你持久化的不应该只是聊天记录,还包括记忆索引、Token 用量统计、以及上下文压缩策略的中间产物。这样即使正在执行压缩算法时服务崩溃,重启后也能从上次记录的位置继续,而不是从零再来。

另外,敏感信息要加密存储。用户聊天记录往往包含个人信息,直接明文落库存在合规风险。我的习惯是在入库前对关键字段做脱敏,输出时再按权限还原。这不仅是技术问题,也是产品底线问题。

4.3 并发场景下的上下文隔离:别让不同用户串台

"AI Agent 怎么扛并发"是最近特别热的话题。并发问题的核心不在于你的 FastAPI 能扛多少 QPS,而在于每个用户的上下文状态必须严格隔离。如果你用一个全局字典按user_id存历史消息,在高并发下不同请求的读写顺序一旦错乱,就可能出现 A 用户的上下文被 B 用户覆盖,Agent 开始用 A 的信息回答 B——这在生产环境是绝对的事故。

我的隔离方案分三层:

  • 接口层:每个 HTTP 请求通过请求头或请求体携带session_id,后端以此为 key 定位独立的状态对象
  • 存储层:Redis 以agent:session:{session_id}为 key 存储状态,TTL 按业务需求设置,比如 30 分钟无交互就清理
  • 计算层:用异步锁保证同一会话内操作有序执行。同一用户的并发请求很容易导致上下文互相覆盖,所以同一session_id的请求必须排队处理,不同session_id之间则可以并行

这里有个很常见的坑:很多人在 FastAPI 里用异步函数处理聊天请求,但调用 LLM 是同步阻塞的,处理不好就会卡住整个事件循环。我的做法是用run_in_executor把 LLM 调用放到线程池里跑,释放事件循环去处理其他请求。

import asyncio from concurrent.futures import ThreadPoolExecutor executor = ThreadPoolExecutor(max_workers=8) @app.post("/chat") async def chat(session_id: str, user_input: str): loop = asyncio.get_running_loop() # 把同步的 graph.invoke 丢到线程池 result = await loop.run_in_executor(executor, app_graph.invoke, initial_state, config) return {"reply": result["messages"][-1].content}

这个优化上去后,同样的机器配置,并发能力能提升好几倍。很多号称" Agent 扛不住并发"的情况,其实不是模型 API 的问题,而是你代码里阻塞了事件循环。

5. 常见问题与排查技巧实录

最后这部分,我把自己在实际调试和线上运维中遇到过的典型问题整理成一份速查表,外加几条独家避坑经验。这些问题我几乎都在同事的代码里见过一遍,你大概率也会遇到。

5.1 症状:Agent 跨几轮后丢失关键信息

现象:用户在第 1 轮说了"我预算 8000",第 8 轮问"有没有推荐",Agent 推荐了一款 15000 的笔记本。

原因:早期对话信息没有进入后续上下文。要么是滚动窗口把前面的消息裁掉了,要么是你压根没做摘要压缩和长期记忆抽取。

排查思路:把第 8 轮请求实际发送的 messages 列表打印出来,一条条看,检查"预算 8000"这条信息是否还存在。不存在就是记忆链路有问题,存在但模型没参考,那才是提示词引导的问题。

解法:做"关键信息前置",把用户画像、核心需求、关键约束在每轮系统提示词里动态注入一遍。让模型"每次翻开笔记本第一页就看到重点",而不是靠翻聊天记录去回忆。

5.2 症状:上下文长度持续暴涨,API 报错

现象:对话多轮后请求体越来越大,最终在接近模型上限时 API 返回 400 错误。

原因:消息列表只增不减,工具返回结果没有裁剪,RAG 片段重复注入无去重。

排查思路:检查每一轮请求的 usage 字段,prompt_tokens的增量是否异常。如果每轮增长超过 1500 token,甚至更多,说明你往上下文里塞了太多与当前任务无关的信息。

解法:分段设阈值触发压缩。比如消息超过 15 轮触发摘要压缩,工具返回单条超过 800 token 就裁剪后注入,RAG 结果按相关性去重后只保留 Top-3。另外,代码里要捕获 context length exceeded 异常,触发降级逻辑,而不是直接把报错抛给用户。

5.3 症状:Agent 开始答非所问,甚至"精分"

现象:Agent 忽然用完全不同的语气说话,或者开始引用别人对话里的信息。

原因:大概率是上下文里混入了不属于当前会话的内容。典型场景是并发环境下状态串了,或者是系统提示词里的动态插槽填错了数据,比如注入的是上一个用户的 profile。

排查思路:立刻检查系统提示词中动态注入的部分,再检查消息列表里是否有异常的用户输入。

解法:给每个会话打上独立的 trace_id,日志和上下文里都带上这个 ID。这样排查问题时可以直接按 trace_id 拉取完整的消息链,不用靠猜。同时在代码层面对 session_id 做严格的隔离校验,状态读写必须绑定同一 key。

5.4 排障速查表

症状可能原因快速验证方法首选修复方案
跨轮信息丢失早期消息被裁/未注入打印实际 messages 检查关键信息动态注入系统提示
Token 快速爆涨无压缩策略、工具返回未裁剪观察每次请求的 prompt_tokens设置触发式摘要压缩 + 结果过滤
行为"精分"上下文串台/注入错误信息检查 session_id 隔离与 trace_id加锁 + 独立 key + 日志链路追踪
模型胡编数据证据链断裂,工具结果未完整回填检查 tool_call_id 是否正确对应确保工具结果带 id 回填,保留证据片段
并发阻塞卡死同步调用堵住事件循环压测看响应耗时曲线用线程池异步化 LLM 调用
RAG 回答混乱检索片段过多/相互矛盾检查注入的 Top-K 数量与文本长度降到 Top-3,加边界标识,提示"没找到就承认"

5.5 我的一点独门心得

最后分享两个我自己常年受益的做法,属于那种"文档里不会写,但实测下来特别管用"的细节。

一个是给上下文做版本快照。每次重大改动后,我会保留一份旧版本的上下文构建逻辑,然后拿同一批测试用例跑 A/B 对比。别小看这个习惯,上下文工程的改动经常是"牵一发而动全身"——你优化了压缩策略,可能就把证据链截断了;你增加了动态注入字段,可能就挤掉了系统指令的位置。有快照才能快速回溯,没有快照你只能在黑暗里瞎摸。

另一个是不要把上下文工程做成"一次性交付"。Agent 跑在真实场景里,用户输入千奇百怪,上下文策略必须持续迭代。我建议每周抽 30 条线上真实对话做"上下文审计":看哪些轮次模型答错了、错的原因是不是上下文缺失或冗余,然后针对性调整压缩阈值、注入字段和工具返回的裁剪规则。上下文工程的本质就是"信息取舍的动态平衡",没有一劳永逸的方案,只有持续调优的习惯。

我在实际搭建各类 Agent 的过程中越来越确信一件事:模型能力决定了 Agent 的天花板,但上下文工程决定了 Agent 能不能够到那个天花板。很多项目前期跑得飞快,一到复杂业务场景就崩,根源往往不在模型选型,而在上下文这个"隐形地基"没打牢。把地基打牢,后面加功能、上并发、接记忆系统,都会顺畅很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询