做Agent应用做得越久我越确认一件事长文本处理这事拼的不是模型窗口有多大而是上下文管理能力。你给模型塞2万字它能读但不代表它读得懂、读得准。我最早做文档问答Agent时也犯过这个毛病把200页产品手册一股脑丢进上下文第一轮问答看着还行第二轮开始胡说第三轮用户问“刚才那个参数是多少”模型直接编了一个数字出来。后来我才明白优雅地处理长文本关键不是“怎么喂”而是“怎么选、怎么理、怎么忘”。这篇文章就从问题根源讲起把Agent场景下长文本处理的核心思路、完整方案和踩坑记录都拆开讲一遍适合正在做Agent应用、或者准备把文档类问答接入业务流的开发者参考。1. 先看清问题Agent场景下的长文本到底难在哪1.1 长文本是个复合体不是单条输入很多人提到“长文本”第一反应就是那篇很长的文档。但在Agent场景里长文本远不止文档本身。一个真实运行的Agent在每一轮交互时上下文里其实塞着四类东西系统指令与工具说明告诉Agent它是谁、能干什么、回答时要遵守什么规则这部分通常就有几千token。工具调用记录与返回结果Agent调了哪个接口、传了什么参数、拿到了什么JSON、中间有没有报错这些过程和结果全要留在上下文里。检索到的知识片段从外部知识库、文档库、网页里捞回来的备选内容数量一多很容易膨胀。多轮对话历史用户上轮问过什么、Agent上轮答过什么、中间有没有澄清动作这些历史越积越长。我见过一个实际的酒店预订Agent用户从咨询、比价、确认、改期到取消来回七八轮交互。每一轮都要把之前的房间信息和价格带上前一个会话轻松到2万token。再加上平台的服务条款说明上下文瞬间逼近窗口上限。这个例子说明Agent的长文本压力不是某个单一文本造成的而是复合文本持续累积的结果。所以处理长文本的第一步不是考虑怎么把文档压缩而是先搞清楚上下文的构成比例。哪部分是高频变动的哪部分是固定不变的哪部分只在特定场景才需要。把这个问题理清楚后面所有方案的思路都会清晰得多。1.2 两笔藏不住的硬成本钱和注意力上下文越长成本越高这是明面上的账。按token计费的主流模型接口假设一次请求带2万token单次成本就是数千token的四五倍。看起来单次不贵但如果Agent每天处理几万轮对话累计起来就是一笔不可忽视的支出。很多团队做Demo时没感觉一上线做成本核算才发现大头全在上下文。另一笔账是注意力。学界和工程界早就发现一个现象当输入超过某个长度之后模型对中间位置内容的记忆和利用能力明显下降这就是业内说的“迷失在中间”。通俗地讲模型更像一个注意力有限的人开头和结尾的印象最深刻中间一大段内容容易看过就忘。你往上下文中间塞了一堆参考文档告诉模型“答案在里面”模型可能真的“看不到”。延迟也跟着涨。上下文长度每增加几千token每次请求的首字延迟都会上升用户会明显感觉到“转圈”的时间变长。在Agent这类需要多轮交互的场景里延迟和准确性同样致命。用户等久了会失去耐心模型在长上下文里反复“翻找”还容易出错。1.3 三个最常见的错误认知常见认知实际情况窗口越大越好窗口只是容量上限不是推荐用量超出有效范围后准确性会明显下降全部塞进去就不会漏信息过载比信息缺失更容易让Agent胡说八道切块就是按照字数切开打断语义边界会让检索结果变成“半句话”反而误导模型先谈窗口。现在的模型动不动就支持100k甚至200k上下文看起来空间绰绰有余但实测下来上下文超过一定阈值后回答质量会波动。窗口大是给你应急用的不是让你当仓库用的。再说“全部塞进去”。信息过载的典型表现是模型面对20个来源片段不知道该信哪个于是自己“脑补”了一个答案或者把A片段的信息套到B片段的问题上。相比之下只给5个高质量片段模型反而答得更克制、更准确。切块的问题我在后面实操部分会细讲这里先记住一句话切块不是切豆腐切得好不好直接影响检索和生成的质量。很多团队只按固定字数切把表格拆断了、把列表折成两半、把一组“问题—答案”拆成两块结果检索命中率极低。这个坑几乎每个做RAG的团队都踩过。2. 方案选型优雅处理长文本的四层设计2.1 第一层任务分解与路由能不带的坚决不带优雅处理长文本的第一原则是能不带的就不带。这不是偷懒而是遵循“上下文瘦身”的本质逻辑——模型不需要看的东西就不该出现在上下文里。具体怎么做靠任务分解和路由。用户问“这份合同有什么风险”这不是一个单步问题而是隐藏着多个子问题责任条款在哪儿、赔偿机制在哪儿、争议解决在哪儿。如果一开始就设计一个“合同风险分析Agent”它应该先做意图识别和问题拆解把大问题拆成几个小问题然后分别去检索对应章节最后汇总成一份结构化的风险清单。而不是把整份合同一次性倒进上下文让模型自己找。这个思路也适用于工具调用。Agent本身不应该带着所有工具的全部说明出场而应该按需加载。比如一个智能助理会订机票、查天气、记日程它不需要在启动时把所有工具的详细参数都读一遍只需要在用户真正需要某个功能的时候把对应工具的说明书临时加进上下文。做这个需要Agent框架支持动态工具注册值得在架构阶段就预留。类比一下真人读合同也不是每次从第一页念到最后一页而是按需翻章节。Agent也该这样。任务分解和路由是长文本治理的第一道闸门也是收益最明显的一道闸门。2.2 第二层检索优于全量把读全文改成找答案如果任务无法进一步分解比如用户就是问“某个具体参数在文档哪里写的”那下一步就是检索优于全量。检索增强生成RAG的核心思路很简单不要试图让模型读完整篇文档而是先从文档里找到与问题最相关的内容片段再把这些片段和问题一起交给模型作答。这就像考试时先查目录和索引而不是把教科书抄一遍。这里有个关键细节单纯用向量检索是不够的。向量检索擅长语义相似比如用户问“公司面临的最大风险是什么”它能匹配到文档里关于“经营风险”的段落。但向量检索对精确值不友好比如用户问“2023年Q3利润是多少”这是数字型问题如果切分和分词不到位向量检索可能搜不准。我自己常用的方案是混合检索向量检索加BM25关键词检索再用排序融合算法把两路结果合并。向量负责“语义联想”关键词负责“精确命中”两者互补。很多现成工具都自带这个能力如果没有自己撸一个也不复杂后面实操部分我会给一个简化实现。检索比全量的另一个好处是可控性。检索结果可以设置条数上限比如Top-5配合相关度分数阈值过滤。分太低就不给模型模型也就不可能拿低质量内容编答案。这种“宁可不说不愿错说”的特性在生产环境里非常重要。2.3 第三层压缩与蒸馏只留对当前问题有价值的信息检索回来的片段如果还是太长而且里面夹杂大量与用户问题无关的上下文那就需要做压缩和蒸馏。压缩分两种方式。第一种是抽取式压缩核心逻辑是把片段里的句子逐条判断这条与用户当前问题相关吗相关就保留不相关就删掉。这种方式的好处是保留原文原句不引入模型自己编的新词也方便后续做引用溯源。第二种是抽象式改写让模型用更短的句子重新概括段落内容好处是信息密度更高坏处是有可能丢掉关键细节、新增不存在的表述。我的建议是优先用抽取式实在需要进一步压缩时才用抽象式。抽象式一旦用不好就会把原文里的数字、人名、型号“约”掉。之前A同学的团队做产品客服Agent文档里产品价格是19999元让模型做抽象摘要之后硬生生变成了“约2万元”。用户按这个价格去买东西差一块钱都是事故。丢数字、丢专名是压缩操作里最典型的隐形事故。抽象式改写如果实在躲不掉务必要在压缩之前先做一轮关键信息抽取把数字、日期、型号、合同主体等结构化字段单独提取出来压缩完成后再拼接回去。这是血泪教训换来的经验后面踩坑部分再展开。2.4 第四层分层记忆该记的记该忘的忘长文本处理到最后真正决定一个Agent体验高低的是记忆管理。很多Agent为什么聊到第三轮就开始失忆因为把所有对话原文一股脑都塞进上下文既不筛选也不分层。分层记忆的经典做法分三个层次。短期记忆保留最近3到5轮的对话原文保留用户的原话、Agent给出的具体承诺这部分用于应付常见的连续追问。中期记忆是前面若干轮的会话摘要把“用户上上轮问了什么、Agent答了什么”压缩成一段话既保留关键信息又避免原文堆砌。长期记忆是提取出来的关键事实表比如用户的偏好、决策、已确认的订单号、重要的时间节点这些可以存进独立的存储在需要的时候才拉取。用大白话类比短期记忆像工作台面上摆着的文件随时能拿中期记忆像抽屉里归档的分类卷宗比台面低一档取用优先级长期记忆像仓库档案需要时才调阅。Agent没有记忆管理就等于一个把所有文件都铺在工作台上的办事员表面看起来啥都记得实际上啥都找不着。分层的另一个好处是可以精确控制上下文占用短期对话最多占多少token、中期摘要最多占多少token、长期记忆本次要不要拉取全部可以量化设计。这样不管用户连续聊多少轮上下文长度都能被控制在一个可预期的区间内而不是无脑增长。四个层次从“任务分解”“检索”到“压缩”“遗忘”本质是一条从源头到末端的治理链路。实际项目里并不需要每一层都用上具体用几层取决于数据形态、交互频率和延迟要求。我个人的经验是先把第二层检索做扎实再按需加其他层性价比最高。3. 核心实操完整实现一个Agent文档问答3.1 场景设定与方案总览用一个具体场景把前面的方法串起来。某团队要做一个“产品文档客服Agent”输入是一份200页的产品说明书PDF用户可以在线提问比如“F系列设备的最大功率是多少”“支持哪些网络协议”“故障灯闪烁怎么处理”。要求是多轮问答答案尽量带引用出处不许编造。整体方案分为离线与在线两部分。离线阶段解析PDF、清洗文本、按语义边界切块、生成向量索引和BM25索引。在线阶段用户提问后走混合检索拿到Top-N片段压缩去重组装进提示词交给模型生成答案然后追加到对话历史。整个过程中记忆按第三层的策略进行分层管理。这个方案不需要什么重型平台一台小服务器加一个支持嵌入和生成的模型接口就能跑。核心逻辑全在数据预处理和提示词拼接这两个环节。下面按步骤逐个拆。3.2 文本切分的参数与细节切得好不好决定检索准不准第一步是解析PDF并按照标题结构拆块。很多PDF工具能直接抽出文本但抽出来的往往是纯字符流标题样式、字号层级都会丢失。所以拿到文本之后先要恢复结构再处理内容。import pdfplumber import re # 1. 解析PDF按页抽取文本 with pdfplumber.open(product_manual.pdf) as pdf: pages [page.extract_text() for page in pdf.pages] # 2. 把每一页文本拼起来按行处理 all_lines [] for page in pages: all_lines.extend(page.split(\n)) # 3. 根据标题特征识别章节边界 def likely_heading(line: str) - bool: # 匹配 第1章 3.2 参数规格 F-2000 操作指南 这类标题特征 if re.match(r^第.[章节], line): return True if re.match(r^\d(\.\d)*\s\S, line): return True # 也可以配合字号、加粗信息判断这里从简 return False # 4. 按标题把文本分成结构块 blocks [] current_block [] for line in all_lines: if likely_heading(line) and current_block: blocks.append(\n.join(current_block)) current_block [line] else: current_block.append(line) if current_block: blocks.append(\n.join(current_block))这里有个容易被忽略的细节PDF解析出来的行顺序不一定完全对尤其是多栏排版、表格跨页等情况。如果发现解析结果乱序先做“栏检测”或者“阅读顺序恢复”否则后续所有步骤都会被带偏。推荐优先处理单栏文档多栏文档要么用带版面分析能力的解析器要么人工校验一遍。做完结构切分后有些章节依然很长比如某个产品规格表可能有几十行。这时需要二次切分。我常用的参数是chunk_size700字符、chunk_overlap80字符。700字符在中文场景下大约是四五百个token既能表达一个完整知识点检索时又不会引入太多噪音。重叠80字符是为了相邻切片之间的信息不断层特别是句子被拦腰截断时重叠窗口能保留下半句话。def split_text_by_size(text: str, chunk_size: int 700, overlap: int 80): 按字符数切分保留重叠区域 chunks [] start 0 while start len(text): end start chunk_size chunk text[start:end] chunks.append(chunk) if end len(text): break start end - overlap return chunks切分时最重要的原则是优先按语义边界切比如段落、列表项、表格行。只有实在遇到单一超长段落时才退回到按字符数硬切。千万不要在表格中间硬切那会让一行表格的前半边进一个切片、后半边进另一个切片检索时无论哪个切片都不完整。我在项目里就踩过这个坑后来加了“表格保护”逻辑遇到表格结构整表作为一个不可切分的单元超长的表格单独处理。3.3 混合检索的实现让语义与关键词互补切片做完之后每个切片就是一个基本检索单元。接下来同时建立两路索引向量索引与BM25关键词索引。# 向量索引用嵌入模型把切片转成向量再写入向量库 from sentence_transformers import SentenceTransformer import numpy as np embedder SentenceTransformer(BAAI/bge-small-zh-v1.5) chunk_vectors embedder.encode(chunks, normalize_embeddingsTrue) # 这里简化用numpy保存向量生产环境可替换为faiss或其他向量库 vector_db np.stack(chunk_vectors)向量检索时把用户问题转成向量和库内向量做余弦相似度计算取相似度最高的Top-K。这是语义联想的主力。但光有向量不够。用户问“F-2000支持Modbus协议吗”“F-2000”和“Modbus”都是强标识符靠向量可能命中相关但不够精确的段落。这时候BM25就该出场。BM25是经典的概率检索模型对关键词命中非常敏感“F-2000”一出现它就能精确锁定包含这个型号的切片。# BM25索引简化版记录每个词出现在哪些切片 from collections import Counter import jieba tokenized_chunks [list(jieba.cut(chunk)) for chunk in chunks] doc_freq Counter() for toks in tokenized_chunks: for tok in set(toks): doc_freq[tok] 1 def bm25_score(query_tokens, doc_tokens, avg_dl, doc_len, N, k11.5, b0.75): score 0.0 tf Counter(doc_tokens) idf_cache {} for tok in query_tokens: df doc_freq.get(tok, 0) # 防止分母为0做平滑 idf np.log((N - df 0.5) / (df 0.5) 1) f tf.get(tok, 0) score idf * (f * (k1 1)) / (f k1 * (1 - b b * doc_len / avg_dl)) return score两路结果怎么合并比较稳妥的算法是RRFReciprocal Rank Fusion倒数排名融合。思想很朴素同样的文档在两路结果里排名都靠前那它就该排最前面。RRF对分数绝对值不敏感只关心排名天然避免了两路分数量纲不同的问题。K 60 # RRF平滑常数 rrf_scores {} for rank, doc_id in enumerate(vector_top_k): rrf_scores[doc_id] rrf_scores.get(doc_id, 0) 1 / (K rank 1) for rank, doc_id in enumerate(bm25_top_k): rrf_scores[doc_id] rrf_scores.get(doc_id, 0) 1 / (K rank 1) final_top sorted(rrf_scores.items(), keylambda x: x[1], reverseTrue)[:N]RRF常数K一般取60这是社区实验后比较稳定的默认值。K越大排名差异对最终分数的惩罚越柔和。实际使用中我还会给两路结果分别设一个硬阈值相关性太低的片段即使被融合到前面也要过滤掉。混合检索的实测效果语义类问题“如果这台设备故障了该怎么办”向量路更容易命中精确类问题“型号F-2000的尺寸是多少”BM25路更强。两条腿走路总体召回率明显比单路高。3.4 上下文组装与token预算每一分token都要花在刀刃上检索结果不是越多越好组装进Prompt时要精心控制数量和顺序。这里给一份我常用的上下文预算表以25k总预算为例组成部分预算占比说明系统指令与抽取规则3k - 4kAgent角色、回答格式、引用要求检索片段最多5段5k - 8k每段控制在1k-1.5k保留编号对话历史最近3轮4k - 6k短暂保留原文支撑连续性长期记忆摘要1k - 2k关键事实、用户偏好、已确认信息工具返回结果2k - 4k按需携带用完即清回复预留2k - 4k保证模型有足够空间生成答案缓冲1k - 2k防止超出窗口上限25k这个数字是怎么来的它是按主流模型上下文开销和实测精度折中的结果。窗口更大当然能装更多但从“模型注意力有效性”出发25k以内通常能维持较好表现。如果你的模型上下文窗口只有16k那就按比例缩小各分区。Prompt组装顺序也有讲究。我的默认顺序是系统指令最早检索片段放在中间用户问题放在最后对话历史穿插在检索片段之后、用户问题之前。为什么要把用户问题放最后因为模型对最近位置的注意力最强问题紧贴生成入口能最大程度减少“遗忘”。下面是一个简化版的提示模板核心是把检索到的片段用标签包裹并分配编号系统指令 你是一名产品文档客服。回答只允许基于doc标签中的内容。 每个回答的末尾标注引用编号例如[1][2]。 如果标签中没有任何内容能回答问题直接说“我无法从现有资料中找到答案”禁止编造。 doc id1 F-2000设备最大功率为2200W支持AC 85V-264V宽电压输入。 /doc doc id2 F-2000支持Modbus RTU、Modbus TCP、Profinet三种网络协议。 /doc 对话历史 用户F-2000的功率是多少 助手F-2000最大功率为2200W支持宽电压输入。[1] 当前问题 用户它支持哪些网络协议这个模板有几个好处。第一检索片段有编号回答可以追到来源方便用户去原文核对。第二标签把模型注意力圈定在指定区域降低它从无关历史里“自己发挥”的概率。第三“无法回答时要承认”这条指令比任何后端兜底都便宜能有效压低幻觉。组装完毕之后记得做一次token统计。如果检索片段太长超出预算就裁掉分数最低的片段如果预算还有富余也不要擅自加片段维持原有数量即可。上下文组装这块宁缺毋滥。3.5 多轮记忆管理三段式记忆让上下文不再“滚雪球”记忆管理的目标很简单用户问十轮我的上下文不会变成“用户九轮对话原文九轮回复原文”的巨无霸。轮数越多压缩和分层的作用越明显。短期记忆用滑动窗口就能实现。维护一个队列最多保留最近3轮对话的原文。窗口满了最老的两轮对话就把原文从队列里弹出。注意这里弹出的是原文不是“删除”——被弹走的原文可以转去生成中期摘要。from collections import deque class MemoryManager: def __init__(self, short_window: int 3): self.short_memory deque(maxlenshort_window) # 最近N轮原文 self.medium_summary [] # 中期摘要列表 self.long_term_facts {} # 关键事实表key:字段名, value:值 def add_turn(self, user_query: str, assistant_reply: str): self.short_memory.append({user: user_query, assistant: assistant_reply}) # 窗口溢出时把最早那一轮弹出来做摘要 if len(self.short_memory) self.short_memory.maxlen: oldest self.short_memory[0] summary self._summarize_turn(oldest) self.medium_summary.append(summary) # 从摘要中抽取长期事实 self._extract_facts(oldest) def _summarize_turn(self, turn: dict) - str: # 调用模型做一次性压缩也可以写成模板化的规则抽取 return f用户询问{turn[user][:20]}助手答复的核心内容是{turn[assistant][:50]} def _extract_facts(self, turn: dict): # 用正则或模型抽取独立事实比如订单号、时间、型号 pass def build_context(self) - str: # 组装长期事实表 中期摘要 短期原文 parts [] parts.append(【长期事实】 str(self.long_term_facts)) parts.append(【中期摘要】 \n.join(self.medium_summary[-5:])) parts.append(【最近对话】) for turn in self.short_memory: parts.append(f用户{turn[user]}) parts.append(f助手{turn[assistant]}) return \n.join(parts)这个设计里有一个值得注意的细节中期摘要列表也要设上限比如最多保留5条超过就做“摘要的摘要”或者直接丢弃最早的低价值摘要。否则时间拉长之后中期摘要本身也会变成新的长文本。长期事实表则建议用结构化的方式保存比如“用户偏好靠窗座位”“已确认订单号ABC123”这些字段后续可以按需拉取。记忆分层的本质思路就是原始内容只在需要精确复述时才出场其他时间尽量用压缩摘要。这样上下文总量增长得慢Agent在长会话里也能保持相对稳定的表现。4. 踩坑复盘这些坑我替你踩过了4.1 检索到了但模型还是答错最常见也最让人崩溃的问题检索分数很高Top片段看起来也跟用户问题相关可模型给出的答案还是风马牛不相及。排查下来发现问题不在检索而在于检索结果的信息完整性。举个例子用户问“F-2000支持哪些安装方式”检索命中了第8章的第3小节但那一段开头写着“安装方式见表8-4”真正的表格在下一段。模型拿到了“见表8-4”这句话却不知道表8-4的具体内容自然只能瞎编。这就是检索命中但信息不完整导致的典型翻车。解决办法是“按段取整”。检索时不要以切片为单位而是以切片所在的完整章节段落为单位返回。如果命中的切片处于某个标题章节的中间位置就把这个章节从标题到结尾整段取回来再做压缩。这样虽然会稍微增加上下文长度但保住了信息的完整性。另一个辅助手段是给每个切片记录它的前序和后继片段ID命中时把相邻片段也拉回来防止“临界截断”。4.2 摘要压缩把关键数字“约”掉了A同学的团队做产品客服时发生过一件惨事一份设备规格表里写了“最大功率2200W”让模型做抽象摘要后变成了“最大功率约2000W”。用户结合数据下单结果实际参数对不上直接投诉。问题不在模型能力而在于摘要这种操作天然不擅长精确保留数值。从那以后我给团队定了几条铁律。第一压缩优先用抽取式尽量保留原文原句。第二压缩之前先跑一轮正则匹配把数字、金额、日期、型号、协议名等关键信息抽出来单独存一份。第三如果非用抽象式压缩不可把抽出关键信息和压缩结果做一个合并验证不一致就回退到原始片段。正则抽取的代码很简单比如import re numbers re.findall(r\d(?:\.\d)?(?:万|千|%|℃|W|KW|V)?, text) models re.findall(r[A-Z]-\d, text)虽然不完美但能兜住大部分需要精确保留的内容。抽象的“约”字是长文本压缩里最危险的隐形杀手。4.3 片段堆太多模型反而“迷路”还有一次实测我们把检索的Top-K从5调到了10想着给模型更多参考总没错。结果准确率不升反降。用户问“如何配置IP地址”模型在回答里混入了某个片段里提到的子网掩码规则而那段内容其实讲的是路由配置。这又是信息过载的典型表现。教训是检索回来10段不等于要全部塞进上下文。多不是优势精准才是。后来我们统一改成最终进入上下文的片段不超过5段每段不超过300字。同时设置相关性阈值低于阈值的片段直接丢弃。多出来那些高分片段如果确实有潜在价值放到后续追问的候选池里等用户进一步明确需求后再二次检索。宁可让模型说“我无法从现有资料中找到答案”也不要让它从10段互不相关的文字里强行拼出一个答案。4.4 延迟拉满用户等不起最后一个坑集中在性能层面。长文本场景下的延迟主要来自三块嵌入计算、检索耗时、生成耗时。嵌入计算可以在离线阶段预生成所有切片的向量在线时只对用户问题做一次嵌入延迟可忽略。检索耗时在数据量大时比较明显单机扫描几万条向量和倒排索引看起来快但QPS一上来就撑不住。生成耗时则是上下文越长越高。我们做了三件事把整体延迟降下来。第一检索并行化向量路和BM25路同时跑结果汇合后再做RRF。第二给高频问题做缓存同一个问题直接返回缓存的答案不再走完整链路。第三生成启用流式输出用户看到早期token的时间大幅提前心理等待感明显降低。另外如果项目对延迟极其敏感可以考虑把嵌入模型换成更小更快的那一档代价是精度略降但换来的响应速度通常值得。5. 一些体会设计前先问自己一句“该不该带”项目做到后面我自己形成了一份长文本处理的自检清单每次设计Agent方案时都会过一遍当前这个Agent上下文中是否存在与用户当前意图无关的内容切分文本时是否尊重了标题、段落、表格等语义边界检索结果是否带了来源编号能否支撑引用溯源记忆是否区分了短期原文、中期摘要和长期事实模型是否被允许说“我不知道”而不是拿不完整信息硬编压缩摘要之后关键数字和专名有没有二次校验这份清单不是KPI也不是流程文件它是我在多次项目返工中沉淀下来的实际操作准绳。每次出问题回查清单总能找到对应的疏忽项。说实话踩了这么多坑之后我现在给自己定了一条规矩每次设计Agent先问一句——这一步它真的需要看见全文吗想明白这个问题长文本的坑一大半都不用踩。上下文窗口再大也大不过内容的积累速度真正的优雅不是把窗口塞满而是让Agent在正确的位置找到正确的信息其余的都留着别带。