news 2026/10/7 13:47:43

AI Agent上下文工程实战:从Token预算到记忆管理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI Agent上下文工程实战:从Token预算到记忆管理

AI Agent 之 深度解析上下文工程

这半年我一直在折腾 AI Agent 相关的项目,从最早用 LangChain 搭一个简单的链式调用,到后来用 LangGraph 做有状态的多智能体编排,再到帮团队落地一套基于 FastAPI 的 Agent 服务。走到最后发现一个扎心的事实:决定 Agent 上限的往往不是模型选得多强,而是你怎么管理它的“记忆”——也就是上下文。

很多刚开始接触 AI Agent 的朋友,最容易被各种花哨的框架和术语带偏,比如 ReAct、Plan-and-Execute、多智能体协作这些概念。但真正把 Agent 扔到生产环境里跑几天,你大概率会遇到同一个问题:上下文塞爆了、关键信息被截断了、Agent 聊着聊着就“失忆”了、甚至同一个上下文被多个请求并发复用导致数据串味。这些问题归根到底都属于上下文工程(Context Engineering)的范畴。这玩意儿在圈内已经慢慢从“没人提的小技巧”变成“决定 Agent 能不能落地的关键工程”,但我发现市面上系统讲它的中文资料还是太少,大部分都在讲概念,不讲怎么落地。

这篇文章我想从自己踩过的坑出发,把上下文工程这件事从原理到实操完整拆一遍。无论你是用 Spring AI 做 Java 后端的场景、用 Rust 追求极致性能的场景、还是基于扣子这类低代码平台搭智能体的场景,只要你的 Agent 需要面对多轮对话、长文档处理、多用户并发,上下文工程就是你绕不开的一课。

1. 上下文工程到底在解决什么问题

1.1 大模型的“记忆”到底有多大

先明确一个基础概念:大模型本身没有记忆。你不要被 ChatGPT 那种丝滑的多轮对话体验骗了,它之所以看起来记得住你之前说的话,是因为客户端把历史消息一遍又一遍地重新发给了模型。也就是说,对话历史不是被模型“存储”的,而是被“重新阅读”的。Agent 也是一样的逻辑,它的记忆本质上就是往上文里塞多少东西。

那这个“上文的容量”是多少呢?以目前常见的模型为例,GPT-4o 是 128K token 的上下文窗口,Claude 系列普遍做到 200K,国内一些开源模型的窗口也做到 128K 甚至更长。听起来很大对吧?但你要知道 token 不是字,一个 token 大约是 0.6 到 0.8 个汉字,1K token 大概能装 600 到 800 个汉字。那 128K token 大概就是 8 万到 10 万字——看起来一本中篇小说都放得下了。

但现实很骨感。你的 Agent 不是只跟用户聊天的,它要带工具定义、系统提示词、用户的历史对话、检索回来的参考文档、中间推理过程。我实测过一个典型的 Agent 调用链:光是系统提示词加十几个工具的 JSON Schema 定义,就能吃掉 3K 到 5K token;每次工具调用的输入输出又要消耗 2K 到 4K;用户的问题本身再加历史轮次,分分钟逼近窗口上限。所以你会发现,上下文窗口的“实际可用空间”远远小于标称值。

在项目里,我习惯用一个粗略的口诀去估算:模型标称 128K 的窗口,留出 25% 给模型输出(因为输出也必须落在窗口内),再留一些余量给系统提示和工具定义,真正能塞历史信息和参考资料的地方往往只剩一半左右。如果你在做一个需要反复查找文档的 Agent,这个空间会显得非常局促。这也是为什么单纯堆窗口长度不是治本之策,真正的关键在于你怎么“安排”这一亩三分地。

1.2 上下文工程的核心目标与边界

搞清楚了上下文窗口的物理限制,上下文工程要回答的问题就自然浮现了:如何在有限的窗口中,让模型拿到它最需要的信息,同时不要丢掉关键记忆,还要控制住成本和响应延迟。

