Anthropicプロバイダの不要な「単一システムメッセージ」警告を削除

crmne/ruby_llm

Anthropicプロバイダが複数の :system ロールメッセージを受け取るたびに出力していた誤った警告ログを削除しました。Anthropicの system パラメータは既にテキストコンテンツブロックの配列を受け付けており、gemの実装も正しく動作していたため、警告だけが実態と乖離した状態になっていました。

背景

Anthropic::Chat#build_system_content には、複数の :system メッセージが渡されたときに警告を出力するコードが残存していました。警告の内容は「Anthropicは単一のシステムメッセージしかサポートしておらず、複数のメッセージは結合される」というものでしたが、これは現状の実装と一致しない記述でした。

実際には、メソッド内の flat_map による実装は各 :system メッセージを独立したテキストブロックとしてシリアライズしており、cache_control 付きのブロックも正しく保持されます。Anthropicの system パラメータはテキストコンテンツブロックの配列を受け付けるため、複数のシステムメッセージは「結合」ではなく「配列の各要素」として送信されていました。

Issue #767 で報告されたように、静的プロンプトとセッションごとのコンテキストを別々の :system メッセージとして分割するユースケース(プロンプトキャッシングのためのブロック分割など)では、リクエストのたびに誤った警告がログに出力されていました。高スループットなエージェント環境では、この誤警告がログを大量に埋め尽くす問題になっていました。また、Bedrockプロバイダ (lib/ruby_llm/providers/bedrock/chat.rb) では同じケースを警告なしで処理しており、プロバイダ間で挙動に一貫性がない状態でもありました。

技術的な変更

lib/ruby_llm/providers/anthropic/chat.rbbuild_system_content メソッドから、system_messages.length > 1 の条件分岐と RubyLLM.logger.warn の呼び出しを削除しました。

変更前:

def build_system_content(system_messages)
  return [] if system_messages.empty?

  if system_messages.length > 1
    RubyLLM.logger.warn(
      "Anthropic's Claude implementation only supports a single system message. " \
      'Multiple system messages will be combined into one.'
    )
  end

  system_messages.flat_map do |msg|
    content = msg.content
    # ...
  end
end

変更後:

def build_system_content(system_messages)
  return [] if system_messages.empty?

  # Anthropic's `system` parameter accepts an array of text content blocks
  # (each optionally with cache_control); each :system message becomes its
  # own block in the resulting array.
  system_messages.flat_map do |msg|
    content = msg.content
    # ...
  end
end

この変更はワイヤフォーマットに影響しません。APIリクエストのペイロード形式は変わらず、既存のVCRカセットも引き続き有効です。

テストカバレッジとして、spec/ruby_llm/providers/anthropic/chat_spec.rbbuild_system_content の直接ユニットテストが追加されました。追加された6つのテストケースは以下のシナリオをカバーします:

  • システムメッセージがゼロのとき [] を返す
  • 1つのメッセージから1つのブロックを返す
  • 2つのメッセージから2つのブロックを順序通りに返す
  • 複数の :system メッセージが渡されても warn が呼ばれない
  • Content::Raw ブロックの cache_control を保持する
  • Content::Raw と平文文字列が混在する場合を正しくフラット化する

設計判断

警告の削除のみを行い、flat_map によるシリアライズロジックには手を加えない最小限の変更が選ばれました。

コードの挙動自体は正しかったため、修正対象は誤った前提に基づくログ出力のみです。コメントで「system パラメータが cache_control 付きのテキストコンテンツブロックの配列を受け付ける」という正しい仕様を明示することで、将来の開発者が同じ誤解をしないよう配慮されています。

また、今回追加されたユニットテストは build_system_content をクラスメソッドとして直接呼び出す形式を採用しています。これにより、HTTPリクエストやVCRカセットを必要とせず、メソッドの契約を独立して検証できる構造になっています。

まとめ

実装は正しく動作していたにもかかわらず、ログに誤ったメッセージを出力し続けていた負債を解消した変更です。警告の削除という最小限の修正でプロバイダ間の挙動の一貫性も回復しており、正確なユニットテストの追加によってメソッドの正しい契約が明文化されました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
9da27f60

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

記事構成は「総論→各論→結論」の3部構成(リード文、背景、技術的な変更、設計判断、まとめ)を完璧に満たしており、非常に分かりやすいです。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト(```ruby:ファイルパス)およびGitHubのPR・Issueへのリンク記法は、ガイドラインに完全に準拠しています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

プロバイダ、シリアライズ、VCRカセットといった専門用語を適切に使用し、専門知識を持つエンジニアという対象読者に完全に適合した内容となっています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成になっており、各段落もトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が見事に適用されています。可読性が非常に高いです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

`lib/ruby_llm/providers/anthropic/chat.rb` のコード変更点を、Diff情報と一致する形で正確に引用しています。テストコードの変更点についても、内容を正確に要約して伝えています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`:system` ロール、`flat_map`、ワイヤフォーマットなど、PRで使われている技術用語を正確に選択し、文脈に沿って正しく使用しています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

「警告が実態と乖離していた理由」「変更がワイヤフォーマットに影響しないこと」「Bedrockプロバイダとの一貫性が回復すること」など、すべての技術的な説明がPR情報によって裏付けられており、正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(Issue #767への言及、ログが溢れる問題、Bedrockプロバイダとの非一貫性など)は、PR Descriptionの内容と完全に一致しており、ハルシネーションは一切見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#768)、Issue番号(#767)、追加されたテストケースの数(6つ)など、記事に含まれるすべての数値・固有名詞が正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトル「Anthropicプロバイダの不要な「単一システムメッセージ」警告を削除」は、PRのタイトルと内容を的確に要約しており、完全に一致しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PR Descriptionに記載のあったリリース日などの外部情報には触れず、提供された情報源の範囲内で記事が構成されており、捏造された外部知識はありません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「残存していた」「実態と乖離した状態になっていました」といった表現が、PRで述べられている「stale」な状況を正確に反映しており、時間表現は適切です。