
这周大部分时间都泡在 AI 工程的一线说实话周报都不想写流水账就想把几件真实有体感的事记下来。今年的重心和前年完全不一样了前年大家还在问“能不能跑通”现在问的全是“token 花得值不值”“模型换不换”“agent 能不能管住”。围绕 AI、token、模型、agent 这四个关键词我这一周几乎每天都有新的认知修正下面这几段算是比较核心的观感里面有不少是反复踩坑后得到的经验希望能给正在做同类项目的朋友省点时间。1. “更少 token”这周是真体会不是算法上的便宜话这一周最直观的感受就是 token 已经从“账单上的数字”变成了“架构上的约束”。我以前觉得 token 优化是个锦上添花的事情等用户量上来了再说但本周复盘了一次成本之后才发现自己的想法过时了。当业务的调用规模到一定量级之后真正吃掉预算的往往不是“新产生的文字”而是“被反复重发的旧文字”。1.1 从账单回看代码重复上下文的成本复盘我们在负责一个多轮对话类工具上周刚刚把底座从一个早期模型切到一个能力更强的模型。模型强了之后单轮回答质量确实明显提升但周一对账的时候我发现成本涨得有点不讲道理。第一反应是“新模型单价贵了”可仔细拉了调用日志之后发现根本问题不在单价而在上下文使用方式。当时业务场景大概是这样的每个任务平均会进行 12 到 15 轮工具调用每轮都要把前面所有历史记录、工具响应、系统指令重新发送一遍。我抽了一个典型任务算了笔账上下文的输入长度一度接近 26k token一轮任务下来模型实际读取的输入 token 超过 300k。而其中真正属于“新增信息”的可能只有最后两三轮的内容差不多只占两成。剩下的全部是历史复述。后来我做了个前后对比表可以从侧面看出问题有多严重分项优化前优化后单任务平均输入 token约 26k约 11k平均输入次数14 次16 次缓存命中率0%67%单任务输入成本约 0.19 美元约 0.08 美元任务成功率92.1%91.6%单靠缓存和压缩单任务的输入成本就能降一半还多而且对任务成功率几乎没有负面影响。这说明很多时候我们花出去的钱其实是被“低效的上下文管理”白白烧掉的。这也印证了一个观点token 优化不是要牺牲质量而是先解决浪费。1.2 三个立刻能上手的压 token 手段我在这次优化里没有引入特别复杂的方案只做了三件很基础的事但每一件都立竿见影。第一个是稳定前缀利用 prompt 缓存。现在不少模型服务都支持类似 HTTP 缓存的那套机制只要请求里的前缀足够稳定命中缓存后的输入价格可以降到正常价格的一半甚至更低。我们的做法是把系统提示词、工具函数定义、以及固定的输出格式说明拼成一个稳定前缀保证它在所有请求里一字不变然后把变化的部分放到消息列表的后半段。这个改造不需要改模型只需要在封装模型的客户端里调整一下拼装顺序即可。第二个是上下文摘要设置阈值再压缩。长对话场景里让模型把前面已经完成的工作浓缩成几百字的摘要替代原先那几十条完整历史。这个法子很有效但要注意的是压缩本身也是一次模型调用也有成本。如果对话才 5k token 就急着摘要反而会越压越贵。建议设一个阈值比如超过 15k token 才触发摘要并且只对“已经结束的工作步骤”做压缩当前步骤保持原样。第三个是输出形式的控制。在 agent 和工具调用场景里模型经常会输出一大段“解释性文本”来展示它的想法但这些内容对最终结果没有直接作用却按输出 token 计费。输出 token 往往比输入 token 贵不少所以让模型用结构化格式比如 JSON 字段里的“thought”字段来承载思考内容既保留可解释性又能把冗余说明压下去。实测下来这个手段能让单任务的总 token 消耗再下降 15% 左右。这几个手段加在一起我们 7 天内的平均单任务 token 消耗从 32k 降到了 17k接近一半的降幅。整个过程没有明显感觉到体验变差唯一要盯住的是摘要压缩的频次别让它变成新的成本黑洞。1.3 另一个“token”访问凭证失效给我提的醒这周还处理了一个和模型 token 完全无关、但同样叫 token 的问题差点把我绕晕。下游服务在调用我们接口时报了一串“token exchange failed: refresh token 无效”之类的错误。我们查了半天发现问题的核心不在代码逻辑而在缓存生命周期设计上刷新逻辑和业务进程的存活时间绑在同一个模块里服务运行一段时间后访问凭证过期了却没有触发独立的刷新机制。这类问题我猜很多做服务的都遇到过。最稳妥的做法是定义独立的凭证管理器并且把刷新动作和业务请求解耦每次请求前检查剩余有效期如果低于某个阈值就先刷新再请求。同时刷新失败要有重试和告警不能只靠日志里的一行错误信息慢慢排查。从这两类“token”问题联想到一个共同点无论是模型侧的 token 还是认证侧的 token都要当做一等公民来管理不能靠运气。模型侧的 token 要算成本、做缓存、设上限认证侧的 token 要管生命周期、做预热、加重试。只有把这两件事都制度化工程才算入门。2. 模型更强之后prompt 好写回归却难防“更强的模型”是本周第二个关键词但在实际工程里“强”带来的并不全是省事反而给测试和回归带来了新的麻烦。模型能力提升之后很多原先要精心设计的提示词可以直接简化这是好的一面但同时模型对措辞的敏感度、对格式的“自由发挥”空间也变大了回归问题因此变得更加隐蔽。2.1 简化 prompt 的收益与“过度迎合”的隐患我们在一个编码辅助 agent 里用了新的强模型之后直接把原来的 3500 token 长提示词砍到了 1200 token效果反而更好。原来需要写好几个 few-shot 示例才能教会模型“先读需求、再定位文件、然后修改代码”的执行顺序现在只要一句话模型就能自己规划出来甚至能处理一些我们没预料到的边界情况。这确实是模型能力提升带来的红利。但第三天后我开始发现另一面更强的模型在“迎合用户意图”这件事上做得太到位了有时候反而会绕过安全边界。举个例子我们原本在提示词里写了一句“不要执行任何破坏性命令”模型在大多数情况下都能遵守但如果我们把问题表述得稍微含糊一点它有时候会主动“脑补”出我们没有要求的操作甚至会自己补全明显应该被拦截的破坏性命令。这个东西在工程上非常危险。解决方式不是把提示词写得更强硬而是要专门设计“拒绝分支”——明确列出哪些动作是禁止的、哪些情况下需要停下来询问用户、哪些信息缺失时必须终止。同时在工具调用层做二次校验不能只靠提示词来兜底。2.2 切换模型版本工具调用格式偏移的回归问题这周另一个让我头疼的事情是把一个任务从模型 A 迁移到模型 B。模型 B 的推理质量更高但在工具调用环节返回的 JSON 结构里多了一个我们从未见过的顶层字段而且某些工具参数从数组形式变成了对象形式。严格来说模型 B 的格式更合理但我们的下游解析逻辑是按照模型 A 的格式写的结果产生了大量解析异常。这种问题在模型版本升级时特别容易出而且往往到线上才暴露因为开发环境用的是小样本测试回归不容易发现。我现在要求模型层必须加一个适配层统一把模型返回内容解析成内部标准格式无论底层模型怎么变调度器拿到的都是一致的结构。这个适配层有点像依赖接口的防腐层成本不高但非常值得。另外切换模型版本之前一定要跑一遍回归测试集。这个测试集不需要一开始就很大我的做法是选取过去一周线上真实用户请求的样本加上一些手工构造的边界场景每次切换模型之前都跑一次。别小看这几十条用例它能提前拦下很多“看起来质量更好、实际上行为变了”的问题。2.3 混合路由不是所有任务都值得上新模型模型越强单价通常也越贵但并不是所有任务都需要强模型。这周我做一个成本收益分析时发现在那些高重复、低复杂度的任务里旧模型在成本和响应速度上仍然有明显优势而且行为更稳定不容易“过于灵活”。我们的做法是做了简单的任务分层默认使用强模型处理规划和长链路任务对固定 pattern 的简单抽取、短文本分类、意图判断这类任务则路由到便宜快速的小模型上。这个路由规则一共不到 50 行代码却让整体模型调用成本又下降了 20% 到 30%而且业务方几乎感知不到差异。不要被“最强模型”的光环牵着走。在做 AI 工程时选模型的正确姿势不是“哪家强用哪家”而是“这个任务到底需要多强的能力”。把任务拆细、按需分配既省钱又能保持稳定性。3. agent 真正难管的地方并发、状态和沙箱边界这周有一半时间花在调教一个编码 agent 上可以说最大的体会就是“能写出 agent 的人很多能管住 agent 的人很少”。模型步进、工具调用、循环执行这些单点能力已经非常成熟了但把它们组合成一个可靠的生产级 agent工程复杂度是指数级增长的。3.1 并发与幂等提升并发不是加机器而是修逻辑我们的编码 agent 会按照模型生成的计划去读取代码、执行命令、修改文件最后提交改动。单线程跑的时候一切正常可一旦把并发提上来各种诡异问题就出现了两个任务读了同一个文件的旧版本各自改了不同行最后提交时出现冲突同一个工具被重复执行了两次产生了重复的数据。这类问题表面上是“资源竞争”本质上是工具调用缺乏幂等性。我后来给每个写操作增加了全局唯一的 request_id在执行前先尝试获取对应的锁如果锁已被持有就说明这是个重复请求直接返回上一次的结果而不是再次执行。def run_write_tool(tool_name, args): request_id args.get(request_id) or uuid.uuid4().hex lock acquire_tool_lock(tool_name, request_id) if not lock.acquired: return {status: duplicate, result: get_previous_result(request_id)} result execute_tool(tool_name, strip_internal_meta(args)) record_result(request_id, result) return {status: ok, result: result}这个改动听起来很简单但效果非常明显并发从 2 提到 8 之后任务冲突率从 30% 降到了 2% 以下。做 agent 并发不能只盯着资源扩容先把自己的操作设计成幂等的才能谈水平扩展。3.2 状态不可复现把每步过程事件化这周最让我崩溃的是一个 agent 在任务进行到第 17 步的时候崩掉了。日志里能看到它收到了哪些工具的响应却完全看不出来它下一步为什么做出了那样一个不合理的计划。后来才发现agent 内部维护了一个“私有记忆”缓冲区这些内容只存在于模型上下文中没有被持久化到任何日志里。崩溃之后我没法完整还原它的思维链路也就没法定位问题到底出在提示词、工具返回还是模型输出的哪一环。之后我吸取了教训把 agent 的每一步都改成事件化记录每次模型调用、每个工具执行、每个状态更新都作为一个事件写入持久化存储。复现时只需要把这些事件按顺序重放就能完整还原 agent 当时的行为。这个做法成本不高但对排查问题的帮助是决定性的。一份典型的事件日志长这样{ task_id: t-2025-05xx, step_id: 17, model_input_tokens: 12840, model_output_tokens: 320, tool: fetch_code_diff, tool_args: {file: src/worker/scheduler.py}, tool_result_status: ok, latency_ms: 1400, ts: 2025-05-xxT10:24:00Z }把这样的记录串起来看不仅能定位故障还能用来统计每个步骤的 token 消耗和工具调用频率直接给后续的优化提供数据依据。3.3 沙箱与权限边界agent 不能替人做越权决定随着 agent 的能力变强它的工具权限也在变大。一个会写代码、会执行命令的 agent如果还在普通进程里裸跑一旦模型产生了错误判断后果可能非常严重。这周我给编码类 agent 专门搭了一层沙箱环境原则很简单文件系统上看不见范围之外的文件命令执行只允许白名单内接口网络访问默认关闭只有明确授权过的域名才能连通。在这个基础上还有一个很容易被忽略的点agent 代表用户执行操作时它持有的访问凭证往往跟用户登录会话是同一个。但一个 agent 任务可能持续几个小时而会话令牌的有效期可能只有几十分钟于是就会出现“token exchange failed”“refresh token 无效”这类报错。这类问题的解法是给 agent 任务分配独立的短期任务凭证在执行器上层统一做凭证的刷新与预热而不是让 agent 自己在工具逻辑里处理登录。否则 agent 遇到登录失败后会陷入一种非常原始的重试循环白白浪费 token 和算力。4. 这周我在内部推的一套 agent 成本与失控治理如果说前三个部分更多是“发现问题”这一部分就是“解决问题”。面对 token 成本膨胀、agent 失控、行为不可预测这三个问题我在内部把治理动作拆成了三层预算控制、超时重试、过程可观测。这套东西不需要很高的技术门槛但能显著提升 agent 在生产环境里的可靠性。4.1 token 预算给每个 agent 任务一个“次数上限”agent 任务和普通对话请求有一个本质区别没有明确的结束点。普通对话一问一答就结束了agent 任务却可能无限循环地调用模型和工具。如果只靠提示词里写“别做太多步骤”那等于把安全交给概率我非常不推荐。我现在的做法非常简单粗暴给每个 agent 任务设置一个最大步骤数。每调用一次模型就计数一次超过上限就强制停止并且把当前状态整理成一份“任务未完成总结”返回给使用者。MAX_STEPS 25 step_counter 0 while step_counter MAX_STEPS and not finished: plan llm_call( contextcontext, token_budgetremaining_token_budget(), ) step_counter 1 if plan.is_finished: finish(plan.summary) break if step_counter MAX_STEPS: return emit_breakpoint({ reason: budget_exceeded, current_state: compress_state(context), })这个机制的好处是就算模型自己失去了方向系统也不会跟着它一起失控。真正的治理不是管住模型怎么想而是在工程层面设下它跨不过去的边界。4.2 超时、重试与退避别让失败变成死循环agent 执行工具调用时外部依赖随时可能出问题接口超时、数据库抖动、网络闪断。最初我们的 agent 会在工具失败后直接重试但遇到持续故障时就会陷入“失败-重试-再失败”的死循环。这一周我把重试策略改成了带指数退避的形式第一次失败后等 1 秒重试第二次等 2 秒第三次等 4 秒并且每个计划阶段的工具失败最多重试两次。超过重试次数后不再继续当前计划而是让模型重新规划替代方案。这套逻辑和普通后端服务的重试策略是一样的只是在 agent 场景里多了一层重试之后如果仍然失败需要把“错误信息”原封不动地交给模型让它判断是尝试别的路径还是干脆停下来向用户求助。不要低估这一层它决定了 agent 是“僵化地死磕”还是“灵活地处理问题”。4.3 过程可观测把每一步都变成数据管不住 agent 的另一个重要原因是它的行为链路太长缺乏可观测性。我们之前只在出问题时看日志而且日志是散落在各个服务里的很难拼出完整图景。这周我做了一个简单的改进所有 agent 执行步骤统一输出结构化日志字段包括 step_id、计划内容、模型输入输出 token、调用工具、工具参数、执行结果、耗时、时间戳全部一条不落。把这些数据收集起来之后我每天会看一个特别的统计单个任务中各工具被调用的次数和结果。结果发现很多工具调用其实是无效的——比如模型读了一个文件但后续计划里根本没用文件内容又比如同一个搜索动作被重复执行了三次只因为前两次结果没有写入上下文。这些数据反过来指导了提示词的优化和工具设计让 agent 的行为越来越“经济”。5. 说点带个人倾向的判断接下来几周我重点盯什么以上基本都是这周的实际经历。最后聊一点带个人倾向的看法不算严谨结论仅供同路人讨论。5.1 框架别迷信链路要自己看得见现在 agent 框架一个接一个抽象层级非常厚封装得很好。但在看团队新人写的代码时我发现一个共同的问题框架把调度逻辑包得严严实实想追踪“模型这一刻到底输出了什么、调用了哪个工具”非常困难。框架是用来自动化常规逻辑的不是用来遮蔽关键路径的。对于小团队和中期项目我更倾向于自己写一层很薄的调度层核心逻辑用一两百行代码表达出来每一次模型调用和工具执行都是透明的。这样出问题时能快速定位做优化时也知道该从哪里下手。5.2 评估要跟上 token 优化不能只省成本不看质量接下来我会重点盯一件事eval 和 token 优化的闭环。我们这周把 token 砍了不少但光看 token 数字是不够的还要验证“更少的 token 是否真的完成了同样的任务”。如果没有一套可靠的任务评估集token 优化很可能变成“省了成本、丢了准确率”。让模型在每个场景上的表现进入可量化的回归体系这才是 AI 工程长治久安的办法。5.3 本周实操清单最后把这周最值得记住的几条经验列一下方便复盘先做 prompt 缓存再谈压缩缓存是性价比最高的 token 优化手段。模型版本切换必跑回归评估集别轻信“新版效果更好”的传闻。并发 agent 的关键是幂等而不是无限扩容。agent 必须有“步数上限”和“token 预算”只靠提示词约束等于裸奔。工具调用全部做超时和退避失败后给模型一个重新规划的机会。所有执行步骤都要事件化记录这是排查和优化共同的基础。沙箱和独立凭证机制是 agent 走出演示阶段之前必须先补上的安全件。这一周的观感总结起来就是题目那九个字更少 token、更强模型、更难管的 agent。模型越来越聪明是大趋势但工程上的每一分进步最终都要靠“把细节抠到极致”来实现。希望这篇周观感能给你手头的项目带来一点参照物。