yoi/tickets/llm-scheme-openai-responses.md
2026-04-20 02:59:16 +09:00

6.8 KiB
Raw Blame History

OpenAI Responses scheme の新設

レビュー完了close 可) — 詳細は llm-scheme-openai-responses.review.md 9 要件すべて達成、指摘事項は優先度低の 2 件のみtool 引数 delta 先行時の空メタデータ、tools[].strict ハードコード)。いずれも実害薄。

背景

現状の crates/llm-worker/src/llm_client/scheme/openai_chat は OpenAI Chat Completions (/v1/chat/completions) wire format のみ実装。OpenAI の Responses API (/v1/responses) はリクエスト body・SSE イベント構造ともに Chat Completions と別物で、同じ scheme には乗らない。

Codex CLI (github.com/openai/codex) の実装を確認したところ、ChatGPT OAuth 経路でも OpenAI API Key 経路でもすべて /v1/responses を叩いており、Chat Completions は使っていない。Codex 流用(別チケット llm-auth-codex-oauth)を実現する前提として、この scheme が必要。

また OpenAI 本家の最新モデルGPT-5 系・o シリーズの reasoningは Responses API 経由が主要な経路であり、長期的にも Chat Completions の地位は低下していく。

要件

  1. scheme/openai_responses を新設し、HttpTransport<S: Scheme> に差し込めるようにする

  2. リクエスト body/v1/responses の item-based 形式:

    • model, instructions (system prompt 相当), input: [ResponseItem], tools
    • tool_choice: "auto" / parallel_tool_calls: truescheme 固定値で常時送信(将来必要になれば Request / RequestConfig に昇格、今は YAGNI
    • reasoning: { effort?, summary? }ReasoningControl から投影
    • store: false + include: ["reasoning.encrypted_content"] を scheme 固定値で送信stateless 運用 + 再送のため encrypted reasoning を取得)
    • stream: true 固定
    • service_tier?, prompt_cache_key?, text?: { verbosity?, format? } は当面未使用、フィールドの予約のみ
    • previous_response_id使わないstateless、履歴は insomnia 側管理)
  3. SSE event パース:

    • response.created / response.completed / response.failed / response.incomplete
    • response.output_item.added / response.output_item.done
    • response.content_part.added / response.content_part.done
    • response.output_text.delta
    • response.function_call_arguments.delta(通常 function tool の引数 partial JSON
    • response.custom_tool_call_input.deltacustom tool のフリーフォーム入力 partial JSON
    • response.reasoning_text.delta / response.reasoning_summary_text.delta
  4. BlockType / DeltaContent との対応:

    • text BlockStartresponse.content_part.addedAnthropic の content_block_start と対称)
    • tool_use BlockStartresponse.output_item.addedid と name が確定する時点、streaming に乗せるためここ)
    • response.output_text.deltaDeltaContent::Text
    • response.reasoning_text.delta / response.reasoning_summary_text.deltaDeltaContent::Thinking
    • response.function_call_arguments.deltaresponse.custom_tool_call_input.delta → 両方とも DeltaContent::InputJson に正規化
    • response.content_part.done / response.output_item.doneBlockStop
  5. Item::Reasoning の拡張llm-worker/types.rs への変更を含む):

    Item::Reasoning {
        text: String,
        summary: Vec<String>,
        encrypted_content: Option<String>,
    }
    
    • 送信時は input[] の reasoning item に再構築(encrypted_content があれば添える)
    • 受信時は SSE から text / summary[] / encrypted_content を組み立てて Item::Reasoning に格納
    • 既存 Item::Reasoning { text } の 1 フィールドからの拡張。summary は空 Vec、encrypted_contentNone で既存互換を保つ
    • 将来 Anthropic の extended thinking で signature: Option<String> を追加する余地を残す
  6. 認証は AuthRef::ApiKey のみ対応: Authorization: Bearer <api_key> ヘッダ。base_url デフォルトは https://api.openai.com、パスは /v1/responses。ChatGPT OAuth 経路(CodexOAuth)は別チケット(llm-auth-codex-oauth)で追加

  7. Usage の正規化: response.completedusage: { input_tokens, output_tokens, total_tokens }UsageEvent に変換

  8. capability テーブル: GPT-5 / o3 / o4 のモデル ID 判定は scheme/openai_chat/capability.rs と重複するため 共通関数に切り出して共有(配置は scheme/openai_chat/capability.rspub(crate) fn classify(model_id) -> Option<OpenAiFamily> を置くか、scheme/openai_common/ を切り出すかは実装時判断。Responses 側は ReasoningSupport::Effort 固定でマッピング

  9. 完了時の動作: OpenAI API key (OPENAI_API_KEY) + モデル gpt-5 等で ModelConfig { scheme: OpenAIResponses, base_url: https://api.openai.com, model_id: "gpt-5", auth: ApiKey } を宣言すると、reasoning + tool call を含む会話が動作する

設計課題

1. scheme-specific 設定の override フィールド

store / include[] を scheme 固定値にしたが、将来 ZDR 非対応環境で store=true を許したくなる可能性がある。OpenAIResponsesScheme 自身にフィールド (store: bool, include_encrypted_content: bool 等) を持たせ、new() 時に上書きできる形にする。ModelCapability には入れないscheme-specific な wire 設定なので)。

2. Responses 非対応パラメータ

service_tier / prompt_cache_key / text.verbosity は当面不要だが、将来対応時に scheme 拡張で入れられる構造にしておく。

Scope 外

  • ChatGPT OAuth 認証(llm-auth-codex-oauth チケットで実装)
  • previous_response_id を使う stateful 運用
  • 高次ツール(web_search / code_interpreter / computer_use)— insomnia では採用しない方針
  • tool_choice / parallel_tool_calls の Request 昇格(必要性が出てから別チケット)

依存

  • tickets/llm-model-config.md 完了済(HttpTransport<S> 構造と AuthRef が前提)

影響範囲

llm-worker 単独ではなく以下にまたがる:

  • crates/llm-worker/src/llm_client/types.rs: Item::Reasoning の拡張
  • crates/llm-worker/src/llm_client/scheme/openai_responses/: 新規
  • crates/llm-worker/src/llm_client/scheme/openai_chat/capability.rs: モデル family 判定を pub(crate) に露出
  • crates/llm-worker/src/llm_client/scheme/mod.rs: pub mod openai_responses;
  • crates/provider/src/lib.rs: build_clientSchemeKind::OpenaiResponses アームを SchemeNotImplemented から実装に差し替え