PaPoo
cover
technews
Author
technews
世界の技術ニュースをリアルタイムでキャッチし、日本語でわかりやすく発信。AI・半導体・スタートアップから規制動向まで、グローバルテックシーンの「今」をお届けします。

OpenAI Python SDKがHTTPX2へ移ったことで、何が変わるのか

OpenAIのPython SDKが、内部のHTTPクライアントをHTTPX2へ切り替えた。ふだんはSDKの上だけを触っている人には地味に見えるが、TLS証明書、プロキシ、モック、型注釈のあたりで挙動が変わるので、実運用では無視できない。しかも今回は「そのまま動く部分」と「移行が必要な部分」がかなりはっきり分かれている。公式ドキュメントとしては珍しく、互換性を保ちながらも、古い前提を少しずつ切り替えていく姿がよく見える内容だ。

SDKの内側でHTTPX2に切り替わったとき、何がそのまま残るのか

元記事は、OpenAI Python SDKが同期・非同期のHTTPクライアントにHTTPX2を使うようになったと説明している。HTTPX2はopenaiと一緒に自動で入るが、以前のhttpxパッケージはもう同梱されない。つまり、SDKを普通に使うだけなら、特別な追加インストールは必要ない。OpenAIAsyncOpenAIhttp_clientなしで作る、いわゆるデフォルトの使い方では、既存のAPI呼び出し、レスポンスのパース、streaming、認証、リトライ、数値で指定するtimeoutはそのまま動く、と明記されている。

一方で、以前のSDKがたまたまhttpxを連れてきていたので、その副作用を前提にしていたアプリは注意が必要になる。SDKを入れたからhttpxも使える、という状況ではなくなったので、httpxを直接importしていたコードは、自分で依存関係を追加するか、httpx2へ移す必要がある。

大きく変わるのはTLS証明書の扱いだ。HTTPXでは証明書検証にcertifiのCA bundleを使っていたが、HTTPX2ではOSのtrust storeを使う。SDKもcertifiを入れなくなる。これにより、システムCA証明書を持たない最小構成のコンテナ、社内プロキシでTLSを中継している環境、certifiを改変して使っていた環境では証明書検証が壊れる可能性がある。対処としては、OS側にCA証明書を入れるか、SSL_CERT_FILESSL_CERT_DIRを設定する。自前のssl.SSLContextverifyに渡して、DefaultHttpx2ClientDefaultAsyncHttpx2Clientに食わせる方法も示されている。

カスタムHTTPクライアントを渡すケースでは、httpx2.Clienthttpx2.AsyncClienthttpx2.Timeouthttpx2.HTTPTransportのようなHTTPX2系の型と設定オブジェクトを使う。旧来のDefaultHttpxClientDefaultAsyncHttpxClientという名前はまだ動くが、内部ではHTTPX2クライアントを作る。だから新しく書くなら、DefaultHttpx2ClientDefaultAsyncHttpx2Clientを使って、どちらの世代のクライアントかを明示したほうがよい、という整理になっている。

さらに、request/responseのイベントフックや認証ハンドラもHTTPX2のRequestResponseを受け取る。with_raw_responseで生レスポンスを扱う場合や、streaming wrapperの中身もHTTPX2型になる。テストではhttpx2.MockTransportを使ってHTTPX2のrequestを受け、HTTPX2のresponseを返す必要がある。RESPXを使っているならHTTPX2対応版に更新するか、forkするか、どうしても間に合わなければ一時的にlegacy HTTPX clientを明示的に入れて使う逃げ道もある。ただしその場合、SDKの型注釈はHTTPX2前提なので、cast(Any, ...)やtype ignoreが必要になる。aiohttp向けのopenai[aiohttp]もHTTPX2ネイティブのtransportに乗り、旧来のhttpx-aiohttpは不要だとしている。

互換性を残しながら、旧HTTPXを少しずつ外しにいっている

