跳到主内容
快讯直播
AI智模界
教程

上手 Sonnet 5.5:编码任务按成本分层迁移

这篇能做出什么

先看结果。跟着做完,你会得到一个能跑起来的小工具,它做三件事:

1. 分档:把每天遇到的编码任务分成「琐碎」「常规」「硬骨头」三类,前两类默认交给 Sonnet 档位的模型,第三类才动用 Opus 档位。

2. 省重复:把项目规范、代码风格、常用约定这些几乎不变的文字固定成提示前缀,开启提示缓存,同一会话里反复调用时不用重复付全价。

3. 能回退:Sonnet 试了一两轮不收敛,自动整理上下文、升级到 Opus,而不是让人手动重写一遍需求。

最后再加一张日志表,能看出这一天里钱花在哪个档、缓存命中了几次。数据不用猜。

前置条件清单

  • 一个可用的 Anthropic API Key,放进环境变量 ANTHROPIC_API_KEY,不要写进代码。
  • Python 环境,具体最低版本以官方文档当前说明为准。
  • 安装官方 SDK:

```bash

pip install anthropic

```

版本以官方文档当前版本为准,不建议在依赖里锁死某个具体小版本。

  • 一个你熟悉的小仓库,最好自带测试命令(pytest、npm test 之类都行)。有验收标准,分档才有意义。
  • 心里清楚三个概念:token、system 提示、流式输出。

先明确 Sonnet 和 Opus 的定位差

Anthropic 的模型命名习惯里,Haiku 偏轻快,Sonnet 是均衡主力,Opus 是能力档位更高的一档。落到实际体验上,两者通常表现为:

维度Sonnet 档Opus 档
单位 token 成本更低更高
首字延迟、输出速度通常更快通常更慢
适合的任务需求清晰、验收标准明确需求模糊、需要拆解和判断
典型失败模式硬扛难题,来回改小任务上也慢、也贵

具体价格、上下文长度、缓存读写倍率会随版本调整,以官方页面为准。这篇教程的做法是:不背数字,把模型名和分档规则做成配置,价格变了只改配置不动逻辑。

一句话判断口诀:能不能一句话说清「输入什么、输出什么、怎么算做完」?能,走 Sonnet;不能,走 Opus。

第一步:跑通最小调用

先确认钥匙能用,再谈分层。

```python

hello.py

import anthropic

client = anthropic.Anthropic() # 自动读取 ANTHROPIC_API_KEY

resp = client.messages.create(

model="把这里换成官方文档里的 Sonnet 模型名",

max_tokens=512,

messages=[{"role": "user", "content": "用一句话解释幂等"}],

)

print(resp.content[0].text)

print(resp.usage)

```

把模型名字符串集中管理,别散落在十几个文件里:

```python

config.py

import os

MODELS = {

"fast": os.environ.get("MODEL_FAST", "替换为官方文档中的 Sonnet 模型名"),

"deep": os.environ.get("MODEL_DEEP", "替换为官方文档中的 Opus 模型名"),

}

```

以后官方换名字,改环境变量就行。

第二步:给任务分档

不要凭感觉,先列一张自己的表。可以参考这个模板,按你的实际工作改:

档位典型任务默认模型
琐碎变量改名、补注释、写正则、格式化报错信息、加一行日志fast
常规按接口写实现、补单元测试、小 bug 修复、写 SQL、脚本加参数fast
硬骨头跨模块重构、性能问题定位、并发与竞态、模糊需求拆解、线上故障根因先用 fast 探路,不收敛转 deep

判断时看两个信号:

  • 涉及文件数:改动落在 1~2 个文件、接口不变,基本是常规档;要动公共接口或跨越 3 个以上文件,先按硬骨头对待。
  • 验收标准:能写出一条断言或一个测试命令的,走 fast;需要人来判断「这样设计合不合理」的,走 deep。

第三步:写出路由函数

```python

router.py

from config import MODELS

HARD_KEYWORDS = ("重构", "竞态", "死锁", "性能", "架构", "定位", "设计", "为什么")

def pick_tier(task: str, files_touched: int = 1, force_deep: bool = False) -> str:

if force_deep:

return "deep"

if files_touched >= 3:

return "deep"

if any(w in task for w in HARD_KEYWORDS):

return "deep"

return "fast"

```

关键词判断很粗糙,但足够撑起第一版。用一段时间后,把判断错的例子记下来,再考虑换成一个轻量分类调用。

第四步:把提示缓存用起来

缓存的原理不复杂:前缀逐字符一致,才可能命中。所以消息结构要按「稳定 → 易变」排序:

```

[ 固定角色设定 ] → [ 项目规范、代码风格、常用约定 ] → [ 本次任务与代码片段 ]

```

在稳定部分的末尾打缓存断点:

```python

ask.py

import anthropic

from config import MODELS

client = anthropic.Anthropic()

STABLE_RULES = """你是一名资深工程师。回答遵守以下要求:

1. 只输出可运行的代码或必要的说明,不要寒暄。

2. 改动尽量小,不顺手重构无关代码。

3. 涉及删改已有行为时,明确指出影响范围。"""

REPO_CONVENTIONS = """本仓库约定:

  • 缩进 4 空格,字符串用双引号
  • 新增函数必须配一个最小单元测试
  • 数据库访问统一走 repository 层,不在业务函数里写 SQL
  • 错误日志必须带 trace_id

(这里是示例,替换成你项目的真实约定)"""

def ask(task: str, context: str = "", tier: str = "fast", max_tokens: int = 4096):

resp = client.messages.create(

model=MODELS[tier],

max_tokens=max_tokens,

system=[

{"type": "text", "text": STABLE_RULES},

{

"type": "text",

"text": REPO_CONVENTIONS,

"cache_control": {"type": "ephemeral"}, # 字段用法以官方文档为准

},

],

messages=[{"role": "user", "content": f"{context}\n\n任务:{task}"}],

)

return resp

```

