小岛AI
| ONLINE |

posts/anthropic-python-sdk-v1-observability.md

Anthropic SDK 1.0,最危险的改动不会报错

小岛AI 2026 / 08 / 23

版本号 1.0.0 通常像一块放心牌。

依赖从 0.x 升到 1.0,很多团队看到的第一反应大概是,终于稳定了,可以把版本钉死了。

Anthropic 这次有点会玩。v1.0.0 的 release 页只有很短几段,核心一句是把 HTTP 底层从 httpx 换成 httpx2,外加一些小型破坏性变化。点进迁移指南,好家伙,424 行。

稳定版门口,摆着一张 424 行的施工通知。

Anthropic Python SDK v1.0.0 官方发布卡片

更麻烦的是,这次升级最危险的地方,不是服务一启动就报错。真正容易漏掉的,是你的 Claude 请求仍然成功,用户也照样拿到答案,可 OpenTelemetry 没 span 了,Sentry 看不到 HTTP 请求了,respxpytest-httpx 也没拦住本来应该被 mock 的流量。

业务是绿的,观测链路瞎了。

这事儿比一个干脆利落的 TypeError 难排查得多。

先别被 1.0 三个字符哄住

PyPI 上的 anthropic 1.0.0在 8 月 20 日上传,最低 Python 版本从 3.9 抬到了 3.10。只看最普通的调用,升级确实不吓人。

from anthropic import Anthropic

client = Anthropic()
message = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "hello"}],
)

这段代码大概率不用改。官方迁移指南也说得很克制,如果你只传 timeout=30.0max_retries=3 这种普通值,可能什么都不用做。

问题出在 SDK 周围那圈工程胶水。

Anthropic 把 HTTP 层从 httpx 换成了httpx2。后者是 Pydantic 团队维护的兼容分支,类名和行为尽量保持一致,也继续接安全修复。对业务代码来说,这种替换看上去几乎透明。对依赖 Python 类型身份、模块 patch 和运行时 hook 的工具来说,它们不是同一个包。

如果你把旧的 httpx.ClientHTTPTransportTimeout 对象直接塞给新 SDK,构造 client 时会抛 TypeError。这种失败反而省心,日志一红,沿着栈就能找到。

真正阴的是另一类。

OpenTelemetry 的 HTTPXClientInstrumentor、Sentry 的 httpx 集成、respxpytest-httpxvcrpy,常见做法都是 patch httpx。新 SDK 已经不走那里了。它们可能继续正常初始化,测试也未必报错,只是再也看不见 Anthropic 发出去的请求。

你想想看,生产告警靠这些 span 算延迟,单测靠 mock 保证不碰真实 API。升级之后业务请求成功,仪表盘少一截数据,测试还可能悄悄烧真钱。厉害了,大版本最狠的改动没有异常栈,只有一块越来越安静的图表。

业务请求继续成功,观测分支却逐步失明

官方给的兼容办法是,在应用入口最前面调用 httpx2.alias_httpx(),让后续的 import httpximport httpcore 指向新实现。

import httpx2

httpx2.alias_httpx()

import httpx

这里有两个小字很要命。它必须发生在任何模块导入 httpx 之前,晚了会直接报错;而且这是应用级动作,公共库不该擅自替使用者改整个进程的模块解析。

所以别看到一行兼容代码就往共享包里塞。入口脚本、pytest 早期插件、worker 启动器,都可能要分别处理。

这也是为什么只在本地跑一遍 demo 很容易给出假安全感。一个 FastAPI 服务可能由 uvicorn 启动,后台任务却从 Celery worker 进入,测试又由 pytest 插件先加载。三条进程入口的 import 顺序不同,alias 在 Web 服务里生效,不代表 worker 和测试也生效。你在一个入口看见 span 回来了,另外两个入口可能还蒙着眼。

更稳的办法,是把「哪个模块最先接管 HTTP 栈」当成启动合同,明确写进每种进程的入口和测试。不要依赖某个业务模块碰巧先被导入,更别把 alias 藏在一个谁都不知道何时加载的工具包里。Python 的 import cache 很可靠,但它不会替团队解释启动顺序。

会直接炸的地方,反倒更好修

除了 HTTP 栈,这次还清理了不少旧接口。官方 v1 对比里移除了旧的 Text Completions 资源,client.completions.create()HUMAN_PROMPTAI_PROMPT 都不再保留。Messages API 从 2023 年就是推荐路径,仍在用旧入口的项目,这回没法继续拖了。

temperaturetop_ptop_k 也从当前 message 方法签名里拿掉。老模型若确实还需要采样参数,可以临时放进 extra_body,新模型则应该删掉。结构化输出那边,传原始 schema dict 的写法从 output_format 移到了 output_config。这些改动用 pyrightmypy 扫一遍,大多能在合并前抓出来。

raw response 也换了语义。异步 client 读取原始响应时,parse()text()json()read() 现在都要 await。同步代码里的 response.textresponse.content 也从属性变成了方法。

# 旧写法
response = await client.messages.with_raw_response.create(...)
message = response.parse()

# 新写法
response = await client.messages.with_raw_response.create(...)
message = await response.parse()

Bedrock 用户还有一个很实在的坑。以前找不到 region,SDK 会告警后默默落到 us-east-1。1.0 开始,没有 aws_regionAWS_REGIONAWS_DEFAULT_REGION 或 profile 里的 region,构造 client 就抛 ValueError

坦率讲,我喜欢这种改法。云区域关系到延迟、数据驻留和账单,默认猜一个值看似体贴,生产上经常是在帮人埋雷。让错误早点响,比让请求飞去另一个区域再调查半天靠谱。