我比较认可用一句话来概括:上下文工程,就是对模型可见的输入空间进行设计、裁剪、压缩、扩张和更新的系统性方法。它既包含对上下文内容的选择(哪些东西该放进去),也包含对上下文的形态设计(怎么组织结构让模型更容易理解)。它对比“提示词工程”要高一层——提示词工程关心的是“怎么把话说清楚”,上下文工程关心的是“怎么让模型在该看的范围内,看到该看的东西”。

这里要强调一个边界:上下文工程不是万能的,它解决不了模型本身能力不足的问题。比如你要 Agent 做高精度的数学推理,上下文怎么优化都不如换一个推理能力更强的模型来得实在。上下文工程的定位是在给定模型能力的前提下,把输入侧的信息利用效率拉到极致。换句话说,它是性价比最高的性能优化手段——因为换模型是成本的线性增长,而优化上下文往往是零成本甚至负成本。

2. 上下文工程的关键技术拆解

2.1 Token:上下文的最低计量单位

聊上下文工程,第一个绕不开的概念就是 token。这玩意儿很多同学一开始不重视,直到收到账单才发现问题的严重性。Token 是模型处理文本的最小单位,它不是按词或者按字来切的,而是一个基于词频统计训练出来的切分器产出的碎片。以英文为例,一个单词通常是一个或两个 token,而中文字符很多时候一个字就能拆出 1 到 2 个 token。这种切分方式对成本的影响是直接且巨大的,因为 API 计费是按 token 数量算的。

我给大家一个比较直观的日常参照:同样表达一个意思,用中文大概需要 400 到 500 个 token,用英文大概 250 到 350 个 token。这意味着一个面向中文用户的 Agent 项目,在其他条件相同的情况下,成本天然就要比英文项目高 30% 到 50%。这不是什么偏见,纯粹是 token 切分机制导致的结果。所以做成本规划的时候,不要只盯着模型单价,要按“预计对话轮数乘以每轮 token 消耗”来算总账。

那 token 和上下文工程有什么关系呢?关系太大了。上下文工程本质上就是在跟 token 预算做斗争。每一轮对话,Prompt(输入)+ Completion(输出)的 token 总和直接决定你的成本。我截个实际项目的账单数据给大家看:一个日均处理 1 万次请求的 Agent 服务,如果每次请求平均消耗 3K token,按照某主流模型的定价(每百万 token 输入约 15 美元),一天的模型成本大约在 450 元人民币左右。如果通过上下文压缩把每次请求的 token 降到 1.5K,成本直接对半砍。这就是为什么在 Agent 架构里,不能无脑把历史消息全部丢给模型。

2.2 上下文窗口不是越大越好

“既然窗口有上限,那我选一个窗口最大的模型不就好了?”——这是我在团队评审时经常听到的思路。但当你真的把所有历史一股脑塞进去,会发现几个大问题。

第一是注意力稀释。模型对上下文的注意力并不是均匀分布的,虽然 Transformer 结构理论上可以关注所有位置,但实践经验表明,模型对上下文中间位置的信息记忆往往不如开头和结尾。你塞了 100K 的对话历史进去,它可能只对最后几轮印象深刻,之前的关键信息照样“忘记”。

第二是响应质量下降。有一点很多人不知道:上下文越长,模型在生成时就越容易“迷失在长文本中”。这不是玄学,而是注意力机制在超长序列上的数值稳定性问题。我实测过用同一份资料做知识问答,把资料放在 8K 上下文中回答的准确率,显著高于把它埋在一个 60K 的大杂烩上下文里。信息没有变少,但模型“看不过来”了。

第三是延迟和成本飙升。Transformer 的注意力计算复杂度是输入长度的平方级,你的上下文翻一倍,计算量翻四倍。这意味着每轮请求的延迟变长、成本变高,而收益可能反而是负的。

