YouMind
登录

如何构建成本降低 80% 的 Coding Agent:Jev + Opus 5.5(完整架构)

@cyrilXBT
英语2026年10月09日
241K
200
25
11
306

TL;DR

本指南详细介绍了一种用于 Coding Agent 的混合架构:利用 Opus 5.5 处理复杂推理,使用 Jev 执行上下文选择和错误恢复等常规决策任务,从而显著降低成本。

你的编程 Agent 正在用前沿模型的价格,去回答选择题。

该加载哪些笔记?这一步该交给大模型还是小模型?工具刚报错了,是重试、修复还是放弃?哪些测试应该先跑?

上面每一个问题,都只有四五个可能的答案。但在今天大多数 Agent 的配置里,每一个问题都会交给你最贵的模型来回答:开着 thinking,读完整个上下文,写上一段话才肯拍板。

这就是漏钱的地方。

这篇文章会告诉你怎么堵住它。Opus 5.5 负责硬核推理:规划、写代码、调试。Jev 负责几乎每一步都会遇到的那四个决策。而你的调度程序(harness)负责准备选项、核对答案,并做最终决定。

在下面的完整示例中,一个功能开发会话的成本从 $6.42 降到了 $2.00。便宜了 69%,而且每一笔账都写在文章里,你可以拿自己的数据来验算。

这篇文章写给所有在真实业务上跑编程 Agent 的人:无论你是用 Claude Code、自己基于 API 搭的调度程序,还是介于两者之间的任何东西。你不需要重写 Agent。本文提到的每一块内容,都可以作为一个函数调用塞进现有的循环里,并且带个开关随时能关掉。如果你读过我那篇 Jev + Claude Code 的文章,这篇就是升级版:思路一样,但用了更新的推理模型,并且把笼统的原则细化成了四个具体的决策点。

你需要一个 Jev API key、一个 Anthropic API key,以及大约一周的 Agent 日志用来做对比基准。

我们开始动手。

CyrilXBT - inline image

opus 负责推理,jev 负责决策。

问题所在:一个模型干两份活

盯着一个编程 Agent 工作十分钟,你会发现它在做两种截然不同的事。

第一份活是推理。 规划一个功能、写一个函数、读一段堆栈追踪,然后搞明白为什么日期解析器一到闰年就崩。这类工作是开放式的,答案不在清单上。这里你需要你能负担得起的最聪明的模型,而 Opus 5.5 非常擅长这个。

第二份活是决策。 从 40 条项目笔记里挑出相关的 3 条;判断一次重命名能不能交给便宜模型;决定 npm test 超时后该怎么办;在完整的 1,400 个测试文件里,挑出哪 12 个先跑。这些问题的答案都在清单上,清单很短,而且你的代码可以直接根据答案采取行动。

大多数 Agent 把这两份活都丢给同一个模型,因为这是默认做法。循环调用 Opus,Opus 思考,Opus 回答,循环继续。

这确实能跑通。但它又慢又贵,而且这种开销很容易被忽略,因为单次决策调用看起来并不贵。一次“我需要哪些笔记”的调用可能也就一美分。但每一步都要调一次,而且每次都带着完整的上下文。

还有第二个更容易被忽略的成本:当 Agent 不知道该加载什么时,它就会全部加载。每一条项目笔记、每一份规范文档、每一次历史决策,全被塞进每一次调用里。Opus 5.5 把缓存读取价格打到了每百万 token $0.20,但“便宜 × 六十步 × 你整个笔记文件夹”,加起来依然是一笔巨款。

解法:Opus 推理,Jev 决策,你的调度程序说了算

Jev 是 TypeSafe 专门为第二份活打造的模型。它不生成文本。你给它上下文和带有固定选项的类型化问题,它会返回每个选项的概率。它在毫秒级内给出答案,输入成本是每百万 token $0.042,输出免费。

这样一来,架构就很好描述了:

Opus 5.5 负责推理。 规划、写代码、调试,任何答案不在清单上的事。

Jev 负责小决策。 它从你的调度程序准备好的选项中做选择。

你的调度程序掌握控制权。 它构建选项列表、读取概率、应用阈值,并在 Jev 拿不准时回退到 Opus。

