Responses API web_search

Använd värdbaserad webbsökning med PHP cURL och avkoda säkert antingen JSON- eller SSE-svar.

Responses API web_search

Använd den värd web_search verktyg när ett svar beror på aktuell offentlig information: senaste utgåvor, dokumentationsuppdateringar, priser, scheman, förordningar, produktändringar eller andra fakta som kan ha ändrats efter modellutbildningsperioden.

Denna sida beskriver web_searchPOST /v1/responses. Den skiljer sig från den fristående `POST /v1/web-search` slutpunkt.

Rekommenderad begäran

Skicka en kanonisk Responses API-inmatningsmatris. Kräv verktyget när svaret måste vara jordat i livekällor.

{
  "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
}

Varför dessa fält är viktiga

  • tools gör värdsökning tillgänglig för modellen.
  • tool_choice: required förhindrar ett svar endast i minnet när liveverifiering är obligatorisk.
  • include: ["web_search_call.action.sources"] ber API:et att inkludera webbadresserna som beaktas av sökanropet.
  • stream: false begär ett icke-strömmande svar, men kompatibla gateways kan fortfarande returnera en Responses-händelseström. Din klient måste stödja båda formaten.

Lägg inte till leverantörsspecifika genereringsparametrar om inte den valda modellen dokumenterar stöd för dem. Routerade leverantörer kan avvisa parametrar som t.ex temperature, background, eller kontroller för utdatatoken.

Komplett PHP cURL exempel

Följande exempel skickar begäran, bevarar uppströmsfelkroppen och avkodar antingen ett normalt JSON-svar eller ett Server-Sent Event-svar.

<?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;
}

Viktig: stream: false kan fortfarande returnera SSE

stream: false uttrycker det begärda svarsläget. En kompatibel proxy eller dirigerad uppströms kan fortfarande serialisera den avslutade Responses-livscykeln som text/event-stream med evenemang som:

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",...}}

Spring inte json_decode() direkt på hela kroppen tills du har bestämt om det är JSON eller SSE. För SSE, insamling genomförd response.output_item.done föremål och finalen response.completed metadata.

Källvalidering

För aktuella arbetsflöden, validera alla följande innan du accepterar svaret:

  1. Minst en avklarad web_search_call finns.
  2. Minst en webbadress finns i web_search_call.action.sources eller en output_text URL-hänvisning.
  3. Den slutliga svarsstatusen är completed.
  4. Källorna stödjer de påståenden du tänker använda.
  5. Viktiga fakta bekräftas helst av en officiell eller primär källa.

Behandla inte ett flytande svar som ett bevis på att sökningen pågick. Kontrollera verktygsutgången programmatiskt.

Felhantering och försök igen

  • Bevara icke-2xx-svarskroppar. De innehåller användbara leverantörsvalideringsmeddelanden.
  • Försök inte igen med HTTP 400 utan att ändra den avvisade begäran.
  • Försök transient igen 429, 502, 503, 504, och transportfel med exponentiell backoff och jitter.
  • Använd en idempotensnyckel i din egen applikation när ett nytt försök kan utlösa en affärsåtgärd.
  • Håll tidsgränserna för program, PHP, omvänd proxy och gateway anpassade för långa sökförfrågningar.
  • Utsätt aldrig API-nycklar eller fångade rånyttolaster för slutanvändare.

Relaterade sidor