6.8 KiB
OpenAI Responses scheme の新設
レビュー完了(close 可) — 詳細は
llm-scheme-openai-responses.review.md9 要件すべて達成、指摘事項は優先度低の 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 の地位は低下していく。
要件
-
scheme/openai_responsesを新設し、HttpTransport<S: Scheme>に差し込めるようにする -
リクエスト body は
/v1/responsesの item-based 形式:model,instructions(system prompt 相当),input: [ResponseItem],toolstool_choice: "auto"/parallel_tool_calls: trueは scheme 固定値で常時送信(将来必要になれば 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 側管理)
-
SSE event パース:
response.created/response.completed/response.failed/response.incompleteresponse.output_item.added/response.output_item.doneresponse.content_part.added/response.content_part.doneresponse.output_text.deltaresponse.function_call_arguments.delta(通常 function tool の引数 partial JSON)response.custom_tool_call_input.delta(custom tool のフリーフォーム入力 partial JSON)response.reasoning_text.delta/response.reasoning_summary_text.delta
-
BlockType / DeltaContent との対応:
- text BlockStart は
response.content_part.added(Anthropic のcontent_block_startと対称) - tool_use BlockStart は
response.output_item.added(id と name が確定する時点、streaming に乗せるためここ) response.output_text.delta→DeltaContent::Textresponse.reasoning_text.delta/response.reasoning_summary_text.delta→DeltaContent::Thinkingresponse.function_call_arguments.deltaとresponse.custom_tool_call_input.delta→ 両方ともDeltaContent::InputJsonに正規化response.content_part.done/response.output_item.done→BlockStop
- text BlockStart は
-
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_contentはNoneで既存互換を保つ - 将来 Anthropic の extended thinking で
signature: Option<String>を追加する余地を残す
- 送信時は
-
認証は
AuthRef::ApiKeyのみ対応:Authorization: Bearer <api_key>ヘッダ。base_urlデフォルトはhttps://api.openai.com、パスは/v1/responses。ChatGPT OAuth 経路(CodexOAuth)は別チケット(llm-auth-codex-oauth)で追加 -
Usage の正規化:
response.completedのusage: { input_tokens, output_tokens, total_tokens }をUsageEventに変換 -
capability テーブル: GPT-5 / o3 / o4 のモデル ID 判定は
scheme/openai_chat/capability.rsと重複するため 共通関数に切り出して共有(配置はscheme/openai_chat/capability.rsにpub(crate) fn classify(model_id) -> Option<OpenAiFamily>を置くか、scheme/openai_common/を切り出すかは実装時判断)。Responses 側はReasoningSupport::Effort固定でマッピング -
完了時の動作: 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_clientのSchemeKind::OpenaiResponsesアームをSchemeNotImplementedから実装に差し替え