Codex 上下文窗口优化:Token 预算、笔记记忆与 Astra 历史检索实践
用 Codex 写代码多了你一定遇到过这个场景项目改了半小时Codex 突然像失忆一样不记得几分钟前自己刚定下的接口名甚至重新读了一遍已经看过的文件。这不是模型变笨了而是上下文窗口被塞满了。Codex 的一次会话能放进窗口的 token 有限一旦超出最早的内容就被“挤”出去。本文就围绕 Codex 怎么对抗这个上下文窗口限制展开聊聊三件事怎么管好 token 预算、怎么用笔记做跨会话记忆、怎么靠历史检索在需要时把旧信息找回来。其中 Astra 在历史检索这条路上担着一个关键角色——它像一个外置的第二大脑帮 Codex 把“记不住”变成“查得到”。这篇文章适合重度使用 Codex 做多轮重构、跨天项目迭代、或者经常开新会话继续旧任务的开发者看完可以直接照着搭一套自己的记忆增强方案。1. 先搞清楚Codex 的上下文窗口到底卡在哪1.1 上下文窗口不是内存条是工作台很多人把上下文窗口理解成电脑内存觉得窗口越大能记住的东西就越多。这个类比不太对。内存是你说“记住”就记住模型不是。上下文窗口更像一张工作台你只能把需要用到的资料摆在桌面上摆不下的就只能收进抽屉。问题在于模型没有真正的“抽屉”所有材料一旦移出窗口就彻底没了。Codex 作为编程代理比普通聊天更耗窗口。原因很简单它不只是“看一句回一句”而是会经历一个循环——读文件、改代码、执行命令、看报错、再改。每一步都是一次工具调用每次工具调用的输入输出都要占 token。ChatGPT 里你聊十句可能才消耗几千 tokenCodex 里做一轮“读文件 → 修改 → 编译 → 看结果”可能就已经吃掉一两万 token。所以 Codex 的上下文窗口不是“它能背多少书”而是“它当前一次能同时看到多少材料”。理解了这一点所有优化手段就都清晰了要么减少摆上桌的材料要么在需要时再从抽屉里精准取出一小份。1.2 一次重构任务里token 是怎么被吃光的拿一个真实的例子算笔账。假设我要在某个老项目里新增一个用户权限模块Codex 需要系统指令、Codex 自身的提示词、工具定义这部分固定开销通常在 3000~8000 token 左右我给的初始任务描述几百到一千 token读取项目结构一次ls或查看文件树几百 token读取相关代码文件每个文件少则 500、多则 2000 token如果涉及跨模块改动一次要读四五个文件轻松破万执行测试或编译输出结果动不动就是上千 token 的报错堆栈对话历史累积每多一轮交互前面所有轮次的输入输出都要继续占用空间。实测下来一个中等规模的功能开发Codex 跑完一个完整任务通常消耗 30k~80k token。如果模型上下文窗口是 200k前一两轮还能撑住但到第三四轮开始涉及历史文件调整时最早的约束条件就被挤掉了。这也是为什么总有人抱怨“Codex 改着改着就忘了最开始说的不准动某个文件”。1.3 为什么“换更大窗口模型”不能一劳永逸那换个 1M 上下文窗口的模型行不行行但只能治标不能治本。窗口变大成本线性上升延迟也明显增加。更麻烦的是模型对中间部分内容的注意力会下降学术界把这个问题叫“lost in the middle”。就是说哪怕信息还在窗口里模型也不一定真的“注意”到它结果就是你又塞了更多 token又贵又慢回头该忘还是忘。我的观点是上下文窗口是一个“预算”问题不是一个“容量”问题。真正该做的不是无限扩大桌子而是控制摆上桌的东西。接下来的策略总结下来就三条线管预算、做笔记、搞检索。2. 第一道防线把 token 预算当成项目预算来管2.1 动手前先“记账”估算一次任务要花多少 token既然要管预算第一步就是记账。我习惯在给 Codex 派任务之前先估算这次任务大概会消耗多少 token心里有个数免得任务跑到一半窗口就爆了。估算公式不复杂总消耗 ≈ 固定开销 单轮工具调用消耗 × 预计轮数 最终输出 20% 缓冲固定开销就是 Codex 系统提示、AGENTS.md 内容、任务描述。工具调用消耗可以按“读文件每 1000 行代码约 3000~5000 token”来粗算。最终输出按实际代码量算通常 500~2000 行代码就是 5000~20000 token。举例一个任务预计要读 5 个文件共约 2000 行代码跑 8 轮工具调用产出约 500 行新代码那么估算就是固定开销5000 token工具调用2000 行代码约 8000 token × 8 轮 ≈ 64000 token最终输出约 6000 token合计约 75000 token再加 20% 缓冲接近 90000 token如果这个数接近甚至超过窗口上限就必须在开工前做减负比如只让它先读最关键的文件、明确禁止它频繁跑全量测试。记账不是目的目的是逼自己提前做取舍。2.2 Codex 配置里值得调整的几个关键参数Codex CLI 的配置文件config.toml里有不少可以控制“钱包”的选项。我自己最常用的几个model指定模型。模型决定窗口上限别选太老的模型硬撑。temperature控制随机性。做代码任务我习惯调到 0 或 0.2既减少无谓的试错也降低因为“发挥”导致的多轮拉扯。提供商的wire_api决定走responses还是chat协议。这个参数如果和模型不匹配会出现模型不支持之类的报错后面常见问题里会细说。会话轮数相关的限制参数主动控制最大交互轮数防止 Codex 在一个问题上反复打转烧 token。注意Codex 的计算是按照输入、输出、缓存分开计费的。同一段代码如果能在多轮里走缓存会便宜很多。所以“尽量少改动已读取文件的基础结构”不只是代码习惯问题也是在省 token。2.3 笔记先行让 Codex 按需读取而不是每次都背一遍既然窗口有限那就别让 Codex 每次都把项目背景从头背一遍。我的做法是把项目里那些“每轮都需要知道”的信息放进根目录的AGENTS.md把“当前任务才需要知道”的信息写进一个笔记文件有需要时让 Codex 再去读取。AGENTS.md里放什么我放的是三类“契约”构建命令、测试命令、代码风格约束。比如构建用uv sync make build测试统一跑pytest tests/unit -x新代码必须加类型注解禁止使用Any糊弄所有对外接口变更必须同步更新docs/api.md这些内容每次会话都会自动进入上下文所以一定要精简。那些“这次任务才需要的文件清单”“当前进度到哪一步了”之类的信息千万别塞进AGENTS.md而是单独开一个prompt/notes/目录每轮任务开始前由我或者 Codex 自己读一遍。这样一来Codex 的固定开销被压到最低预算大头都花在真正的代码逻辑上。3. 第二道防线笔记与摘要把记忆存档而不是背诵3.1 三类笔记分清楚才能不乱管好预算之后第二个要解决的是跨会话的“记忆”。一次会话的上下文窗口再省也扛不住跨天、跨周的项目。这时候就要靠笔记把记忆“存档”。笔记不是随便写我建议分成三类笔记类型作用是否自动进入窗口举例契约笔记固定规则、命令、约束是但要求精简AGENTS.md运行笔记当前任务进度、下一步计划否按需读取prompt/notes/2025-06-01.md决策记录为什么做这个选择的背景否供检索docs/decisions/001-user-auth-flow.md分类的目的是区分“每次都要知道”和“偶尔需要知道”。如果所有信息都往窗口里放和没做笔记有什么区别。3.2 跨会话传递用文件系统当临时记忆我最常用的做法很朴素每次 Codex 会话结束前让它把“当前进度”和“下一步计划”写进一个运行笔记文件。下次开新会话第一句话就是“先读prompt/notes/2025-06-01.md了解上次进度然后继续”。这个方式的成本极低效果却很明显。Codex 不需要在窗口里保留几十轮前的完整对话只需要保留一份几百 token 的进度摘要。相当于把历史从“逐字背诵”改成了“读总结”。我会在会话末尾加一句固定的提示词在结束之前请把本次会话的进展写入prompt/notes/today.md内容包括已完成的修改、关键文件和函数、测试结果、遗留问题、下一步计划。每项控制在 200 token 以内。每次会话结束后再把这个文件复制到带日期的文件名里归档。这么做有一个额外好处即使过了三个月我也能快速知道当时改到哪了。3.3 摘要压缩与其删除不如提炼会话太长的时候很多人会选择直接开新会话把旧的都扔掉。我强烈不建议这么做。旧会话里往往有大量“失败尝试”的信息这些信息恰恰是避免重蹈覆辙的关键。但硬把旧对话塞回去又不现实怎么办靠摘要压缩。我是这样做的当会话超过窗口 60%~70% 的时候主动喊停让 Codex 先输出一份结构化摘要然后再开始新会话。摘要的提示词模板大致是请为当前会话生成一份结构化摘要要求1. 目标与当前状态2. 已完成的关键修改按文件列出3. 被验证过的失败方案及原因4. 未完成事项5. 下一步具体动作。全文控制在 800 token 以内不要保留代码细节只保留结论和引用。输出完毕后我把摘要另存为新的运行笔记。摘要当然会丢失细节所以原会话我并不会立刻删除而是保存到历史目录。这样一旦摘要信息不够还能从历史的检索里找回原文。摘要是一条“线索”不是“替代品”。3.4 实操示例一条命令把上次笔记注入新会话如果你用 Codex CLI可以直接在启动时拼一个提示词让它先读最新笔记。我通常用一个简单的 shell 脚本#!/usr/bin/env bash # 启动 Codex 并加载最新运行笔记 LATEST_NOTE$(ls -t prompt/notes/*.md | head -1) codex 先读取 ${LATEST_NOTE}这是上次会话的进度记录请基于它继续任务。如果笔记里有未完成事项优先处理。这样一个十几行的脚本就解决了“每次都得手动复制历史背景”的麻烦。实测下来新会话进入状态的轮次从原来的三四轮缩减到一两轮。4. 第三道防线历史检索翻旧账靠邻居而不是靠全文4.1 为什么文件系统笔记在大型项目里不够用笔记做久了问题就来了prompt/notes/目录里可能有几十个文件全塞进窗口不现实靠grep搜关键词又经常搜不到。因为你不一定记得当时用了什么词比如你只记得“当时讨论过数据库连接池的问题”但笔记里写的是“connection pool timeout”关键词对不上就搜不出来。这时候就需要语义检索不是按字面匹配而是按“意思相近”来找。你输入“数据库连接池”它能把“connection pool timeout”相关的片段捞出来。这就是向量检索的强项也是历史检索这条线的核心。4.2 最小可用的向量检索方案如果你只是个人项目其实用不着马上上重型数据库先用 FAISS 本地文件就能把原理跑通。流程是把笔记、决策记录、代码注释切成块每块大约 500~800 token相邻块之间重叠 50~100 token保证上下文不割裂用 embedding 模型把每块文本转成向量查询的时候把问题也转成向量然后找“最近邻”。Python 示例大致长这样from openai import OpenAI import faiss import numpy as np client OpenAI() def embed(texts): resp client.embeddings.create( modeltext-embedding-3-small, inputtexts ) return [item.embedding for item in resp.data] # 假设 notes 是分块后的文本列表 vectors embed(notes) dim len(vectors[0]) index faiss.IndexFlatIP(dim) index.add(np.array(vectors).astype(float32)) # 查询 query_vec embed([数据库连接池超时的问题]) scores, idx index.search(np.array(query_vec).astype(float32), k3) for i in idx[0]: print(notes[i])这套方案做实验足够但等到项目大了、笔记多了、想加过滤条件了本地文件就不太好维护了。这个时候就需要一个真正的向量数据库也就是 Astra 要登场的地方。4.3 把检索结果注入 Codex prompt 的样例检索不是终点把结果喂回 Codex 才是。我的做法是在任务描述里直接加入检索片段并标注来源。构造 prompt 的示例def build_prompt(task, retrieved_chunks): context \n\n---\n\n.join( f[来源: {chunk[metadata][file]}] {chunk[text]} for chunk in retrieved_chunks ) return ( 下面是历史上下文的检索结果可能和当前任务相关请参考但不盲从\n f{context}\n\n f当前任务{task} )注入位置我会放在用户消息的第一轮而不是放到 system prompt。原因很简单用户首轮消息离最终输出较近模型的注意力通常更强检索片段放这里是性价比最高的位置同时也能避免污染 Codex 自己的系统指令。5. Astra 到底在哪里发力当向量数据库成为第二大脑5.1 Astra 是什么为什么我选它而不是继续用本地文件这里说的 Astra指的是 DataStax 家的 Astra DB——一个基于 Cassandra 的托管向量数据库服务。如果你在别的地方听到同名的其他产品可能概念不同但在这套“历史检索”方案里我拿它当分布式记忆层用。为什么不继续用 FAISS因为 FAISS 只能解决“查相似向量”这一个问题项目相关的过滤条件它不管。比如我只想检索上个月的决策记录或者只看某个模块的代码片段FAISS 就得自己维护一堆索引文件很麻烦。Astra DB 的好处是它同时具备键值存储和向量检索能力每一条记录可以带 metadata检索的时候能直接按字段过滤。选 Astra 替代本地方案还有一个更现实的理由多人协作的时候记忆是共享的。我们团队三个人同时跑 Codex如果各存各的 FAISS 索引谁改了什么都对不上。用 Astra 作为共享记忆层大家写入的代码片段、决策记录都会汇聚到同一个 collection 里Codex 检索到的是整个团队的经验。5.2 Astra 里的数据模型三类集合别把鸡蛋放一个篮子在实际使用中我会按内容类型分成三个集合而不是把什么都塞进一个大库集合名存储内容典型 metadataconversation_chunks会话摘要、关键结论、失败尝试project, session_id, timestampcode_snippets关键代码片段、函数签名、接口定义project, file, languagedecision_logs技术选型、架构决策、踩坑记录project, date, author每条记录的核心字段是text和embedding其余全是 metadata。metadata 是 Astra 的杀手锏它能让你在检索时先缩小范围再做向量搜索速度和准确率都会高很多。用 astrapy 客户端写入的代码大致如下from astrapy import DataAPIClient client DataAPIClient(ASTRA_TOKEN) db client.get_database_by_api_endpoint(ASTRA_API_ENDPOINT) collection db.get_collection(conversation_chunks) collection.insert_one({ text: 数据库连接池超时原因是默认连接数太小已把 maxPoolSize 调到 50, $vector: embedding_vector, # 由 embedding 模型生成 metadata: { project: billing-service, session_id: 2025-06-01-001, timestamp: 2025-06-01T10:30:00Z, } })这里的$vector字段就是 Astra 存储向量并建立索引的约定写法。插入完成后它会自动参与后续的向量检索不需要额外做索引重建。这是托管服务比本地 FAISS 省心的地方。5.3 检索组合拳向量相似度 元数据过滤检索的时候最忌讳的就是“一检索就把全部数据拉出来”。Astra 支持在向量搜索之前先用 metadata 过滤这非常实用。比如我只需要当前项目、最近三十天、类型是“决策记录”的内容results collection.find( filter{ metadata.project: billing-service, metadata.timestamp: {$gte: 2025-05-01}, metadata.type: decision, }, sort{$vector: query_vector}, limit5, projection{_id: 0, text: 1, metadata: 1}, ) for doc in results: print(doc[metadata].get(file), doc[text][:200])这比单纯靠向量相似度靠谱得多。很多检索不准的问题其实不在向量质量而是因为没做 metadata 过滤导致不同项目的笔记互相干扰。Astra 在较新的版本里还支持混合检索把向量召回和 BM25 关键词召回结合。像函数名、报错码这类精确信息用关键词召回效果反而更好。我实际使用中会把两者结合先跑关键词召回再跑向量召回最后合并去重效果比单一方式好很多。5.4 完整链路串起来Codex 怎么用上 Astra把这套东西接到 Codex 上大致是这样一条链路Codex 任务开始时运行一个初始化脚本读取AGENTS.md和最新运行笔记确定当前任务的关键词初始化脚本把任务描述转成 embedding 向量向量作为查询条件发给本地检索服务我通常用 FastAPI 包一层避免每次都写 astrapy 代码检索服务到 Astra 里做向量 metadata 过滤取回 top-k 片段片段被注入 Codex 的初始 prompt像上一章说的那样带上来源标识会话过程中Codex 产生的关键决策、修改摘要实时或定期写回 Astra下一次会话再查询时就相当于拥有了之前所有会话的“长期记忆”。这套链路跑起来之后Codex 的窗口里永远只有最相关的少量片段但它的“可检索记忆”是无限的。本质上就是把上下文窗口的边界从模型层挪到了数据层。6. 实际踩坑记录与排查思路6.1 配置了半天模型没生效先检查提供方和 wire_api我见过不少人把模型名改成一个自定义名称结果 Codex 根本不认报错信息类似the gpt-5.6-sol model is not supported when using codex with a chatgpt account。这个问题的本质是你用 ChatGPT 账号登录的时候Codex 只允许官方支持的模型想用第三方模型或者企业内部模型必须走自定义的 model provider配合对应的 API key、base_url、wire_api 设置。检查顺序建议是确认你用的是 API key 方式而不是 ChatGPT 账号登录检查 config.toml 里 model_providers 的base_url是否指向你要用的端点确认wire_api是responses还是chat第三方模型通常只支持chat用 curl 直接调一下端点确认模型名能被识别绕过 Codex 单独验证。很多时候卡住不是因为 Codex 有问题而是底层 API 端点本身就不接受那个模型名。6.2 本地网关类报错的排查思路用“模型切换工具”这类方式管理多个 API 端点时偶尔会碰到类似“local proxy failed”的错误后面往往还跟着handling codex endpoint /responses之类的信息。第一次看到会慌其实只是一个链路问题Codex 把请求发到本地转发服务本地转发服务再转给上游 API。任何一环出问题都会报这个错。我自己的排查套路是先确认本地转发进程是否还活着端口是否在监听用 curl 直接请求该端点的/responses路径看返回是什么。如果是连接失败说明转发服务没起来或地址写错如果返回鉴权错误说明上游 key 有问题检查 Codex 配置里的base_url是否和转发服务的端口对得上切回官方默认端点验证一次如果能通就说明问题出在本地转发服务的配置上而不是 Codex 本身。注意这类报错里的关键词按字面理解就行别急着怀疑网络环境。多数情况下就是端口、路径、模型名这三样东西对不上。6.3 向量检索召回不准问题大多出在分块和过滤我用 Astra 做检索的前两周最大的感受是“有时候搜出来的东西驴唇不对马嘴”。排查下来问题基本出在三点分块太大一块塞进了太多不同主题导致向量表达模糊没有做 metadata 过滤不同项目的笔记互相干扰插入和查询用了两个不同的 embedding 模型向量空间对不上。分块建议控制在 300~600 字左右用 Markdown 标题作为天然边界尽量保证一个块讲一个主题。metadata 过滤一定要加先按 project 过滤再搜索召回准确率会明显提升。embedding 模型一定要固定不要今天用text-embedding-3-small明天换text-embedding-3-large临时测试可以生产链路必须锁死。6.4 常见问题速查表问题现象常见原因解决思路上下文被塞满早期决策被遗忘没做预算控制精简 AGENTS.md、用笔记按需读取新会话丢失进度Codex 不记得上次改到哪没写运行笔记会话结束前强制写进度文件检索结果不相关搜出别的项目内容缺少 metadata 过滤按 project、time 过滤后再向量搜索模型不支持自定义模型名报错provider 配置不对检查 base_url、wire_api 和 API key网关请求失败请求无法通过本地转发本地服务未启动或端口错误curl 核对端点分层排查向量质量差同一主题找不到分块过大或 embedding 不一致减小分块固定 embedding 模型最后分享一点实际体会这套方案从最早“只靠 AGENTS.md 硬扛”到后来“手写笔记 FAISS”再到现在的“Astra 作为共享记忆层”一路踩了不少坑。我自己最大的体会是上下文窗口从来就不是用来装历史的它是用来装“当前判断”的。真正的高手不是让模型记得更多而是让模型在需要时能快速找到最相关的那一小块。如果你现在还在被 Codex 的“失忆”问题困扰我建议不要急着换更大的窗口模型先花一个下午把笔记体系搭起来再跑通最简单的向量检索。等这一步稳定了再考虑上 Astra 做团队级共享记忆。路要一步一步走记忆也是。

相关新闻

最新新闻

日新闻

周新闻

月新闻