レスポンス API web_search

PHP cURL でホストされた Web 検索を使用し、JSON または SSE 応答を安全にデコードします。

レスポンスAPI web_search

ホストされているものを使用する web_search 回答が最新の公開情報 (最近のリリース、ドキュメントの更新、価格、スケジュール、規制、製品の変更、またはモデルのトレーニング期間後に変更された可能性のあるその他の事実) に依存する場合には、このツールを使用します。

このページでは説明します web_search の上 POST /v1/responses。スタンドアロンとは違います `POST /v1/web-search` 終点。

おすすめのリクエスト

正規の応答 API 入力配列を送信します。答えがライブソースに基づいている必要がある場合は、ツールが必要です。

{
  "model": "gpt-5.5",
  "input": [
    {
      "role": "user",
      "content": [
        {
          "type": "input_text",
          "text": "Find the current stable PHP version. Use web search and cite the official sources."
        }
      ]
    }
  ],
  "tools": [
    {
      "type": "web_search"
    }
  ],
  "tool_choice": "required",
  "include": [
    "web_search_call.action.sources"
  ],
  "stream": false
}

これらのフィールドが重要な理由

  • tools ホスト型検索をモデルで利用できるようにします。
  • tool_choice: required ライブ検証が必須の場合、記憶のみの回答を防ぎます。
  • include: ["web_search_call.action.sources"] 検索呼び出しで考慮される URL を含めるよう API に要求します。
  • stream: false は非ストリーミング応答を要求しますが、互換性のあるゲートウェイは引き続き応答イベント ストリームを返す場合があります。クライアントは両方の形式をサポートする必要があります。

選択したモデルでサポートされている場合を除き、プロバイダー固有の生成パラメーターを追加しないでください。ルーティングされたプロバイダーは、次のようなパラメータを拒否する場合があります。 temperaturebackground、または出力トークン コントロール。

完全な PHP cURL の例

次の例では、リクエストを送信し、アップストリームのエラー本文を保存し、通常の JSON 応答または Server-Sent Events 応答のいずれかをデコードします。

<?php

declare(strict_types=1);

$baseUrl = 'https://api.model-gate.com';
$apiKey = getenv('MODEL_GATE_API_KEY') ?: '';

if ($apiKey === '') {
    throw new RuntimeException('MODEL_GATE_API_KEY is not configured.');
}

$url = rtrim($baseUrl, '/') . '/v1/responses';

$request = [
    'model' => 'gpt-5.5',
    'input' => [[
        'role' => 'user',
        'content' => [[
            'type' => 'input_text',
            'text' => 'Find the current stable PHP version. '
                . 'Use web search and cite official sources.',
        ]],
    ]],
    'tools' => [[
        'type' => 'web_search',
    ]],
    'tool_choice' => 'required',
    'include' => [
        'web_search_call.action.sources',
    ],
    'stream' => false,
];

$json = json_encode(
    $request,
    JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR
);

$responseHeaders = [];
$ch = curl_init($url);
if ($ch === false) {
    throw new RuntimeException('Unable to initialize cURL.');
}

curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT => 15,
    CURLOPT_TIMEOUT => 900,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
        'Accept: application/json, text/event-stream',
    ],
    CURLOPT_POSTFIELDS => $json,
    CURLOPT_HEADERFUNCTION => static function ($ch, string $line) use (&$responseHeaders): int {
        $length = strlen($line);
        $line = trim($line);
        if ($line === '' || !str_contains($line, ':')) {
            return $length;
        }
        [$name, $value] = array_map('trim', explode(':', $line, 2));
        $responseHeaders[strtolower($name)] = $value;
        return $length;
    },
]);

$responseBody = curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErrorNumber = curl_errno($ch);
$curlError = curl_error($ch);
curl_close($ch);

if ($responseBody === false) {
    throw new RuntimeException(
        sprintf('cURL error %d: %s', $curlErrorNumber, $curlError)
    );
}

if ($httpCode < 200 || $httpCode >= 300) {
    // Keep the exact upstream body. It usually contains the parameter or model error.
    throw new RuntimeException(
        "API returned HTTP {$httpCode}: {$responseBody}"
    );
}

$response = decodeResponsesBody(
    $responseBody,
    $responseHeaders['content-type'] ?? ''
);

$text = '';
$sources = [];
$searchCalls = 0;

foreach (($response['output'] ?? []) as $item) {
    if (($item['type'] ?? null) === 'web_search_call') {
        $searchCalls++;
        foreach (($item['action']['sources'] ?? []) as $source) {
            $url = trim((string) ($source['url'] ?? ''));
            if ($url !== '') {
                $sources[$url] = $url;
            }
        }
    }

    if (($item['type'] ?? null) !== 'message') {
        continue;
    }

    foreach (($item['content'] ?? []) as $part) {
        if (($part['type'] ?? null) !== 'output_text') {
            continue;
        }
        $text .= (string) ($part['text'] ?? '');
        foreach (($part['annotations'] ?? []) as $annotation) {
            if (($annotation['type'] ?? null) !== 'url_citation') {
                continue;
            }
            $url = trim((string) ($annotation['url'] ?? ''));
            if ($url !== '') {
                $sources[$url] = $url;
            }
        }
    }
}