第三点是大家最容易跳过的,但也正是它让整个方案变得安全。Jev 永远不会凭空捏造一个选项。对于任何无法撤销的操作,Jev 永远没有最终决定权。你的代码来决定什么叫“足够确定”。

下面是本文其余所有内容所依赖的辅助代码:

from dataclasses import dataclass

@dataclass

class Decision:

answer: str

prob: float

probs: dict

confident: bool

def jev_choice(context: str, question: str, options: list[str]):

这里接入你的 Jev 客户端:TypeSafe SDK、Vercel AI Gateway,

或者 LangChain 包。它必须为你传入的每个选项

返回 {option: probability}。

raise NotImplementedError

def decide(context: str, question: str, options: list[str], threshold: float):

probs = jev_choice(context, question, options)

answer = max(probs, key=probs.get)

return Decision(answer, probs[answer], probs, probs[answer] >= threshold)

注意里面没有什么:没有去问 Jev “你有多大把握”的问题。你直接读它返回的概率就行。问任何模型“你确定吗?”,几乎每次都会得到“确定”。概率才是诚实的信号。

接下来看这四个决策。

CyrilXBT - inline image

Agent 步骤中的四个决策

决策 1:在加载上下文前,挑出相关的项目笔记

这是最大的单一杠杆,也是几乎没人实现过的一个。

你的 Agent 有一个笔记文件夹。里面有 AGENTS.md、架构文档、规范、历史决策,还有“没做迁移千万别碰计费模块”之类的警告。大概有 30,000 个 token。大多数 Agent 每次调用都会把它们全加载进去,因为谁也不想成为那个漏掉重要笔记的人。

所以,与其全部加载,不如针对每条笔记并行地问 Jev 一个简单问题:Agent 做这个任务需要这条笔记吗?

首先,按标题把你的笔记切分成块。AGENTS.md 的每一节变成独立的一块。每份架构文档变成一块或多块。

import re

def chunk_notes(markdown: str, source: str):

