Responses API web_search

Bruk vertsbasert nettsøk med PHP cURL og dekod trygt enten JSON- eller SSE-svar.

Responses API web_search

Bruk den hostede web_search verktøy når et svar avhenger av gjeldende offentlig informasjon: nylige utgivelser, dokumentasjonsoppdateringer, priser, tidsplaner, forskrifter, produktendringer eller andre fakta som kan ha endret seg etter modellopplæringsperioden.

Denne siden beskriver web_searchPOST /v1/responses. Den er forskjellig fra den frittstående `POST /v1/nettsøk` endepunkt.

Anbefalt forespørsel

Send en kanonisk Responses API-inndatamatrise. Krev verktøyet når svaret må være forankret i levende kilder.

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

Hvorfor disse feltene betyr noe

  • tools gjør vertssøk tilgjengelig for modellen.
  • tool_choice: required forhindrer et svar som kun er i minnet når direkte bekreftelse er obligatorisk.
  • include: ["web_search_call.action.sources"] ber API-et om å inkludere nettadressene som vurderes av søkekallet.
  • stream: false ber om et ikke-streamende svar, men kompatible gatewayer kan fortsatt returnere en Responses-hendelsesstrøm. Din klient må støtte begge formatene.

Ikke legg til leverandørspesifikke generasjonsparametere med mindre den valgte modellen dokumenterer støtte for dem. Rutede tilbydere kan avvise parametere som f.eks temperature, background, eller utdata-token-kontroller.

Komplett PHP cURL eksempel

Følgende eksempel sender forespørselen, bevarer oppstrøms feilteksten og dekoder enten et normalt JSON-svar eller et serversendt hendelsessvar.

<?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 fortsatt returnere SSE

stream: false uttrykker den forespurte responsmodusen. En kompatibel proxy eller rutet oppstrøms kan fortsatt serialisere den fullførte Responses-livssyklusen som text/event-stream med arrangementer 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",...}}

Ikke løp json_decode() direkte på hele kroppen til du har bestemt om det er JSON eller SSE. For SSE, innsamling fullført response.output_item.done elementer og finalen response.completed metadata.

Kildevalidering

For arbeidsflyter med gjeldende informasjon, valider alt av følgende før du godtar svaret:

  1. Minst en fullført web_search_call finnes.
  2. Minst én URL finnes i web_search_call.action.sources eller en output_text URL-sitering.
  3. Den endelige svarstatusen er completed.
  4. Kildene støtter påstandene du har tenkt å bruke.
  5. Viktige fakta bekreftes fortrinnsvis av en offisiell eller primær kilde.

Ikke behandle et flytende svar som bevis på at søket kjørte. Sjekk verktøyutgangen programmatisk.

Feilhåndtering og forsøk på nytt

  • Bevar ikke-2xx respons organer. De inneholder nyttige meldinger om leverandørvalidering.
  • Ikke prøv HTTP på nytt 400 uten å endre den avviste forespørselen.
  • Prøv forbigående på nytt 429, 502, 503, 504, og transportfeil med eksponentiell backoff og jitter.
  • Bruk en idempotensnøkkel i din egen applikasjon når et nytt forsøk kan utløse en forretningshandling.
  • Hold tidsavbrudd for applikasjoner, PHP, omvendt proxy og gateway på linje for lange søkeforespørsler.
  • Utsett aldri API-nøkler eller innfangede rå nyttelaster for sluttbrukere.

Relaterte sider