2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LangChainをやめてAnthropic SDKの直接呼び出しに移行した理由を技術的根拠とともに記録する。デバッグ困難・バージョン破壊・トークン肥大という3つの地雷を踏んだ経験から、Raw API移行の判断基準を整理した。

LangChainを使い始めた理由、やめた理由

最初にLangChainを使い始めたのは、「とにかく早くRAGを動かしたい」という事情からだった。2023年末、複数のLLMを切り替えながら試せる抽象化層は魅力的に映った。ドキュメントも豊富で、Chain・Agentの概念は直感的だった。

ところが、本番環境に載せた瞬間から問題が連続した。デバッグできない、バージョンが壊れる、トークンが爆発する。この3つの地雷を半年かけて踏み抜いた結果、Anthropic SDKの直接呼び出しに全面移行した。その記録を残しておく。

理由1:デバッグがほぼ不可能だった

LangChainの問題として最も痛かったのが、ブラックボックス化によるデバッグの困難さだ。

あるエージェントの挙動がおかしいと気づいたのは、本番リリースから2週間後。APIコストが想定の10倍近くに達していた。原因を追うため、LangChainのAgentExecutorのソースを読み始めたが、Chain・Memory・Callbackが多重に絡み合い、「どこで何が起きているか」を掴むまでに丸2日を費やした。

結論は単純だった。LangChainのConversationBufferMemoryが全会話履歴をそのままシステムプロンプトに追記し続けていた。セッションが長くなるほどトークンが積み上がり、10ターンの会話で最初の10倍のコンテキストを送り続けていた。

Anthropic SDK直接呼び出しに書き直したのは1日かからなかった。毎回渡すメッセージ配列を自分で管理し、不要な履歴を捨てる設計にしたところ、トークン消費は約80%削減された。

LangChainの抽象化はプロトタイプには便利だが、本番チューニングの段階では障害になる。何が渡されているかを完全に制御できないフレームワークは、コスト最適化の場面で詰む。

理由2:バージョンアップのたびに本番が壊れた

LangChainのリリースサイクルは速い。そして、破壊的変更が黙って入る

2024年初頭、langchain 0.0.x系から0.1系へのアップグレード時に経験した障害が典型だ。ステージング環境では問題なく通ったのに、本番の並列処理環境でのみデッドロックが発生した。原因は内部の非同期処理クラスが静かに削除されていたことで、カスタムToolを継承していた部分が想定外の挙動をしていた。

切り戻しは即座にできたが、原因特定に3時間かかった。LangChainのCHANGELOGはあるが、カスタム継承箇所まで影響を追うのは現実的ではなかった。

Anthropic SDKのAnthropicクライアントは、APIの変更があれば明示的にバージョンを指定すれば済む。送るJSONの構造が変わるときはSDKのリリースノートに書いてある。何が変わったかが自分の目に見えるというのは、本番運用においては極めて重要な性質だ。

理由3:Claude固有の機能を活かせなかった

LangChainがOpenAIを主軸に設計されていることは、Claude APIを使う上での制約になった。

最も困ったのはプロンプトキャッシュへの非対応だ。AnthropicのClaude APIには、システムプロンプトや大量のコンテキストをキャッシュし、2回目以降のリクエストコストを最大90%削減できる機能がある。実際に使ったプロジェクトでは、月次のAPI費用が7割近く下がった。

しかし2024年時点でLangChainのChatAnthropicラッパーはこの機能に対応していなかった。extra_headersbetasパラメータを直接渡すには、ラッパーの内部実装をフォークするか、SDKを直接呼ぶかの二択になった。

マルチモーダル処理でも同様の問題があった。ツール呼び出し結果に画像(base64)を含めてLLMに返す処理が、LangChainの文字列前提の設計では素直に書けなかった。SDK直接呼び出しならtool_resultブロックに画像コンテンツをそのまま渡せる。

新機能が出るたびにLangChainの対応を待つより、SDKを直接使う方が常に最新の機能を試せる。

移行後のコード構成

移行後の基本構成はシンプルだ。anthropicパッケージを直接使い、メッセージ配列を自前で管理する。

import anthropic

client = anthropic.Anthropic()

def call_claude(
    messages: list[dict],
    system: str,
    model: str = "claude-opus-4-7",
    max_tokens: int = 4096,
    cache_system: bool = True,
) -> str:
    system_block = [{"type": "text", "text": system}]
    if cache_system:
        system_block[0]["cache_control"] = {"type": "ephemeral"}

    response = client.messages.create(
        model=model,
        max_tokens=max_tokens,
        system=system_block,
        messages=messages,
    )
    return response.content[0].text

これだけで、キャッシュ・ツール呼び出し・マルチモーダルすべてを制御できる。行数はLangChainのChain定義より少ない。

それでもLangChainを使う場面

LangChainを全否定するつもりはない。以下のケースでは今でも選択肢に入る。

  • 複数LLMプロバイダーを同一コードで切り替えたいプロトタイプ段階
  • LangGraphを使った複雑なステートマシンが必要なエージェント設計
  • チーム内にLLMアプリ未経験者が多いスタートアップ初期フェーズ

要するに「早く動かす」フェーズには向いている。「安く・安定して動かす」フェーズには向いていない。その切り替えのタイミングを見誤ると、本番で余計なコストを払うことになる。

判断の基準

移行タイミングの判断基準として、自分が使っているのは以下のチェックリストだ。

  • LLMへの入力をバイト単位で制御したいか
  • API新機能(キャッシュ・ツール仕様更新)を即日使いたい
  • 月のAPIコストが5万円を超えている
  • 本番障害の原因をLangChain側のコードまで追えているか

ひとつでも「はい」が出たら、Raw API移行を検討する価値がある。移行コスト自体は思ったより低い。LangChainのChainは結局「メッセージを組み立ててAPIを叩く」だけなので、その処理を自前に書き直すだけだ。

LangChainを使い続けることの問題は、「使えている」という感覚のまま、コストとデバッグの問題が静かに積み上がる点にある。気づいたときには本番が複雑に絡まっている。早めに気づいた方が移行は楽だ。


🌟 お知らせ

この記事が役に立ったら、ぜひフォローやいいねをお願いします!

🐦 X: @nabe_AI_dev
AI開発の最新情報や技術Tips、開発の進捗などを定期的にツイートしています。

📝 ブログ: AI Developer Blog
AIツール開発に関する詳細な記事や実装事例を公開中です。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?