缓存到底有没有生效,看返回的用量字段:

```python

u = resp.usage

print("输入:", u.input_tokens, "输出:", u.output_tokens)

print("缓存写入:", getattr(u, "cache_creation_input_tokens", 0))

print("缓存读取:", getattr(u, "cache_read_input_tokens", 0))

```

cache_read_input_tokens 一直是 0,就说明前缀不稳定,回去查第四步开头的规则。注意缓存通常对提示长度有最低门槛,太短的内容不会缓存,具体门槛以官方文档为准。

第五步:难题回退到 Opus

回退要有触发条件,不能靠心情。可以这样定:

```python

def looks_done(text: str) -> bool:

if not text or len(text) < 40:

return False

unsure = ("需要更多信息", "无法确定", "可能有多种", "取决于")

return not any(s in text for s in unsure)

def summarize(text: str, limit: int = 400) -> str:

return text[:limit].replace("\n", " ")

def solve(task: str, context: str = "", max_rounds: int = 2, escalate: bool = True):

attempts = []

for _ in range(max_rounds):

resp = ask(task, context, tier="fast")

out = resp.content[0].text

if looks_done(out):

return out

attempts.append(summarize(out))

if not escalate:

return out

升级时不要丢掉全部历史,也不要原样丢过去

brief = "\n".join(f"- 第{i+1}轮尝试摘要:{a}" for i, a in enumerate(attempts))

escalated = (

f"{context}\n\n原始任务:{task}\n\n"

f"已尝试但未通过的思路:\n{brief}\n\n"

"请直接给出可执行的方案,并说明为什么前面的思路不成立。"

)

return ask(escalated, context="", tier="deep").content[0].text

```

关键点:升级时传的是摘要加失败现象,不是把两轮完整对话原样塞回去。完整历史又贵又容易让新模型被前面的错误思路带偏。

第六步:记账,别凭感觉

把每次调用写进 JSONL,一周后就能看出分档是否合理。

```python

log.py

import json, time

def log_call(tier, model, usage, task):

row = {

"ts": time.time(),

"tier": tier,

"model": model,

"task": task[:80],

"input": usage.input_tokens,

"output": usage.output_tokens,

"cache_write": getattr(usage, "cache_creation_input_tokens", 0),

"cache_read": getattr(usage, "cache_read_input_tokens", 0),

}

with open("calls.jsonl", "a", encoding="utf-8") as f:

f.write(json.dumps(row, ensure_ascii=False) + "\n")

```

看两个比例就够:fast 档占比是不是在七成以上;缓存读取占输入 token 的比例是不是在稳步上升。第一个低了说明分档太保守,第二个低了说明前缀没稳定。

第七步:接进日常工作流

包一个小命令行,日常就不用写代码了:

```bash

用法示意

ask "把 foo.py 里的 print 换成 logging,保持行为不变"

ask --deep "订单超时重复扣款,帮我定位可能的原因"

ask --files 4 "把 config 模块从 dict 改成 dataclass"

```

把 REPO_CONVENTIONS 那份文本放进仓库,纳入版本管理。团队里所有人跑同一份前缀,缓存才容易在共享场景下复用。

常见坑与排错

缓存命中率一直是 0。 九成是前缀里混了易变内容:时间戳、git status 输出、随机排序的文件列表、每次不同的模型名。把这些挪到用户消息里,别放 system。

以为开了缓存就一定省钱。 缓存写入本身是有成本的。同一个前缀只用一次,反而更贵。适合的场景是「一个会话里反复用」或「团队一天内多次用」。用一两次的一次性任务,直接关掉更划算。

把所有任务都丢给 Opus。 琐碎任务上它慢、贵,收益有限。改个变量名不值得动用最高档。

难题硬扛 Sonnet。 来回改五轮,累计 token 可能超过直接上一次 Opus。回退轮数设成 2 就是防这个。

模型名写死十几处。 集中到一个配置或环境变量里,官方调价换名时只改一处。

上下文无限增长。 每轮把已完成部分压成两行摘要,旧原文丢掉。上下文越长,每一轮都越贵。

重试没上限。 遇到限流或服务端错误用指数退避,并设最大次数,避免失败时疯狂重试。

排错按这个顺序走:模型名对不对 → system 前缀是不是逐字符稳定 → 用量里有没有 cache_read → 分档规则合不合理。四步走完,绝大多数问题都能定位。

下一步建议

1. 翻出过去两周的 20 个真实任务,逐个标一列「Sonnet 能否一轮过」。这张表就是你的分档规则的真实依据。

2. 给回退加个复盘记录:哪些任务升级到了 Opus,下次直接把这类任务默认归到 deep 档。

3. 试试把缓存断点前挪或后挪一格,看 cache_read 的变化。

4. 等分档规则稳定了,再考虑用一个轻量模型自动判档,人工只做兜底。

5. 官方文档里的模型名和定价页收进收藏夹,价格与模型名以官方页面当前内容为准,配置随时可改。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。