所以上下文工程的一条铁律就是:能不塞进去的,就不塞;需要塞的,要放在最合适的位置。我自己在设计 Agent 时,会把上下文按“必须时刻可见的信息”和“需要时才检索的信息”做严格区分,前者常驻,后者按需加载。这样窗口的利用率会高很多。

2.3 记忆分层:短期、长期、工作记忆

在具体讨论代码怎么写之前,我想先分享一个在做 Agent 时非常受用的思维模型:把上下文当成一个“记忆系统”来管理。这借鉴了人脑的记忆机制,分为三个层次。

第一层是短期记忆(Short-term Memory),也就是当前对话窗口里模型能看到的所有内容。它对应的是你直接塞给模型的对话历史、当前输入、检索到的即时知识。短期记忆的特点是非常鲜活,模型对它的注意力最强,但容量有限,而且随会话结束就消失。

第二层是长期记忆(Long-term Memory),它有多种形态:向量数据库里存储的文档切片、外部数据库里的用户画像、只在需要时才检索出来的知识片段。长期记忆的本质是“外置存储”,模型不是无时无刻都能看到它,而是在需要的时候通过检索(比如 RAG)把它拉回来,拼接到上下文中。这种设计的好处是突破了上下文窗口的物理限制,代价是需要接受检索失败和检索延迟。

第三层是工作记忆(Working Memory),这个概念在 LangGraph 这类图编排框架里体现得最为明显。它指的是 Agent 在执行一个复杂任务的过程中,各个步骤之间需要传递的临时状态:比如当前任务的进度、已经完成的子步骤、中间产出物。工作记忆应该被显式管理,而不是混在短期记忆里被自动裁剪掉。否则一个多步骤任务做完第三步,前两步的关键结果可能已经被挤出窗口,任务就直接断线了。

我强烈建议每个做 Agent 的团队都按这个三层结构去梳理自己的上下文设计。先把记忆的数据流画清楚,再讨论用什么框架实现,思路会清晰非常多。

3. 实操过程与核心环节实现

3.1 三种主流上下文管理架构对比

在聊代码之前,先说说上下文管理的几种常见架构模式。我调研下来,业界用得比较多的有三类:

第一种:全量快照模式(Stateless Snapshot)。每次请求都携带整个会话历史,模型本身无状态。这是最简单的模式,适合对话轮数少、上下文比较短的应用。它的优点是实现简单、架构清晰,缺点是 token 消耗大,而且随着会话变长必然会撞到窗口上限。很多早期接入大模型 API 的 SaaS 产品都是这么干的,但做到后期没有一个不痛苦的。

第二种:滑动窗口模式(Sliding Window)。设定一个最大历史轮数,超过的轮次直接丢弃,只保留最近 N 轮对话。这种模式实现也不复杂,在内存里维护一个 deque 数组就行。它解决了无限增长的问题,但粗暴地“丢历史”会带来很严重的副作用——如果用户在第 3 轮提到了一个重要需求,第 20 轮还在依赖这个需求,那滑动窗口会在第 13 轮左右把这个需求挤出去,Agent 就彻底“失忆”了。所以它适合对历史依赖不强的场景,比如简单的闲聊机器人、FAQ 问答。

第三种:感知压缩模式(Context-aware Compression)。这是我现在最推荐在生产环境使用的模式。核心思想是:不再保留原始对话历史的全部细节,而是在关键节点对历史进行摘要、抽取出结构化记忆,只把摘要和最近几轮原文一起交给模型。举个例子:用户先聊了 3 轮产品需求,又聊了 5 轮技术方案细节,最后问你“帮我总结一下上次说的那个功能点”。全量模式会把 8 轮全部塞进去,滑动窗口可能丢得只剩最后 2 轮,而感知压缩会把 3 轮需求摘要成一句话“用户需要一款支持多租户的任务管理系统”,把 5 轮技术细节抽出关键决策,再加上当前的问题。模型拿到的东西更精炼,回答质量反而更高。