if ($searchCalls === 0) {
    throw new RuntimeException('The response did not contain web_search_call.');
}
if ($sources === []) {
    throw new RuntimeException('The response did not contain auditable source URLs.');
}

echo $text . PHP_EOL;
echo "Sources:" . PHP_EOL;
foreach ($sources as $sourceUrl) {
    echo '- ' . $sourceUrl . PHP_EOL;
}

function decodeResponsesBody(string $body, string $contentType): array
{
    $trimmed = ltrim($body);
    $looksLikeSse = str_contains(strtolower($contentType), 'text/event-stream')
        || str_starts_with($trimmed, 'event:')
        || str_starts_with($trimmed, 'data:');

    if (!$looksLikeSse) {
        $decoded = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
        if (!is_array($decoded)) {
            throw new RuntimeException('Responses API returned a non-object JSON value.');
        }
        return $decoded;
    }

    return decodeResponsesSse($body);
}

function decodeResponsesSse(string $body): array
{
    $output = [];
    $completedResponse = [];
    $eventName = '';
    $dataLines = [];

    $flush = static function () use (&$eventName, &$dataLines, &$output, &$completedResponse): void {
        if ($dataLines === []) {
            $eventName = '';
            return;
        }

        $rawData = implode("\n", $dataLines);
        $dataLines = [];
        if ($rawData === '[DONE]') {
            $eventName = '';
            return;
        }

        $event = json_decode($rawData, true, 512, JSON_THROW_ON_ERROR);
        $type = (string) ($event['type'] ?? $eventName);

        if ($type === 'response.output_item.done' && is_array($event['item'] ?? null)) {
            $output[] = $event['item'];
        }
        if ($type === 'response.completed' && is_array($event['response'] ?? null)) {
            $completedResponse = $event['response'];
        }

        $eventName = '';
    };

    foreach (preg_split('/\r\n|\r|\n/', $body) ?: [] as $line) {
        if ($line === '') {
            $flush();
            continue;
        }
        if (str_starts_with($line, 'event:')) {
            $eventName = trim(substr($line, 6));
            continue;
        }
        if (str_starts_with($line, 'data:')) {
            $dataLines[] = ltrim(substr($line, 5));
        }
    }
    $flush();

    if ($completedResponse === [] && $output === []) {
        throw new RuntimeException('No completed Responses API event was found.');
    }

    // Some compatible gateways send an empty output array in response.completed.
    // Preserve the completed metadata but reconstruct output from output_item.done events.
    $completedResponse['output'] = $output !== []
        ? $output
        : ($completedResponse['output'] ?? []);

    return $completedResponse;
}

重要: stream: false まだ SSE を返す可能性があります

stream: false 要求された応答モードを表します。互換性のあるプロキシまたは上流にルーティングされた場合でも、完了した応答ライフサイクルを次のようにシリアル化できます。 text/event-stream 次のようなイベントが含まれます。

event: response.output_item.done
data: {"type":"response.output_item.done","item":{"type":"web_search_call",...}}

event: response.completed
data: {"type":"response.completed","response":{"status":"completed",...}}

走らないでください json_decode() JSON か SSE かを判断するまで、本文全体に直接入力します。 SSEの場合、収集完了 response.output_item.done アイテムとファイナル response.completed メタデータ。

ソースの検証

最新情報のワークフローの場合は、回答を受け入れる前に次のすべてを検証してください。

  1. 少なくとも 1 つは完了しました web_search_call 存在します。
  2. 少なくとも 1 つの URL が存在します web_search_call.action.sources または output_text URL引用。
  3. 最終的な応答ステータスは、 completed
  4. ソースは、使用する予定の主張を裏付けています。
  5. 重要な事実は、公式または一次情報源によって確認されることが望ましいです。

流暢な回答を検索が実行された証拠として扱わないでください。ツールの出力をプログラムで確認します。

エラー処理と再試行

  • 2xx 以外の応答本文を保持します。これらには、役立つプロバイダー検証メッセージが含まれています。
  • HTTP を再試行しないでください 400 拒否されたリクエストを変更せずに。
  • 一時的な再試行 429502503504、および指数関数的なバックオフとジッターを伴うトランスポート障害。
  • 再試行によってビジネス アクションがトリガーされる可能性がある場合は、独自のアプリケーションで冪等キーを使用します。
  • アプリケーション、PHP、リバースプロキシ、およびゲートウェイのタイムアウトを、長い検索リクエストに合わせて調整します。
  • API キーやキャプチャされた生のペイロードをエンド ユーザーに決して公開しないでください。

関連ページ