この変更でいちばん印象に残るのは、破壊的変更を隠さずに、逃げ道もちゃんと残している点だと思う。デフォルト利用者はほぼ無傷で、明確に壊れるのは「SDKがたまたま入れていたhttpxに依存していた人」と「TLSやモックを深く触っていた人」だ。つまり、一般ユーザーよりも、周辺の道具立てを作っている人ほど影響が大きい。これはSDKの外側にいるライブラリ作者や運用担当にとって、かなり現実的な切り分けだろう。

ただ、ここで気になるのは、httpxhttpx2の二重世界がしばらく続くことだ。公式は旧名のヘルパーも残しつつ、新しい名前を推奨しているが、実際の現場では「とりあえず動くから旧名のまま」に流れやすい。そうなると、見た目は互換でも、内部では新旧が混在する。型注釈は新しいのに、実体は古いクライアント、という組み合わせが出てくると、静的解析で引っかかったり、テストだけ通って本番で証明書エラーになったりしやすい。私は、今回のドキュメントがこの混在を早めに言語化しているのはかなり誠実だと思う。

TLS証明書の変更は、地味だが一番事故りやすい

今回の変更で実害が出やすいのは、APIの使い方そのものよりTLSだろう。certifiからOS trust storeへ寄るのは、標準的な環境では自然な流れに見えるが、コンテナや企業ネットワークでは話が別だ。特に、最小イメージのDocker、社内CAを使う閉域網、プロキシが証明書を差し替える環境は、証明書検証まわりの「普通」が通用しない。ここで壊れると、エラーはただの接続失敗に見えるので、原因特定に時間がかかる。

私はこの変更を、ライブラリが「ネットワーク越しに動けばよい」段階から、「運用環境の証明書事情まで含めて面倒を見る」段階へ寄っている動きだと見ている。OS trust storeを使うほうが管理上は筋がよい場面も多いが、逆に言えば、アプリ単体で完結していた安全の前提が崩れる。公式がSSL_CERT_FILESSL_CERT_DIRssl.SSLContextまで例示しているのは、その崩れ方をかなり現実的に見積もっている証拠だ。ここを軽く流して導入すると、CIでは動くのに本番だけ落ちる、という一番嫌なパターンになるはずだ。

テストやモックの作者にとっては、かなり大きい切り替え

もう一つ重いのは、モックと計測系だ。MockTransportRESPXの話は、一般利用者には脇役に見えるが、実際には社内SDKやテスト基盤を支えている。ここがHTTPX2対応しないと、SDKのデフォルトクライアントを横取りできなくなる。つまり、単なる依存更新では済まず、request/response型、transport interface、アサーションの書き方まで見直しになる。

私はここに、この変更の本質があると思う。SDK本体の機能追加ではなく、周辺エコシステムの接続点を整理し直しているのだ。cast_to=httpx2.Responseのような細かい指定まで書かれているのは、SDKが返す「解析済みモデル」と「生のHTTPオブジェクト」を分けて扱う思想を明確にしたいからだろう。そうするとテストでは、SDKの高レベルAPIだけでなく、HTTP層の境界が本当にHTTPX2に乗っているかまで検証できる。面倒ではあるが、ここを曖昧にすると後で壊れ方が読めなくなる。

しばらくは「新しい標準」と「古い逃げ道」が同居する

最後に、このドキュメントは移行方針としてかなり珍しいバランスを取っている。新規コードにはHTTPX2を使ってほしい。でも、どうしても外部ライブラリがHTTPXしか知らないなら、legacy clientを明示的に差し込んでしのげる。その代わり、型チェックは通らないし、型無視も必要になる。つまり、互換性をゼロにはしないが、借金であることは隠さない。

私はこのやり方は正しいと思う。実務では「今日すぐ直せない」ことのほうが多いからだ。ただし、逃げ道があることと、逃げ道に居座ってよいことは別だ。今回の文書は、そこをかなりはっきり線引きしている。SDK利用者はほぼ何もしなくていい一方で、HTTPを深く触る層には、早めにHTTPX2へ寄せるか、少なくとも自分の依存関係を見直す覚悟がいる。地味だが、こういう移行は後回しにすると必ず高くつく。


参考: openai-python/httpx2.md at main · openai/openai-python

同じ著者の記事