三种模式对比起来看,各有适用场景。我这里整理了一个快速选型参考:

架构模式实现复杂度Token 成本信息保留能力适用场景
全量快照极低极高完整但浪费短会话、简单问答
滑动窗口低中差,旧信息易丢闲聊、FAQ
感知压缩高低好,按需保留多轮复杂任务、Agent

3.2 一个最小可复现的上下文管理代码框架

说了这么多理论,是时候上点实操了。下面这个框架我是在一个基于 FastAPI + LangChain 的 Agent 服务里抽出来的,剥离了业务细节,保留了上下文管理的完整骨架。这个代码本身不依赖特定框架,即使你不是用 LangChain,换成 Spring AI 的 Message 对象也一样能套这个思路。

我用 Python 写一个ContextManager类,它的职责是管理一个会话里所有要放进模型上下文的内容。这个类最难的一步是“如何决定什么时候压缩历史”,我先实现一个基于规则的版本:当历史消息的 token 估算值超过阈值时,触发摘要压缩。

# context_manager.py import tiktoken from dataclasses import dataclass, field from typing import List, Dict, Any, Optional from langchain_core.messages import HumanMessage, AIMessage, SystemMessage # 统一用 tiktoken 做 token 估算,这里是 cl100k_base 编码 # 对于中文内容,实际切分片数会有偏差,但用于预算控制足够 _encoder = tiktoken.get_encoding("cl100k_base") def estimate_tokens(text: str) -> int: return len(_encoder.encode(text)) @dataclass class ContextState: """保存历史消息和当前系统提示词的容器""" system_prompt: str messages: List[Dict[str, str]] = field(default_factory=list) summary: Optional[str] = None # 压缩后的历史摘要 max_context_tokens: int = 10000 # 可用上下文预算,不含输出 compress_threshold: int = 8000 # 超过这个值触发压缩 def add_message(self, role: str, content: str) -> None: self.messages.append({"role": role, "content": content}) def current_token_usage(self) -> int: usage = estimate_tokens(self.system_prompt) if self.summary: usage += estimate_tokens(f"历史摘要: {self.summary}") for msg in self.messages: usage += estimate_tokens(msg["content"]) return usage class ContextManager: """负责在消息添加前后检查上下文占用并触发压缩策略""" def __init__(self, state: ContextState, summarizer=None): self.state = state # summarizer 需要是一个能接收消息列表并返回摘要字符串的 callable self.summarizer = summarizer def append(self, role: str, content: str) -> None: self.state.add_message(role, content) if self.should_compress(): self.compress_history() def should_compress(self) -> bool: return self.state.current_token_usage() > self.state.compress_threshold def compress_history(self): if not self.state.messages: return # 第一步:用模型对历史消息做摘要 old_messages = self.state.messages.copy() new_summary = self.summarizer(old_messages) if self.summarizer else \ f"[摘要占位] 共 {len(old_messages)} 条消息,关键结论: ..." # 第二步:合并旧的摘要和新的摘要 if self.state.summary: combined = f"{self.state.summary}\n{new_summary}" else: combined = new_summary self.state.summary = combined # 第三步:只保留最近 4 轮原始消息 self.state.messages = self.state.messages[-8:] # 每轮 message + response = 2 条 def build_messages_for_llm(self) -> List[Dict[str, str]]: """构造真正发送给模型的 messages 列表""" result = [{"role": "system", "content": self.state.system_prompt}] if self.state.summary: result.append({"role": "system", "content": f"以下是前面对话的历史摘要,请参考并继续: {self.state.summary}"}) result.extend(self.state.messages) return result

这个实现里有几个细节我要刻意说明一下。压缩后保留“最近 4 轮”不是拍脑袋定的,而是根据我在实践中总结出的经验:保留太少(比如 2 轮)会让模型对当下话题的细节掌握不够,保留太多(比如 10 轮)又会让摘要的压缩效果大打折扣。4 轮原文加完整摘要的组合,是我测试下来性价比最高的配置,你完全可以根据自己的业务调整这个数字。