但你会发现,上面这些都属于「会响的错误」。Python 版本不够,安装失败;类型不对,构造失败;旧参数还在,类型检查失败;region 缺失,启动失败。它们让人烦,却不太会装没事。

header 处理也值得扫一眼。1.0 按 HTTP 规范把 header 名称视为大小写不敏感,User-AgentUSER-AGENT 不会再被当成两条不同键。过去靠不同大小写偷偷塞两份值的代码会改变行为,bytes 类型的 header value 也必须先解码成字符串。这个改动方向没问题,但认证代理、签名中间件和自定义网关往往正爱在 header 上做文章,别漏。

Bedrock streaming 还有一处不太显眼的变化,未知事件现在会被跳过。官方目前点名的已知影响是 amazon-bedrock-invocationMetrics。如果你正好拿这类事件做自定义指标,主消息流可能完全正常,旁路统计却少了数据。你看,又绕回了同一个主题,功能成功不代表观测完整。

观测失明才是这篇真正想提醒的东西。

升级别只验答案,要验整条请求链

很多 SDK 升级方案只有两步,改版本,跑单测。对 anthropic 1.0.0,这套验收不够。

先把代码库里跟旧行为有关系的入口找全。下面这条 rg 不漂亮,但够实用。

rg -n 'import httpx|with_raw_response|client\.completions|HUMAN_PROMPT|AI_PROMPT|temperature=|top_p=|top_k=|output_format=|AnthropicBedrock\(' .

它不是自动迁移器,只是把可能需要人看一眼的地方摊到桌面上。尤其要顺着 import httpx 往外查,项目里有没有自定义 transport、代理、证书、event hook、HTTP mock 和 APM 初始化。普通业务调用没命中,不代表外围没依赖。

然后开一个明确的 v1 迁移分支,把范围钉在 anthropic>=1,<2,更新 lockfile,再跑类型检查和测试。

pip install --upgrade "anthropic>=1,<2"
pyright
pytest -q

如果项目用 uv、Poetry 或 PDM,就换成对应的锁版本命令。动作不重要,重要的是产物要能复现,不能让开发机装了 1.0,CI 还在 0.125。

这里还可以顺手做一次依赖账本。把直接依赖 anthropic 的服务、共享库、命令行脚本和 notebook 分开记,标出它们各自的 Python 版本、HTTP client、运行入口和观测方式。迁移指南不是让所有项目同时改一遍,而是先找出真正触碰变化边界的那部分。一个只用 Messages API 的离线脚本,跟一个挂着自定义 proxy、OpenTelemetry、异步 raw response 和 Bedrock fallback 的生产服务,风险根本不是一个量级。

小项目也别嫌这张表重。四列就够,运行入口、HTTP 定制、观测工具、回滚版本。十分钟写清楚,真出问题时能少翻一小时依赖树。

接下来补两类过去经常没人写的断言。

第一类断言观测真的发生。发一条最小测试请求后,不只检查返回内容,还要检查 trace exporter 收到了 Anthropic HTTP span,状态码、重试次数和 request id 能继续进入日志。若团队靠 Sentry 看外部请求,也要在测试环境确认那条 breadcrumb 或 span 还在。

第二类断言 mock 真的拦住。把 API 域名的外网访问临时封掉,再跑使用 respxpytest-httpxvcrpy 的用例。用例仍能过,才说明它走的是 mock;一断网就失败,之前的绿灯很可能只是在偷偷访问真实服务。

这个动作挺土,但有用。

到这一步才是小流量上线。别只盯成功率,还要看请求量、p95 延迟、连接错误、重试分布、超时、trace 覆盖率与 API 账单有没有一起对上。应用成功数是一条线,出口请求数是另一条线,两条对不上就先别扩大流量。

回滚也要提前写。不要等 canary 异常了才讨论是回应用版本、回 lockfile,还是只关 httpx2 alias。最省事的方案通常是让旧版依赖与旧启动入口一起可恢复,避免留下半套新 SDK、半套旧观测插件的混合状态。回滚成功的标准也不只是接口恢复,还要看 trace、mock 与账单重新对齐。

我跟你说,SDK 迁移最容易出现的幻觉就是「答案一样,所以系统一样」。答案只是最外面那层。代理有没有生效、证书有没有走对、重试有没有翻倍、span 有没有留下、mock 有没有拦住,这些才决定它能不能安心进生产。

1.0 是维护承诺,不是免检通行证

Anthropic 这次不是乱改。迁移指南写得很细,甚至直接提醒 APM 和 HTTP mock 可能静默失效,也给了 httpx2.alias_httpx()、pytest 早期插件和类型检查的处理路径。能把难听的小字放进官方文档,这点值得肯定。

而且从工程方向看,清掉 2023 年前的旧 Completions 接口,把 client-side compaction 换成服务端上下文管理,让 header 合并回到大小写不敏感的 HTTP 语义,再把 Bedrock region 变成必填,这些都在减少长期歧义。

只是 1.0 从来不等于「升级无感」。它更像维护者说,从这里开始,我愿意把接口边界讲清楚。对使用者来说,边界一旦讲清楚,也该把验收补到边界上。

所以这次别只跑一条 Claude 请求,看到输出正常就收工。

去看那块安静的仪表盘。

施工通知写了 424 行,真正该修的,可能正是那条没有报错的链路。

参考资料包括 Anthropic Python SDK v1.0.0 release官方迁移指南PyPI 包页面Claude Platform release noteshttpx2 官方仓库