Claude Codeが生成するストリーミング実装の罠:AIの「思考」がJSONパースを破壊する現象の回避策

PythonとReact/Vueのフルスタック構成で、Claude Codeによる自律的なコード修正を開発フローに導入していたところ、SSE(Server-Sent Events)の実装でハマったので備忘録として残します。AIエージェントにストリーミング処理を書かせると、一見動くコードが出てきますが、特定の条件下でフロントエンドがクラッシュする罠が潜んでいました。

この記事でわかること

  • Claude Codeが生成するストリーミングAPI実装の弱点
  • SyntaxError: Unexpected token ‘A’ の正体
  • FastAPI(Python)での正しいフィルタリング実装
  • フロントエンドでの堅牢なJSONパース処理

[現象] ブラウザコンソールに現れる謎のSyntaxError

Claude Codeに「チャットUIのストリーミング機能を実装して」と依頼し、生成されたコードを実行すると、文字の受信中に突然以下のエラーが発生してUIが止まってしまうことがあります。

SyntaxError: Unexpected token 'A', "Anthropic-..." is not valid JSON at 
JSON.parse (<anonymous>)

[環境] 使用しているスタック

レイヤー技術スタック
BackendPython 3.10+ / FastAPI / Anthropic SDK
FrontendReact 18 / Vue 3 / Fetch API (ReadableStream)
AI AgentClaude Code (claude-3-5-sonnet / 3-7-sonnet)

[原因] AIの「思考」とストリーミングの混同

このエラーの根本的な原因は、Claude Codeが生成するコードが「Anthropic APIの生イベント」と「アプリケーションが必要な純粋なデータ」を混同していることにあります。

特にClaude 3.7などの最新モデルでは、APIのレスポンスにThought(思考プロセス)Tool Use(ツール実行)のデータが含まれることがあります。Claude Codeが生成した単純な yield chunk.text のような実装では、これら非表示にすべきメタデータや、API内部の識別子(”Anthropic-…”)がJSONとして不完全なままストリームに流れ込み、フロントエンドの JSON.parse を破壊してしまいます。

[解決策] パース用のフィルタレイヤーの導入

解決には、サーバー側で不要なイベントを間引き、フロントエンド側でエラーに耐性を持たせる両面からのアプローチが必要です。

1. Python側の修正(FastAPI)

生のチャンクをそのまま流さず、ContentBlockDeltaEvent かどうかを判定して、必ず整形済みのJSONとして送出するように変更します。

# Before: AIが生成しがちなNGコード
for chunk in client.messages.stream(...):
    yield chunk.text  # <- ここで「思考プロセス」等が混ざる可能性がある

# After: 堅牢なフィルタリング実装
async def generate_stream():
    async with client.messages.stream(...) as stream:
        async for event in stream:
            # テキストの差分イベントのみを抽出
            if event.type == "content_block_delta":
                # フロントエンドが期待するJSON構造に包む
                data = {"content": event.delta.text}
                yield f"data: {json.dumps(data)}\n\n"
            elif event.type == "message_stop":
                break

2. フロントエンド側の修正

フロントエンドでは、1つのチャンクに複数のJSONが含まれる場合や、不完全なデータが混ざる場合を考慮し、try-catchで保護します。

// フロントエンドのストリームデコーダー部分
const reader = response.body.getReader();
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  
  const chunk = new TextDecoder().decode(value);
  const lines = chunk.split('\n');
  
  for (const line of lines) {
    if (!line.startsWith('data: ')) continue;
    try {
      const jsonStr = line.replace('data: ', '');
      const parsed = JSON.parse(jsonStr);
      setMessages(prev => prev + parsed.content);
    } catch (e) {
      console.warn("JSONパースをスキップ:", e);
      // 不完全なJSONは無視して次へ
      continue;
    }
  }
}

[まとめ] AIエージェント時代の実装指針

Claude CodeなどのAIエージェントは非常に強力ですが、SSE ストリーミングのような「動的な型の変化」が起きやすい場所では、今回のようなJSON.parseエラーを引き起こすコードを生成しがちです。

AIエージェント 実装においては、以下の2点を徹底しましょう。

  • バックエンド: Pydantic等のBaseModelを活用し、AIからの生の出力を必ずフィルタリングする。
  • フロントエンド: ストリームのデコードには必ず例外処理を入れ、パース失敗でUI全体が止まらないように設計する。

これで、AIの思考プロセスがフロントエンドを破壊することなく、快適なチャット体験を提供できるようになります。

タイトルとURLをコピーしました