在压缩触发的时机上也有讲究。我个人喜欢设置一个“余粮”机制:compress_threshold设置成max_context_tokens的 80% 而不是 100%,因为还要给模型输出留出空间。如果你算出来模型输出最多能到 2K token,那上下文预算至少要预留出这一块。否则就会出现一种诡异的情况:你这边明明还有 1K 的余量,但模型生成到一半直接报“context length exceeded”错误。

3.3 关键参数配置与调优经验

到了调参这一步,很多同学会问:这些阈值到底怎么设?有没有标准答案?我的回答是,没有标准答案,但有一套方法论。这套方法论是在一次次账单和报错中打磨出来的,下面几条我觉得含金量最高,建议你拿小本本记下来。

第一,最大上下文窗口不要卡死在上限。假设你用的是 128K 上下文的模型,我给你的参考配比是这样的:预留 20% 给模型输出,再预留 10% 作为波动余量(因为工具调用结果可能很长或很短),剩下大约 70% 才是你的上下文预算。算下来 128K 模型真正给你做改写、压缩、检索拼接的空间,大概只有 90K 左右。这个 70% 经验值来自我跑过的十几个项目,适用于绝大多数长上下文模型。

第二,系统提示词也要算进 token 预算。很多人做上下文工程只盯着对话历史,忽略了系统提示词。一份写得详细的系统提示词加工具定义,轻松 2K 到 4K token,这在 10K 预算的小上下文里已经占了三分之一。所以我建议每一个 Agent 项目都做一次“系统提示词审计”,把里面无关紧要的说明性文字删掉,能用结构化描述的不用自然语言堆砌。我曾经把一个 3.6K token 的系统提示词精简到 1.9K,效果不打折,成本和延迟都降下来了。

第三,压缩摘要本身也要控制篇幅。压缩不是无限压,摘要如果写得太长,反而把它想省的空间又占了。我一般把摘要目标控制在 200 到 500 字以内,并且强制用结构化格式输出。比如“用户需求: xxx;已确认决策: xxx;待办事项: xxx”。这样模型在后续引用摘要时能快速定位,不会像读散文一样逐字去翻。结构化摘要在实测中比自由文本摘要的问答准确率高 20% 左右。

4. 高级场景:并发、成本与系统集成

4.1 并发场景下的上下文隔离策略

如果只是做一个个人用的 Agent,上下文管理其实不用太复杂,单会话单线程就能搞定。但一旦要接业务流量,比如做一个小红书自动互动工具的服务端、一个面向多租户的企业助手系统,并发一来,上下文管理立刻从“怎么省 token”变成“怎么保证隔离和数据安全”。我在实际项目里吃过一次大亏:早期图省事,把所有会话的消息都放在一个全局列表里,结果两个用户同时在对话,消息互相串了,A 用户问的问题被 B 用户的 Agent 看到了。这在生产环境上是完全不能接受的失误。

并发场景下上下文管理的核心要求是会话隔离。最简单的方案是用一个session_id来标识每一个独立会话,在获取和更新上下文时都带上这个 ID:

# context_store.py from contextlib import contextmanager from typing import Dict import threading class SessionContextStore: def __init__(self): self._lock = threading.RLock() self._contexts: Dict[str, ContextState] = {} def get_or_create(self, session_id: str, system_prompt: str) -> ContextState: with self._lock: if session_id not in self._contexts: self._contexts[session_id] = ContextState(system_prompt=system_prompt) return self._contexts[session_id] @contextmanager def acquire(self, session_id: str, system_prompt: str): """上下文管理的并发安全访问入口""" ctx = self.get_or_create(session_id, system_prompt) with self._lock: yield ctx

