最近在调一个代码 Agent让它自己修仓库里的一个老 bug。调试的过程让我发现一件事很多时候我们不是在写代码而是在给大模型“送外卖”。每次把整份代码库切片、嵌入、塞进向量库再给它检索出一堆“相关片段”Token 用量就像水龙头坏了一路狂飙。后来我干脆把向量库扔掉换成一个精简的 CLI 工具链让 Agent 自己敲tree、rg、sed去翻代码实测下来 Token 直接省了四成左右。这个方案黑话叫“CLI-first Agent context”意思是把外部信息的获取方式从“批量灌入”改成“按需点单”。如果你也在用 Codex CLI、Claude Code 这类编码智能体或者自己搭 Agent 做代码辅助这篇文章会把省 Token 的原理、实操步骤和踩坑记录一次性讲透。1. 代码 Agent 的 Token 成本到底花在哪1.1 上下文窗口是“自助餐”不是“储物间”大模型一次能接收的 Token 数量是固定的。换句话说它就像一个自助餐厅你在门口端着盘子进去能拿多少菜取决于盘子大小而盘子大小由模型决定。窗口越大你能给 Agent 喂的信息越多但加工这些信息是要额外付费的而且信息摄入越多推理越慢、越容易“消化不良”。代码 Agent 天然需要“看代码”才能“改代码”。它没法隐形读取你的文件系统必须有人把代码内容放进对话里。这个“放”的动作就直接决定 Token 消费量。传统的做法是提前把所有代码块嵌入向量库等用户提问时做语义检索把“最相关的几个片段”取出来拼进 prompt。听起来很合理但这个链路从头到尾都在浪费 Token。首先嵌入过程本身会产生大量 API 调用每个文件切块后都要走一遍 embedding 模型Token 费用是一层其次向量检索出的 top-5 chunks 往往彼此重叠一个函数被切开后出现在多个 chunk 里拼进 prompt 的重复信息也要付费最后检索结果是否真的“相关”也要打问号语义相似的文本不一定能帮 Agent 写出正确修改。我在处理一个 Python 项目时亲眼见过仓库总共 3 万行代码嵌入向量库后大概占了 2 万 Token 的索引量而一次真实 bug 修复请求检索出的上下文大概 2800 Token其中真正派上用场的可能不到 900 Token。剩下的要么是重复的函数签名要么是“看起来相关但实际没帮助”的注释。你说这种折腾值不值1.2 向量库“笨重”的本质把运输成本藏了起来向量库本身不是不好它是一种非常通用的基础设施适合做知识库问答、语义搜索。但对代码 Agent 来说它有点“高射炮打蚊子”。代码结构和自然语言不同它有非常明确的符号系统文件名、函数名、类名、变量名、import 关系。要定位一段逻辑你往往只需要一句rg -n def handle_payment就能找到精确行号。可向量库做的是“模糊匹配”它把代码块变成浮点向量再用余弦相似度找相近内容。这个过程会出现两个问题一是索引需要实时同步代码一变、入库就要变否则检索到的是旧代码二是模糊匹配天然不适合精确引用Agent 拿到一个片段后如果片段里有残缺、缩进错误或遗漏 import它就要花更多推理 Token 去猜测上下文。还有一个被多数人忽略的隐性成本维护端到端向量检索链路需要额外的服务、API、存储和管道。如果为了一个单仓库项目专门起一个 FAISS 服务或 Pinecone 实例那运维成本会盖过 Agent 本身。CLI 方案则完全不同它直接复用你已经存在的文件系统控住输出要多少给多少不需要任何额外服务。我把两种方案放在一起做过对比对比维度RAG 向量库精简 CLI基础设施需要向量数据库、embedding 服务无需额外服务依赖文件系统信息精度语义近似可能有杂质精确到文件名、行号、代码块Token 消耗嵌入费 检索片段冗余只消费必要输出上下文更新需要重新 embedding实时读取当前文件失败模式检索不到、召回不准命令超时、输出太长适用场景巨型仓库、语义搜索日常编码、定向定位所以我的结论是不是所有代码 Agent 都要配一个向量库尤其是个人项目、中小型仓库、日常修复任务精简 CLI 是更强的“Token 利用率”武器。1.3 省 Token 的第一性原理减少无效上下文为什么标题敢说“省掉四成 Token”因为 Agent 每次调用外部工具都会把工具返回的全部文本拼进上下文。你把检索到的 5 个 chunk假设每个 chunk 300 Token放进去实际有用的只有 100 Token那 1400 Token 就是纯纯的浪费。而 CLI 通常只返回文件名、行号、一段代码最多 300 Token而且几乎每个 Token 都有用。更关键的是CLI 能“分步看”。Agent 先看目录结构再 grep 到具体函数再用 sed 读取指定行。每一步得到的信息都是下一轮决策的输入这种“渐进式探索”比一次性把相关代码块全塞进上下文要省得多。这也符合人写代码的习惯你先看项目结构再定位文件再读函数体而不是把整个项目砸在面前。2. 核心细节解析CLI 能为 Agent 提供哪些“关键情报”2.1 五条最基础但最值钱的命令我把给 Agent 用的 CLI 命令收敛到了一个最小集合全部是 Unix 工具链里现成的不需要额外安装。第一条是tree -L 2。它用来输出目录树限制层数很重要否则输出会把一堆 node_modules 或 venv 目录塞进上下文。tree -L 2 --dirsfirst -I node_modules|.git|__pycache__这样执行输出结构一目了然Token 消耗在 150 左右Agent 就能知道 repo 里有什么模块、核心入口在哪。第二条是rg -n pattern。rgripgrep是 grep 的现代替代品速度快默认递归搜索并忽略 .gitignore 里的文件。比如rg -n def process_order src返回所有包含该模式的匹配行一行就有文件名、行号和内容Agent 马上能定位到目标位置。第三条是sed -n 20,45p src/worker.py。当你已经知道某个函数在文件里的行号范围直接用它读取代码块。这个命令会输出指定行区间的内容精确控制不拖泥带水。也可以用awk NR20 NR45 src/worker.py效果一样取决于你的习惯。第四条是git diff --stat。Agent 如果要改动代码最好先看看当前工作区已经改了什么避免破坏已有修改。git diff --stat只显示变更的统计信息几十个 Token 就能让 Agent 了解全局。要具体看某个文件的变更用git diff path/to/file。第五条是git grep。如果项目是 Git 仓git grep token_expired可以只搜索被 Git 跟踪的文件自动跳过临时文件和未跟踪文件比裸搜更干净。注意git grep默认不搜索历史版本只搜当前工作区内容这对 Agent 来说完全够用。这些命令的共同点是输出短、信息密度高、能自由组合。Agent 像在用一张地图和放大镜而不是一次性把整座城市打印到屏幕上。2.2 让 CLI 输出“短到刚刚好”直接执行rg或sed示例代码没问题但真正到生产环境输出往往很容易超长。怎么限制最简单的方法是限制工具输出行数。rg -n class User src | head -50保证输出最多 50 行。sed -n 1,200p file只读取前 200 行。Agent 端我会设置max_output_tokens上限比如 800超出部分截断并在 prompt 里告诉它“如果输出被截断请用更精确的定位方式”。更“面向 Token”的做法是让命令输出结构化短文本。用--no-heading关闭rg的分组标题用-o只输出匹配的部分而不是整行。比如查一个函数名你只需要rg -o -N def [a-zA-Z_] src/order.py这个命令会输出所有函数签名不包含函数体信息量就像一个索引卡片。我还会给 Agent 配一个辅助脚本ccat作用是对文件按行采样。比如“显示第 10 到 20 行并且压缩空行”核心是避免 JSON 格式的代码块被转换工具吃掉太多 Token。方法是cat file | awk NF || NR % 5 0 {print NR: $0}这个命令每五行输出一次并且跳过空行适合快速浏览长文件。习惯之后真的很难再忍受向量库那种“一次性返回一坨”的做法。2.3 让 Agent 真正“会用”命令的三种接入方式光有命令没用Agent 需要能调用它们。实践下来有三条路难度从低到高排列。第一种把命令直接写进代码 Agent 的工具定义里。比如使用 OpenAI Function Calling 或 Claude Tool Use为 Agent 提供一个bash_executor工具参数是完整命令字符串。工具描述写“在沙箱中执行 shell 命令返回 stdout 和 stderr请保证命令只读、不修改系统文件”。这样 Agent 在执行主线程的时候会主动调用bash_executor获取代码信息。第二种用 MCPModel Context Protocol暴露一个文件系统工具。MCP 是让 Agent 和外部数据源交互的标准协议社区里已经有很多 filesystem server但大部分都给了“读文件、写文件”接口缺少“目录树、grep、按行读取”的组合接口。我直接封装了一套给 MCP server让 Agent 可以调用mcp_filesystem_tree、mcp_filesystem_rg等方法。好处是权限可以单独控制坏处是要多写一点代码。第三种如果用的是 Codex CLI 这类自带 shell 执行能力的工具那就直接用/shell或/tools让 Agent 跑命令。Codex CLI 本身有“执行代码块”的能力Agent 可以直接在交互会话里运行tree、sed然后把输出粘回上下文。这种方式最省事也是最接近“一个精简 CLI”的形态。我个人是把前两种混合用但对入门者强烈建议先试 Codex CLI 内置能力。2.4 安全边界不要让 Agent 变成“带权限的代码榴弹”CLI 方案最大的风险就是命令执行能力。给 Agent 一个 shell它就能执行任意命令你的一句“帮我清理临时文件”可能变成rm -rf /。所以安全设计是必须的。我采用的做法是给 Agent 一个专用的工作目录所有路径都以这个目录为根禁止cd ..和绝对路径访问。涉及修改类命令sed -i、rm、mv、git push默认禁用Agent 只能读修改代码时输出 diff 让用户确认。用timeout 10限制命令执行时间用find . -name *.py -newer这种只读命令作为替代。另外要注意输出长度控制。Agent 拿到很长的输出时会把上下文 Token 顶爆。所以第一个实践原则是“输出不超过 50 行”第二个是“禁止执行会无限输出的命令”比如没有head限制的cat整个大文件。我会在工具描述里加一条硬规则必须显式限制输出行数否则不许执行。3. 实操过程用精简 CLI 跑通一个 Bug 修复任务并核算 Token3.1 案例修复一个日志重复输出的 bug为了让你更直观地看到 Token 节省效果我拿一个真实小项目示范。仓库结构类似这样myapp/ ├── src/ │ ├── __init__.py │ ├── worker.py │ ├── models.py │ └── util.py ├── tests/ │ └── test_worker.py └── main.py任务是修复一个问题src/worker.py里的setup_logger每次调用时都会添加新的日志 Handler导致日志重复输出。我们需要 Agent 自己找到问题、生成修复代码。Agent 拿到这个任务后第一轮我直接让它执行tree -L 2 -I venv|__pycache__输出大概是这样的. ├── main.py ├── src │ ├── __init__.py │ ├── worker.py │ ├── models.py │ └── util.py └── tests └── test_worker.py这个输出 70 Token 左右。Agent 据此知道要看worker.py和test_worker.py。第二轮Agent 执行rg -n setup_logger|logger|Handler src/worker.py tests/test_worker.py输出src/worker.py:3: import logging src/worker.py:8: def setup_logger(): src/worker.py:9: logger logging.getLogger(demo) src/worker.py:12: if not logger.handlers: src/worker.py:15: logger.addHandler(handler) tests/test_worker.py:7: def test_logger_duplicates(): tests/test_worker.py:9: setup_logger() tests/test_worker.py:12: logger logging.getLogger(demo)这大约 110 Token。Agent 看到第 12 行有if not logger.handlers:可能已经猜到了问题方向但还需要看函数体。第三轮执行sed -n 8,22p src/worker.py输出函数完整体def setup_logger(): logger logging.getLogger(demo) handler logging.StreamHandler() fmt logging.Formatter([%(levelname)s] %(message)s) handler.setFormatter(fmt) if not logger.handlers: logger.addHandler(handler) return logger这大概 150 Token。到这里Agent 已经确切知道问题边界马上可以给出修复方案。整个探索过程产生的 Token 总计70 110 150 330 Token中间还夹带着 Agent 自己的推理 token总共不超过 900 Token。3.2 向量库路径下的 Token 花费还原同样一个任务如果用向量库方案会怎么走假设整个仓库 5 个文件共 2000 行要先把它们切块、嵌入向量库。切 100 个 chunk每个 chunk 平均 200 Token那么 embedding 费用对应的 Token 是 100 × 200 20000 Token且这些是一次性成本每次改动都要重新嵌入部分 chunk。查询时检索器返回 top-5 chunk每个平均 200 Token总共 1000 Token。此外为了检索准确你可能还会把用户问题再改写一次多花 50 Token。如果向量库命中不到准确上下文Agent 可能还要第二轮检索Token 又翻一倍。这只是单次修复的检索成本。实际使用中每次对话都要读旧 chunk、读排名、重试检索单次智能修复轻松破 2000 Token。相比之下CLI 探索的 900 Token 已经包含 Agent 的“思考证明”差距明显。当然嵌入费用是一次性的不能每次都算到单次任务里。但工程上向量库不是只有嵌入费它还需要持续同步、定期重建索引。把维护成本摊进去一个生态位在“个人项目日常开发”的 Agent用向量库确实比 CLI 贵得多。3.3 四成 Token 节省从哪来你需要理解“四成”不是指总 Token而是指Agent 在解决单任务时从外部获取并拼进上下文的 Token 数量。我实测在一次修复任务中CLI 方案外部上下文约 900 TokenRAG 方案约 1500 Token节约了 40% 以上。如果再算上每次检索时 prompt 里携带的检索结果、排序说明、重试信息实际节省可能接近五成。这里的理论支撑是信息熵。CLI 返回的高信息密度文本让模型单位推理成本降低而向量库返回的冗余文本则会让模型多花 Token 去筛选哪些是有效信号甚至可能产生幻觉。所以节省不止是输入费用的减少也是推理质量的提升。我用下表总结了单任务场景的 Token 消耗对比环节CLI 方案向量库方案初始探索直接执行命令观察需构建查询并获取 chunks单次上下文330结构化短文本1000top-5 chunks 拼接结果准确性调整少命令可精确调整多可能要换查询词重试总外部信息 Token约 900约 1500节省比例—粗略 40%这里的数字是经验值但趋势很稳定。只要任务不是“语义模糊搜索”CLI 方案在 Token 效率上几乎稳赢。3.4 具体代码实现我怎么把“分类导航”封装成一个 CLI 工具我习惯把一个最小的命令行导航工具写成一个脚本叫zcode。它不是通用软件只是把上面的命令组合成一个快速入口让 Agent 少记很多参数。#!/usr/bin/env bash # zcode: 为代码 Agent 提供精简代码导航信息 # 用法: # zcode root [dir] # 查看目录树默认当前目录 # zcode scan pattern # grep 搜索 # zcode read file start end # 按行读取 # zcode diff [file] # 查看差异 set -euo pipefail cmd${1:-root} shift || true case $cmd in root) dir${1:-.} tree -L 2 --dirsfirst -I node_modules|.git|__pycache__|venv|dist|build $dir ;; scan) pattern$1 shift || true rg -n --no-heading $pattern ${:-.} | head -80 ;; read) file$1; start$2; end${3:-$((start 30))} sed -n ${start},${end}p $file ;; diff) git diff --stat ${1:-} git diff ${1:-} | head -120 ;; *) echo unknown command: $cmd 2 exit 1 ;; esac这个脚本的每一个输出都被head限制了长度Agent 不能一次性读 1000 行。我把这个脚本丢到 Agent 的工作路径里工具描述里写“运行zcode root查看目录结构运行zcode scan pattern搜索符号运行zcode read file start end读取代码块”。实测发现Agent 会非常自然地组合这些命令效果好过给它裸的 shell 权限。4. 常见问题与排查技巧实录4.1 命令输出太长把上下文挤爆这个坑我一开始就踩了。有一次 Agent 执行rg -n def src/一下输出了 2000 多行直接把上下文窗口塞爆后续生成直接崩掉。后来我强制所有命令都要带长度限制并且在工具描述里注明“若输出超过 100 行请改用更精确的筛选条件”。你还可以在 Agent 的 prompt 里加一句“当命令输出被截断时优先缩小搜索范围而不是扩大搜索范围”。另外如果某个文件太长建议用sed分段读不要一次读完整文件。Codex CLI 也提供了/compact压缩上下文但我发现只要命令输出控制得短根本用不上它。4.2 路径和权限问题Agent 找不到文件Agent 执行命令时经常遇到“当前工作目录不对”的尴尬。比如从src/子目录进入后rg的搜索范围变成了子目录导致搜不到根目录下的文件。解决方案是让所有 CLI 命令都显式传入绝对路径或者在 Agent 工作开始时先执行一次cd /repo/root pwd确保后续命令都基于根目录。权限问题也很常见。如果 Agent 以低权限用户运行执行git diff可能会提示“detected dubious ownership”。解决方法是把仓库目录加入 Git 的 safe.directory 列表并把 Agent 运行用户改成与仓库 owner 一致。4.3 什么时候仍然需要向量库精简 CLI 不是银弹它有两个明显边界一是超大代码库。如果仓库有几万甚至几十万个文件rg搜索慢tree输出不是过大就是不完整此时向量索引能更快定位语义相关文件。二是语义模糊搜索。当你问“哪个模块处理用户权限”时单靠 grep 很难搜到“authorization”这种抽象描述。我保留向量库的唯一场景就是把大仓库的结构性检索能力作为“第二候选”但不会让它接管日常导航工作。如果你只有一个中小型仓库我强烈建议直接从 CLI 方案起步等确实遇到了语义搜索需求再引入向量库也不迟。你可以在 Agent 工具集里同时放上 CLI 和向量检索工具让 Agent 优先尝试 CLI失败时再调向量检索这种混合编排在 Token 效率和准确性上都能取得平衡。4.4 命令执行的安全事故现场最危险的一句话是“你可以运行任何命令”。Agent 有概率把用户意图理解偏执行rm -rf、git push --force等危险指令。我之前在测试中亲眼看到 Agent 执行python manage.py migrate因为我们只是让它改一个 Django 测试但它自作主张跑了数据库迁移。吓人吧。稳妥一点的做法是禁止一切写操作命令默认工具名是readonly_shell。执行修改命令前要求 Agent 输出完整命令并要求用户确认。使用 Docker 沙箱运行 Agent即使发生破坏也只会破坏容器。给 Agent 一个临时目录让它通过拷贝出的快照操作结束后把 diff 合并回来。虽然是“精简 CLI”但它仍然是“命令执行链”。宁可多写几行限制也别在事故现场追悔。5. 一些我的实操心得用精简 CLI 替代向量库之后最大的感受不是 Token 账单变小了而是 Agent 变得更“聪明”了。向量库方案经常让 Agent 拿着一堆“相关性”文本盲目发挥它没有主动探索的概念CLI 方案给了 Agent 一个明确的导航方式它就像是一个实习生在代码库里查资料每看一步都会思考我还需要什么信息如果你只是想试试水建议不用写复杂框架直接用 Codex CLI 试一下内置的命令执行能力把上面提到的zcode脚本放进去让它修一个你熟悉的 bug。观察一下它每一轮调用命令的 Token 消耗和之前用向量库 RAG 管线的时候做个对比我相信你很快就能理解“扔掉笨重向量库”并不是一句口号。另外我再分享一个小技巧如果命令输出里带有颜色 ANSI 转义序列记得用NO_COLOR1环境变量关掉颜色否则控制台字符会混进上下文白白浪费几十个 Token。这个坑几乎没人提过但实际使用中非常常见。CLI 方案的核心其实不是某个具体工具而是“让 Agent 用最小成本拿到精准信息”这个原则。只要这个原则在你的 Agent 就能在 Token 效率和修改准确性上拿到双赢结果。