Claude Sonnet 4 工程实测——和上一代比,工程师最关心的那几点
Claude Sonnet 4 工程实测——和上一代比,工程师最关心的那几点
适读人群:Java/Python工程师、AI应用开发者 | 阅读时长:约12分钟 | 核心价值:真实项目切换Sonnet 4的对比数据,不是PR稿
上周四下午,我在一个内部项目里把模型从 Claude 3.5 Sonnet 切到了 Sonnet 4。
说实话,切之前我没抱太大期望。上一次大版本升级,官方说了很多,结果在我的实际场景里感知并不强烈。这次我打算认真测一把——用同样的任务、同样的 Prompt,对比出真实差距。
花了差不多一周时间,把几个我最关心的维度都跑了一遍。结果有一些超出预期,也有一些我觉得被高估了。写出来给大家参考。
我的测试环境
项目背景:一个内部的代码生成辅助工具,主要功能是接收自然语言需求,生成 Java 代码片段,同时支持解释旧代码逻辑。
日常调用量不算高,但对代码质量要求比较严:生成的代码要能直接进 Review,不能有明显的逻辑错误或过时的 API 用法。
测试方式:相同的 30 个任务(覆盖代码生成、代码解释、重构建议三类),分别用 Sonnet 3.5 和 Sonnet 4 跑,人工打分 + 统计可运行率。
代码生成质量:提升是真实的,但要说清楚在哪里
先说结论:Sonnet 4 在"复杂代码生成"上的提升比较明显,简单代码差别不大。
举一个具体例子。我给了这个任务:
"用 Java 17 写一个线程安全的本地缓存,支持 TTL 过期,要求不依赖第三方库,容量上限 1000 条,超出后按 LRU 淘汰。"
Sonnet 3.5 的输出(节选,精简了注释):
public class LocalCache<K, V> {
private final int maxCapacity;
private final long ttlMillis;
private final LinkedHashMap<K, CacheEntry<V>> cache;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public LocalCache(int maxCapacity, long ttlMillis) {
this.maxCapacity = maxCapacity;
this.ttlMillis = ttlMillis;
this.cache = new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<K, CacheEntry<V>> eldest) {
return size() > maxCapacity;
}
};
}
public void put(K key, V value) {
lock.writeLock().lock();
try {
cache.put(key, new CacheEntry<>(value, System.currentTimeMillis()));
} finally {
lock.writeLock().unlock();
}
}
public Optional<V> get(K key) {
lock.readLock().lock();
try {
CacheEntry<V> entry = cache.get(key);
if (entry == null) return Optional.empty();
if (System.currentTimeMillis() - entry.createTime > ttlMillis) {
// 这里有问题:读锁里不能升级写锁
cache.remove(key);
return Optional.empty();
}
return Optional.of(entry.value);
} finally {
lock.readLock().unlock();
}
}
}这个代码有个典型错误:在 get 方法里持有读锁的同时,试图调用 cache.remove(key)。LinkedHashMap 不是线程安全的,直接在读锁里写会出问题。3.5 版本没有处理这个升级问题。
Sonnet 4 的输出:
public class LocalCache<K, V> {
private final int maxCapacity;
private final long ttlMillis;
private final Map<K, CacheEntry<V>> cache;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public LocalCache(int maxCapacity, long ttlMillis) {
this.maxCapacity = maxCapacity;
this.ttlMillis = ttlMillis;
// 用写锁保护的 LinkedHashMap,access-order 模式实现 LRU
this.cache = new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<K, CacheEntry<V>> eldest) {
return size() > maxCapacity;
}
};
}
public void put(K key, V value) {
lock.writeLock().lock();
try {
cache.put(key, new CacheEntry<>(value, System.currentTimeMillis()));
} finally {
lock.writeLock().unlock();
}
}
public Optional<V> get(K key) {
// 先用读锁检查是否存在
lock.readLock().lock();
CacheEntry<V> entry;
try {
entry = cache.get(key);
if (entry == null) return Optional.empty();
} finally {
lock.readLock().unlock();
}
// TTL 过期:升级到写锁处理删除
if (System.currentTimeMillis() - entry.createTime > ttlMillis) {
lock.writeLock().lock();
try {
// double-check,防止并发删除
CacheEntry<V> recheck = cache.get(key);
if (recheck != null &&
System.currentTimeMillis() - recheck.createTime > ttlMillis) {
cache.remove(key);
}
} finally {
lock.writeLock().unlock();
}
return Optional.empty();
}
return Optional.of(entry.value);
}
private static class CacheEntry<V> {
final V value;
final long createTime;
CacheEntry(V value, long createTime) {
this.value = value;
this.createTime = createTime;
}
}
}Sonnet 4 正确处理了读写锁升级、double-check 的问题。这种并发场景下的细节处理,是我测试里感知最明显的提升点。
30 个任务的统计:
| 维度 | Sonnet 3.5 | Sonnet 4 |
|---|---|---|
| 代码可直接运行(无语法错误) | 26/30 | 29/30 |
| 逻辑正确(人工核查) | 21/30 | 26/30 |
| 复杂任务(并发/算法)逻辑正确 | 6/10 | 9/10 |
长上下文理解:这次真的不一样
我之前对"更长的上下文窗口"没什么感觉——窗口够用,但模型在上下文很长时会"丢失"关键信息,窗口大了也没用。
Sonnet 4 在这一点上让我改观了。
测试方式:给模型一段 8000 token 的 Java 代码(一个老项目的 Service 层),然后问它:"第 340 行的 calculateDiscount 方法里,有没有可能出现除零异常?如果有,在什么条件下触发?"
Sonnet 3.5 的回答:分析了方法签名和逻辑,但遗漏了一个关键上下文——调用这个方法的地方(在代码的第 120 行)会传入一个经过处理的值,处理逻辑导致在特定条件下值会变成 0。3.5 版本没有找到这个调用链。
Sonnet 4 找到了。它不仅指出了 calculateDiscount 方法内部的风险点,还往上追溯了调用链,明确告诉我:"在 OrderService.java:120 的调用处,当订单类型为 INTERNAL 且 discountRate 未初始化时,会传入 0,触发第 347 行的除零。"
这种跨上下文的推理能力,对做代码审查、漏洞分析的场景,价值很实在。
指令遵循稳定性:我测的最多的一个维度
我做 AI 应用最头疼的一件事,不是模型能不能做到,而是它会不会在某些情况下"突然不按规矩来"。
比如我的 Prompt 里写了:
输出必须是合法的 JSON,不要有任何额外文字,不要有 Markdown 代码块标记。Sonnet 3.5 在大多数情况下能遵守,但在输入比较复杂(比如需求描述超过 500 字)时,偶尔会在 JSON 外面加上 "Here is the JSON output:" 这种前缀。这种小偏差在程序解析时直接报错,很烦。
我用 20 个不同复杂度的输入测试了这个场景:
| 指令遵循测试 | Sonnet 3.5 | Sonnet 4 |
|---|---|---|
| 简单输入(<200字)严格输出 | 20/20 | 20/20 |
| 复杂输入(>500字)严格输出 | 16/20 | 19/20 |
| 多条约束同时遵守 | 14/20 | 18/20 |
Sonnet 4 的稳定性确实更高。特别是在多条约束叠加的场景(比如:只输出 JSON + 不超过 10 个字段 + 字段名用驼峰),4 版本的遵守率明显更好。
速度和成本:有变化,但不是你想的那样
很多人担心新版本会更慢更贵。我的实测数据:
延迟(首 token 时间,测了 50 次取均值):
- Sonnet 3.5:约 1.2s
- Sonnet 4:约 1.4s
轻微变慢,但在可接受范围内。如果你的场景对延迟极度敏感,这个差距值得注意。
成本:按 Anthropic 官方定价,Sonnet 4 的 input/output token 价格和 3.5 Sonnet 相比变化不大(具体以官方最新定价为准,我不在这里写死数字,避免过时)。
对我来说,整体 ROI 是合算的:多付了一点点钱,代码质量提升减少了人工 Review 时间,这个账不难算。
我不建议你切的场景
说了这么多好的,也说几个我觉得没必要切的场景:
简单的格式转换任务:比如把 JSON 转 XML,或者做简单的文本提取。这类任务 3.5 已经够用,为了这些切版本没意义。
成本极度敏感的高频调用:如果你每天调用几十万次,那个延迟差和可能的价格差要认真算清楚,不要盲目升级。
已经稳定运行的旧系统:已经上线、测试充分、运行稳定的系统,不要没事找事。新模型行为有细微差异,可能触发你没预料到的边界问题。
一个工程师视角的总结
我不喜欢泛泛说"哪个模型更好",因为这取决于你的具体场景。
从我的实测来看,Sonnet 4 在这三个方向上提升是真实的:
- 复杂代码的逻辑正确性(特别是并发、边界情况)
- 长上下文的跨段推理
- 多约束指令的遵守稳定性
如果你的应用场景落在这三个方向上,升级值得。如果你主要做简单任务,3.5 已经够用,不用急。
我的项目已经全量切到 Sonnet 4 了。跑了一周,没有发现回归问题,代码 Review 的驳回率下降了大概 15%,这个数字比我预期的要好。