这里用了threading.RLock来保证一段连续的读取-修改-写回操作不被并发请求打断。如果服务是多实例部署的,Dict就不可用了,得换成 Redis 之类的分布式存储,序列化方式可以用 JSON 或者 Pickle,但要注意版本兼容问题。我建议不论哪种存储方案,都要给 Store 层的“读改写”做一个统一封装,不要在外面散着直接操作存储,不然并发问题迟早找上门。

另一个比隔离更隐蔽的问题是共享系统提示词的设计。多租户场景下,系统提示词往往包含公共能力和租户特定信息两部分。我会把它们拆开:公共提示词是只读的常量,租户信息单独放一个字段每请求拼进去。这样既有复用效率,又不会把 A 租户的数据带到 B 租户的上下文里。

4.2 成本控制与Token预算

成本控制这件事,我发现很多人不是不会做,而是算不清账。上下文工程的很多技术手段都有明显的成本收益,如果用财务视角来看,你能更好地决定该在哪里投入技术成本。

先说直接的,token 消耗主要来自四个地方:系统提示词、对话历史、工具调用结果、模型输出。前两个可以通过上下文压缩来削减,第三个可以通过缓存和摘要来削减,第四个只能靠模型能力和生成质量来优化。我做项目预算时会给每个会话设定一个“token 上限”:比如一个会话最多允许消耗 100K token,超过就强制触发压缩或者结束会话。这个上限可以在每次请求后累积计算,也可以在服务层面设置账单告警。

给大家一组我实际测得的数据:对一个平均 8 轮对话的客服 Agent 场景,全量快照模式每个会话平均消耗约 28K token;使用滑动窗口压缩到最近 4 轮后,降到 16K;再加上摘要历史和结构化记忆之后,可以压到 9K 左右。也就是说,上下文工程做得好不好,可以带来三倍的成本差异。对于一个每月处理 10 万会话的业务来说,这个差异就是几十万人民币的量级,你不可能不重视。

另外要提醒一点,token 的计算在各家模型里不是完全一致的。你现在用 tiktoken 估算的是一个近似值,真正的计费以模型服务商后台显示的为准。所以做成本预算的时候,建议在估算值上再乘一个 1.2 的保险系数,用来吸收不同编码器和工具调用带来的波动。

4.3 与主流程集成的几种常见模式

上下文管理不是独立存在的模块,它要跟 Agent 的主流程深度绑定。我见过三种常见的集成方式。

第一种是中间件模式,把上下文管理封装成一个中间件,挂在 HTTP 请求处理链路上。请求进来时先加载上下文、检查 token 预算,调用模型再把新的消息写回去。这个模式的好处是侵入性低,业务代码根本感知不到上下文管理的存在。我在 FastAPI 里用一个依赖注入函数搞定:

# middleware_example.py from fastapi import FastAPI, Depends, Request app = FastAPI() async def load_context(request: Request): session_id = request.headers.get("X-Session-Id") store = request.app.state.context_store context = store.get_or_create(session_id, SYSTEM_PROMPT) return context @app.post("/chat") async def chat(request: Request, ctx=Depends(load_context)): # 业务逻辑里直接用 ctx.messages, ctx.summary 等状态 user_message = await request.json() ctx.add_message("user", user_message["content"]) # 检查是否需要压缩 ContextManager(ctx, summarizer=make_summarizer()).shrink_if_needed() # 构造模型输入并调用 ...

第二种是框架内模式,直接使用 LangGraph 这类支持状态管理的编排框架,把上下文状态放在图的 State 里。这种方式特别适合多步骤 Agent 任务。LangGraph 的优势在于它天然支持状态在节点间传递,节点 A 产生的结果可以显式地写入 State,节点 B 在下一轮从 State 读取,解决了我前面提到的“工作记忆”问题。