parts = re.split(r"
(?=## )", markdown)

return [{"source": source, "text": p.strip()} for p in parts if p.strip()]

然后针对当前任务给每一块打分:

from concurrent.futures import ThreadPoolExecutor

def pick_notes(task: str, notes: list[dict], keep: int = 6, floor: float = 0.55):

def score(note):

probs = jev_choice(

context=f"TASK:
{task}

NOTE ({note['source']}):
{note['text']}",

question="Would an engineer need this note to do the task correctly?",

options=["needed", "not_needed"],

)

return probs["needed"], note

with ThreadPoolExecutor(max_workers=16) as pool:

scored = sorted(pool.map(score, notes), key=lambda s: s[0], reverse=True)

picked = [n for p, n in scored[:keep] if p >= floor]

return picked or [n for _, n in scored[:2]]

这里有三个细节很关键。

永远加载不可妥协的内容。 有些笔记无论如何都应该出现在每次调用里:安全规则、“禁止强制推送”、标明哪个数据库是生产库的那行字。给它们打上 always 标签,跳过打分。

设一个底线和一个兜底方案。 如果没有任何笔记超过 0.55,你依然要加载排名前两位的。零上下文的 Agent 比上下文稍微多一点的 Agent 更糟糕。

记录被丢弃了什么。 当 Agent 犯错时,你要查的第一件事就是:那条本可以阻止错误的笔记是不是被打分刷掉了。这份日志就是你调整 keep 和 floor 的依据。

在完整示例中,仅这一项改动就把每次调用中的笔记部分从 30,000 个 token 降到了 6,000 个。单靠这一点,会话成本就降低了约 24%。

决策 2:把合适的任务路由给更快的执行者

不是每一步都需要 Opus。

跨八个文件重命名一个函数不需要。给一个已经有九个同类字段的表单再加一个字段不需要。更新一个导入路径也不需要。这些都是遵循代码库中已有模式的修改。

所以在每一步执行前,让 Jev 对步骤进行分类,你的调度程序再把类别映射到对应的执行者:

ROUTES = {

"rename_or_move": "fast",

"small_edit_following_existing_pattern": "fast",

"new_logic": "opus",

"debugging": "opus",

"design_decision": "opus",

}

def route(step: str, recent_diff: str):

d = decide(

context=f"STEP:
{step}

RECENT CHANGES:
{recent_diff}",

question="What kind of work is this step?",

options=list(ROUTES),

threshold=0.8,

)

if not d.confident:

return "opus"

return ROUTES[d.answer]

看看这些选项是怎么写的。它们不是“简单”和“困难”,更不是“小模型能搞定吗?”。它们是具体的工作类型,你的代码可以直接将其映射为动作。这是给 Jev 提问的铁律:每个选项都必须是你的调度程序知道该怎么处理的东西。

再注意一下默认行为。低于 0.8,步骤就交给 Opus。在这里,路由错误只会朝一个方向发生:把难的任务发给快速执行者,代价是一次失败和重试;而把简单的任务发给 Opus,只是多花一点钱。所以拿不准的时候,往上报。

快速执行者我用的是 Haiku 4.5,输入每百万 token $1,输出每百万 token $5。在完整示例中,36 个实际工作步骤里有 12 个交给了它。

决策 3:工具报错时,选择恢复路径

工具报错是家常便饭。测试超时、文件路径不对、包没装、shell 命令遇到权限错误、linter 抱怨一些无关紧要的事。

大多数 Agent 会用最贵的方式处理:把整个错误原封不动地丢回给大模型,让它去想怎么办。有时候这么做是对的。但很多时候答案是明摆着的,而 Opus 却要花 1,500 个 token 重新发现 ENOENT 的意思是“文件不存在”。

更糟的是,Agent 会陷入死循环。重试、失败、重试、失败,每次都为一个完整的推理步骤买单。

所以,把错误交给 Jev,并配上一组固定的恢复路径:

from collections import deque

def tail(text: str, n: int = 4000):

return "".join(deque(text, maxlen=n)) # 取报错信息的最后 n 个字符

def recover(tool: str, command: str, error: str, attempt: int):

if attempt >= 3:

return "stop_and_report"

d = decide(

context=f"TOOL: {tool}
COMMAND: {command}
ATTEMPT: {attempt}
ERROR:
{tail(error)}",

question="What is the best next move after this failure?",

options=RECOVERY,

threshold=0.8,

)

return d.answer if d.confident else "escalate_to_opus"

text
1RECOVERY = [
2 "retry_unchanged", # 网络抖动或超时
3 "retry_after_install", # 缺少包或工具
4 "fix_path_and_retry", # 文件路径或 cwd 错误
5 "switch_tool", # 这个工具不行,换一个
6 "escalate_to_opus", # 需要真正的调试
7 "stop_and_report", # 需要人工介入
8]

尝试次数的硬上限写在代码里,而不是模型里。无论模型多便宜,都不该允许它无限重试。

每条恢复路径都对应你调度程序里的一个小函数。retry_after_install 用正则从错误信息里提取缺失的包名并安装。fix_path_and_retry 检查存在的路径并推荐最接近的匹配。只有 escalate_to_opus 会真正花钱,而且只在错误确实需要思考时才触发。

在完整示例中,这从会话里砍掉了 6 步浪费的重试。

决策 4:在跑完整测试套件前,先跑针对性检查

这一步省时间多于省钱,而时间决定了 Agent 用起来快不快。

大多数 Agent 每次改动后都会跑一遍完整的测试套件。如果你的套件要跑四分钟,那 Agent 每次改完代码就要干等四分钟,然后再去几百行输出里找那个真正重要的失败。

解法和处理笔记的模式一样:针对 diff 给每个测试文件打分,先跑最相关的,只有针对性检查通过了才跑完整套件。

text
1def pick_tests(diff: str, test_files: list[str], keep: int = 12):
2 def score(path):
3 probs = jev_choice(
4 context=f"DIFF:\n{diff[:12000]}\n\nTEST FILE: {path}",
5 question="Could this change break tests in this file?",
6 options=["likely", "unlikely"],
7 )
8 return probs["likely"], path
9
10 with ThreadPoolExecutor(max_workers=16) as pool:
11 scored = sorted(pool.map(score, test_files), key=lambda s: s[0], reverse=True)
12 return [p for _, p in scored[:keep]]
13
14def check(diff: str, test_files: list[str]):
15 focused = pick_tests(diff, test_files)
16 if not run_tests(focused):
17 return False # 快速失败,先修好再说别的
18 return run_tests(test_files) # 完整套件依然是提交前的关卡

最后一行请读两遍。在任何东西被提交之前,完整套件依然会跑。针对性检查是预警,不是替代品。你能在几秒而不是几分钟内抓住大部分破坏,而且绝不会把完整套件本该拦下的东西发上线。

也要先把便宜的检查加上:只对改动的文件做类型检查和 lint。它们只要一秒钟,就能在任何测试运行前拦住相当大比例的错误。

整合起来:调度程序的循环

下面展示这四个决策是如何嵌入循环的单步之中的:

def run_step(task: str, step: str, state):

1. 上下文:只加载这一步需要的笔记

notes = state.always_notes + pick_notes(step, state.notes)

2. 路由:为这一步挑选执行者

worker = state.workers[route(step, state.recent_diff())]

result = worker.run(step, notes=notes, history=state.history)

3. 恢复:处理这一步中的任何工具报错

for failure in result.failures:

action = recover(failure.tool, failure.command, failure.error, failure.attempt)

state.apply_recovery(action, failure)

4. 检查:先跑针对性测试,提交前跑完整套件

if result.diff:

state.log_check(check(result.diff, state.test_files))

再看 Opus 这边:它就是普通的 Opus 5.5 调用。把稳定的上下文放在前面(系统提示词、工具定义、“always”笔记),这样它们能在各步骤间保持缓存;把与当前步骤相关的内容放在最后。

import os

import anthropic

client = anthropic.Anthropic()

OPUS = os.environ["OPUS_MODEL"] # 你的 Opus 5.5 模型 ID

def opus_step(system: str, notes: list[dict], history: list, step: str):

notes_text = "

".join(n["text"] for n in notes)

return client.messages.create(

model=OPUS,

max_tokens=8000,

system=[{"type": "text", "text": system, "cache_control": {"type": "ephemeral"}}],

messages=history + [{"role": "user", "content": f"NOTES:
{notes_text}

STEP:
{step}"}],

)

没什么花哨的。省下来的钱来自你不再发给它的东西,而不是什么巧妙的提示词。

拆解单步执行过程

当四个决策全部上线后,日志里的单步执行长这样。任务:给公开注册接口加限流。这个 trace 是示意性的,但你在自己的日志里看到的结构会和它一样。

step 14 "add a per IP rate limit to POST /signup"

[notes] 并行给 41 个块打分

保留:api/middleware.md (0.94)、conventions/errors.md (0.88)、

infra/redis.md (0.81)、decisions/2025_auth.md (0.62)

丢弃:37 个块,包括计费、邮件模板、CI

[route] new_logic (0.91) → opus

[opus] 规划改动,编写中间件 + 配置 + handler 修改

[tool] npm test FAILED "Cannot find module 'bottleneck'"

[recover] retry_after_install (0.97) → npm install,重跑

[checks] 对改动文件做类型检查:通过

针对性测试(1,388 个中的 12 个):通过

完整套件:通过

从上往下读,数数有多少行以前是一次完整的 Opus 调用。挑笔记:要么是一次大调用,要么干脆不挑,闭眼加载 30,000 个 token 的笔记。路由:没有,每一步都去了 Opus。缺包:用一个完整的推理步骤去弄明白“缺模块”意味着“装模块”。选测试:没有,每次改动都跑完整套件。

现在这个 trace 里只有一行是 Opus,而那正是真正需要前沿模型的一行:规划并编写限流器。

这就是一图胜千言。昂贵的模型把时间花在只有它能干的活上。

完整示例:69% 是怎么算出来的

这是一个功能开发会话的完整账单:60 个 Agent 步骤,加上典型 Agent 沿途攒下的 6 次浪费的重试。所有价格均为当前公开标价。

优化前:Opus 5.5 包揽一切

每一步加载 70,000 个 token 的上下文:15,000 用于系统提示词和工具,30,000 用于整个笔记文件夹,25,000 用于目前的对话。我假设其中 80% 命中缓存。每一步产生约 1,500 个输出 token,因为在 Opus 5.5 里 thinking 总是开着的。

cache reads: 56,000 × $0.20/M = $0.0112

fresh input: 14,000 × $4.00/M = $0.0560

output: 1,500 × $20.00/M = $0.0300

per step = $0.0972

66 steps × $0.0972 = $6.42

优化后:加入决策层

每步上下文降到 46,000 个 token,因为 Jev 挑出了 6,000 个 token 的笔记,而不是加载全部 30,000 个。24 个纯决策步骤转给了 Jev。6 个重试循环消失了。剩下 36 个工作步骤中有 12 个交给了 Haiku 4.5。

Opus 5.5: 24 steps × $0.0742 = $1.78

Haiku 4.5: 12 steps × $0.0169 = $0.20

Jev: 102 calls × 5,000 tokens

× $0.042/M, output free = $0.02

total = $2.00

从 $6.42 降到 $2.00。同样的功能,便宜了 68.8%。

CyrilXBT - inline image

根据答错的代价来设定阈值

下面是每个杠杆的贡献度,按顺序叠加:

仅做笔记筛选 $4.89 节省 23.7%

  • 决策转给 Jev $3.13 节省 51.2%
  • 恢复路径 $2.69 节省 58.1%
  • 路由给快速执行者 $2.00 节省 68.8%

关于这笔账,有两点实话要说。

第一,这些是基于明确假设计算出的模拟数字,不是对你技术栈的实际测量。你的上下文大小、缓存命中率和决策数量肯定不一样。把每一行都列出来,是为了让你能换成自己的数字。等你测出了真实的优化前后数据,用你自己的数字,别用我的。

第二,看看钱到底花在哪了。Jev 的 102 次调用总共才花了两美分。省下的钱不是因为 Jev 单次调用便宜,而是因为一旦 Jev 做了决策,很多事情就不再发生了:臃肿的上下文、为选择题付出的完整推理步骤、重试循环,以及在重命名任务上调用大模型。

为什么不直接用 Haiku 做决策?

问得好。Haiku 4.5 既便宜又快。为什么还要加一个新模型?

三个原因。

它依然会生成文本。 Haiku 用文本回答。你得提示它用你的某个选项作答,然后解析答案,接着还要处理它自作主张加解释、选了清单外的东西,或者把答案裹在一句话里的情况。Jev 会为你传入的每个选项返回一个概率。不用解析,也不会有清单外的东西。

你拿到的是完整分布,而不只是一个答案。 当 Jev 返回 needed: 0.52、not_needed: 0.48 时,你知道这跟抛硬币差不多,你的调度程序可以上报。而当文本模型说“needed”时,你根本不知道它有多大把握。只有能看到概率,阈值才有意义。

成本算法不同。 Jev 的输出免费,输入是每百万 token $0.042。每步给 41 个笔记块打分,意味着每步有 41 次小调用。用 Jev 这只是零点几美分。用任何文本模型,哪怕是便宜的,那也是 41 次生成,各自带着输出成本和延迟。

用小模型做那些真得写代码的小改动。用 Jev 从清单里挑东西。用 Opus 思考。让每个模型干它该干的活。

如何平滑上线而不搞砸任何东西

别一次性把四个决策全打开。我建议按这个顺序来。

第 1 周:影子模式。 保持你当前的 Agent 完全不变。在它做每个决策的同时,也拿同样的问题去问 Jev,并把两边的答案以及 Jev 的概率都记下来。完全不要按 Jev 的答案行动。

text
1def shadow(decision_name, context, question, options, current_answer):
2 probs = jev_choice(context, question, options)
3 jev_answer = max(probs, key=probs.get)
4 log_row(
5 decision=decision_name,
6 current=current_answer,
7 jev=jev_answer,
8 prob=probs[jev_answer],
9 agree=(jev_answer == current_answer),
10 )

第 2 周:分析分歧。 只看 Jev 和你当前 Agent 意见不一致的那些行。按 Jev 的概率排序。你通常会找到一条清晰的分界线:高于某个概率时,Jev 的正确率基本和 Opus 持平。那条线就是你的阈值。那些分歧行还会免费变成你的测试集,直接从真实流量里捞出来的。

第 3 周:开启笔记筛选。 这是最大的杠杆,也最容易回滚,因为漏掉笔记会很快体现在日志里。

第 4 周:开启恢复和测试。 两者在代码里都有硬上限和兜底,所以最坏的情况也就是退回你现在的做法。

最后:路由。 路由错误对用户最明显,因为把难的任务发给快速执行者会产出烂代码。把它留到最后开,用保守的阈值,并盯紧快速执行者步骤的失败率。

给每个决策都加个开关,这样一行代码就能回滚。

按风险设定阈值

所有事情共用一个阈值是个错误。要根据答错后的后果来设定。

CyrilXBT - inline image

一个功能开发会话,同样从 $6.42 降到 $2.00

低风险,易撤销:0.55 到 0.65。 挑笔记、排测试顺序。答错顶多浪费一点上下文或几秒钟。

中等风险:0.8。 路由和恢复。答错会导致一步失败,但你的调度程序能接住。

高风险,难撤销:0.9 或干脆别用。 删文件、强制推送、动生产配置、跑迁移。我的真心建议:别让任何模型单独做这些决定。交给人工,或者至少交给 Opus 并加一步确认。

任何低于阈值的请求都要往上报:需要推理的上报给 Opus,需要判断力的上报给人工。

这套方案帮不上忙的场景

这个架构并不是在所有负载上都能成倍起效的魔法。

纯生成类负载变化不大。 如果你的 Agent 大部分时间都在根据清晰的规格写大量新代码,那没多少决策可转移。你依然能省下笔记筛选的钱,但别的就不多了。

小项目用不上。 如果你整个笔记文件夹才 2,000 个 token,测试套件八秒就跑完,那搭这套东西的投入目前还不划算。

Jev 不太适合单独做最终拍板。 它非常擅长发现问题和从短清单里挑东西。但你不该用它以单个问题的形式去决定“这个 PR 能合并了吗?”。把大问题拆成小问题,逐个打分,然后在自己的代码里用自己的权重组合结果。

如果你已经为某个固定问题准备好了标注数据,你自己部署的小模型在价格上能打败 Jev。 Jev 的强项在于问题不断变化且你没有训练数据的场景——而这正是大多数编程 Agent 的现状。

我会避开的坑

让模型去写选项清单。 整个架构都依赖于你的代码能处理的选项。如果让模型生成选项,你就又回到了解析自由文本的老路。

问 Jev 有没有把握。 直接读概率。自我报告几乎永远是“有把握”。

没有兜底。 Jev 的每个决策都需要一条“拿不准”的路径,而那条路径只能是 Opus 或人工,绝不能是瞎猜。

把重试限制写在提示词里而不是代码里。 被告知“最多重试 3 次”的模型,偶尔还是会重试 30 次。代码不会。

丢掉完整测试套件。 针对性检查让你跑得更快,完整套件让你保持诚实。两个都要留。

什么都不衡量。 如果你在开始前没有记录每次会话的成本,就永远无法知道自己到底省下了 20% 还是 70%。先拉取一周正常工作状态下的用量数据。这只需要十分钟,但它是唯一真正重要的数字。

本周行动清单

以下是完整的待办事项:

  1. 记录一周的 Agent 会话数据:步骤、token、成本、失败情况。
  2. 找出循环中隐藏的决策点:笔记、路由、恢复、测试。
  3. 把每个决策点写成一个问题,并附上固定的选项列表。
  4. 让 Jev 以影子模式(shadow mode)与你当前的 Agent 并行运行一周。
  5. 根据分歧日志,按风险等级设定阈值。
  6. 先开启笔记选择功能,再开启恢复和测试,最后开启路由。
  7. 对比前后的单次会话成本。这就是你的核心指标。

Opus 5.5 是我用过的最擅长写代码的推理模型。但错误在于,让它把其他所有事情也一并包揽了。

让它去思考。让 Jev 去决策。让你的代码去执行。

如果这篇文章对你有帮助,欢迎关注 @cyrilXBT。我每周都会拆解如何用最新的 AI 模型做开发,并附上真实的数据。

收藏本文,等你准备动手开发时随时查阅。

一键保存

使用 YouMind AI 深度阅读爆款文章

保存原文、追问细节、总结观点,并在一个 AI 工作空间里把爆款文章沉淀成可复用笔记。

了解 YouMind
写给创作者

把你的 Markdown 变成干净的 𝕏 文章

图片上传、表格、代码块,往 𝕏 上手动重排太痛苦。YouMind 把整篇 Markdown 一键转成干净、可直接发布的 𝕏 文章草稿。

试试 Markdown 转 𝕏

更多可拆解样本

近期爆款文章

探索更多爆款文章