第三种是数据流模式,适合知识密集型 Agent。这种模式下,上下文管理不是一个独立的服务,而是穿插在检索、重写、生成整个数据链路里。核心思路是:用户问题进来 -> 先做 query 改写(结合摘要和当前问题) -> 检索 -> 重排 -> 按 token 预算截断检索结果 -> 拼装上下文 -> 生成回答。这种模式在 RAG 场景里效果最惊艳,因为它能把“检索到什么”和“给模型看什么”之间做一个精细的收窄控制。

我个人最常用的是第一种加第三种的组合:中间件负责会话级的状态管理,数据流模式负责单次请求内的上下文精装修。

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

5.1 上下文冲突与“角色错乱”

跑 Agent 时间长了,你会发现一些特别诡异的现象。最典型的一种是:Agent 聊着聊着,突然用错误的角色说话,或者引用了一段当前会话里根本不存在的信息。这种问题九成是上下文管理没有做好导致的。

有一次我排查一个客户工单 Agent,用户在前一轮问“帮我查订单号 A100 的物流状态”,Agent 回复了详情。过了一小时用户又问“那退款进度呢”,Agent 居然能答出和订单 A100 完全对不上的退款信息。我查了半天,最后发现问题出在滑动窗口把原始订单消息挤出去了,但摘要里只写了“用户查询物流状态”,没有保留订单号。模型看到上一条消息是“那退款进度呢”,根据摘要里的“物流状态”发挥,就编了一个答案。

这个案例给我的教训是:摘要压缩不能只保留“语义概括”,关键实体必须原样保留。我现在在实现 summarizer 的时候会专门抽取出用户提到的实体关键词(订单号、日期、金额、产品名等),拼在摘要的末尾。由于这些实体是之后轮次有可能引用的关键锚点,我会给它们更高的保留优先级。

5.2 截断丢信息:输出撞上窗口上限

第二个高频问题是截断。这个问题的表现是模型生成到一半就中断,或者后端报错。很多人第一反应是“模型上下文窗口不够”,然后无脑把窗口调大,或者把系统提示词改短。但如果你真的查过日志,会发现很多时候问题不是“输入太长”,而是“输出太长”。

这种情况在 Agent 工具调用场景尤为突出。Agent 有时会生成一大段文本再调用工具,这段文本加上后续工具的返回结果,会一次性超过模型剩余的输出预算。我在排查时养成了一个习惯:把每次请求的输入 token 数、输出 token 数、窗口上限三条信息打印在日志里。通过对比这三个数,能快速定位是输入侧的问题还是输出侧的问题。

如果是输出侧的问题,解决思路不是扩窗口,而是约束生成长度。你可以告诉模型“你的回答请控制在 300 字以内”,也可以在代码层面做校验,模型生成超过预算时截断并自动触发一轮“续写”或者“摘要”。遇到用 Rust 做高性能 Agent 服务的同学,还会提到流式输出对这个问题有天然的缓解作用——因为流式输出不需要一次性把整个响应算完,可以在接近预算时提前发出控制信号。

5.3 检索不到正确内容:上下文优化的边界问题

最后聊聊最让人头疼的检索问题。RAG 是很多 Agent 项目的基础能力,但上下文工程和 RAG 的关系经常被搞混。你做了一个完美的压缩策略,也做了合理的窗口预算,但 Agent 回答问题时还是拿不到正确的内容,那问题可能根本不在上下文工程,而在检索环节。

我遇到过太多这样的案例:向量检索在语义搜索上表现很好,但父文档层级控制不好,导致检索回来的片段跟问题对不上;或者文档切分固定按 500 个字符一刀切,把关键信息切到了两个 chunk 里。这类问题的典型特征是:你把这几个 chunk 搭给一个大模型看是能答对的,但给 Agent 用时就答不对——因为 Agent 的上下文已经被前面几轮对话占满了,检索模块只能在很小的空间里塞内容。

我的建议是,做 Agent 时不要把检索和上下文管理割裂开。它们应该是一个整体:检索模块负责“找内容”,上下文管理器负责“决定放什么内容进窗口”。在实作中,系统提示词里有一条很重要:告诉模型哪些上下文是辅助信息,哪些是核心依据。这听上去像是个提示词工程的小技巧,但它实际上改变了模型对上下文的注意力分配。检索回来的参考材料如果跟当前问题相关度不高,宁可舍弃也不要硬塞进去,因为那不只会浪费 token,还会污染模型的注意力。

结尾:一点私货与后续方向

写了这么多,最后分享一点纯个人实操体会。我做上下文工程这段时间,最大的感受是:这个方向的门槛不高,但天花板很高。任何一个懂 prompt 的人都能快速上手做一套简单的摘要压缩,但真要在生产环境里做到“省 token、不减效果、扛得住并发”,需要非常精细的打磨。不要迷信某一个框架或者某篇文章的现成方案,你以为的“最佳实践”,换一个业务场景可能就是巨坑。

如果非要给后来者一条建议,我会说:从你的应用日志开始,把每一轮请求的 token 消耗、上下文内容、模型回答质量都记录下来,坚持观察一周。你很快会发现上下文里的“垃圾信息”和“关键信息”分布得有多不均匀,也会发现自己设计的阈值在真实流量面前是多么不靠谱。数据不会骗人,上下文工程本来就是一项需要持续观测和调优的工程,而不是一次性配置完就不管的事。

这个方向后面还能怎么扩展?我目前正在折腾的是给上下文加一层“长期记忆反馈”:当 Agent 发现自己回答问题时反复需要翻看某条摘要,它会自动把这部分信息往“长期值得保留”的位置提升。这相当于让上下文管理具备了一定的自我进化能力。等这轮实验跑出稳定效果,我再写一篇文章跟大家汇报。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 13:47:09

LangGraph+Next.js构建ATS友好型AI简历工作流

1. 这不是又一个“AI简历生成器”,而是一套能真正下地干活的智能体工作流我去年帮三位朋友做过简历优化,其中一位是刚从大厂裸辞的前端工程师,另一位是转行做AI产品经理的博士,还有一位是想进外企但英语表达总卡壳的HR。他们共同的…

作者头像 李华
网站建设 2026/10/7 13:46:52

漫画助手v6脚本助手:批量出图与角色一致性实战指南

简介:stable diffusion漫画助手v6脚本助手是一份面向AI绘画创作者与Stable Diffusion进阶用户的实用工具资源,主要解决漫画风格生成流程中脚本调用与配套模型查找不便的问题,适合已具备基础出图经验、希望提升漫画创作效率的使用者。压缩包共…

作者头像 李华
网站建设 2026/10/7 13:46:38

Si9000阻抗设计实战:从物理模型到PCB制造的硬核闭环

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 13:46:31

Jev-IDS:基于SOM与少样本学习的单模型入侵检测系统解析

1. 从标题拆解Jev-IDS的核心命题第一次看到《Jev-IDS:网络入侵检测一种系统模型》这个标题,我的直觉是:这大概率是一篇把少样本学习和自组织映射网络(SOM)揉进入侵检测场景的工作。标题里“System One Model”这个说法…

作者头像 李华
网站建设 2026/10/7 13:46:03

华为云AgentArts实战:金融信贷智能体搭建与容错机制

金融信贷这个行业对AI智能体的态度一直很微妙——业务部门想要效率,风控部门怕出事,合规部门盯着每一句话的措辞。我最近花了两周时间在华为云AgentArts上搭了一套信贷场景的智能体,从知识库构建到工作流编排再到容错机制,踩了不少…

作者头像 李华
网站建设 2026/10/7 13:46:03

K230驱动闭环步进电机:位置、速度与回零控制实战

前阵子做一台视觉引导的小型分拣装置,手上正好有一块K230开发板和一台张大头闭环步进电机。最初我的想法很朴素:K230主要是跑视觉算法的,驱动器归驱动器,应该再用一块STM32专门发脉冲。后来算了一下物料和调试时间,觉得…

作者头像 李华