Responses API web_search

Koristite hostirano web pretraživanje s PHP cURL i sigurno dekodirajte bilo JSON ili SSE odgovore.

Responses API web_search

Koristite hostirani web_search alat kada odgovor ovisi o trenutnim javnim informacijama: nedavna izdanja, ažuriranja dokumentacije, cijene, rasporedi, propisi, promjene proizvoda ili druge činjenice koje su se možda promijenile nakon razdoblja obuke modela.

Ova stranica opisuje web_search na POST /v1/responses. Razlikuje se od samostalnog `POST /v1/web-pretraživanje` krajnja točka.

Preporučeni zahtjev

Pošaljite kanonski Responses API input niz. Zahtijevati alat kada odgovor mora biti utemeljen na živim izvorima.

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

Zašto su ova polja važna

  • tools čini hostirano pretraživanje dostupnim modelu.
  • tool_choice: required sprječava odgovor samo za pamćenje kada je provjera uživo obavezna.
  • include: ["web_search_call.action.sources"] traži od API-ja da uključi URL-ove koje razmatra poziv pretraživanja.
  • stream: false zahtijeva odgovor bez strujanja, ali kompatibilni pristupnici ipak mogu vratiti tok događaja Odgovora. Vaš klijent mora podržavati oba formata.

Nemojte dodavati parametre generiranja specifične za pružatelja usluga osim ako ih odabrani model ne podržava. Usmjereni pružatelji mogu odbiti parametre kao što su temperature, background, ili kontrole izlaznog tokena.

Kompletan PHP cURL primjer

Sljedeći primjer šalje zahtjev, čuva uzvodno tijelo pogreške i dekodira normalan JSON odgovor ili odgovor događaja poslanih s poslužitelja.

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

Važno: stream: false još uvijek može vratiti SSE

stream: false izražava traženi način odgovora. Kompatibilni proxy ili usmjeren uzvodno još uvijek može serijalizirati dovršeni životni ciklus Odgovora kao text/event-stream s događajima kao što su:

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

Ne trči json_decode() izravno na cijelo tijelo dok ne utvrdite je li JSON ili SSE. Za SSE, prikupljanje dovršeno response.output_item.done stavke i final response.completed metapodaci.

Provjera valjanosti izvora

Za tijekove rada s trenutnim informacijama provjerite sve sljedeće prije prihvaćanja odgovora:

  1. Najmanje jedan završen web_search_call postoji.
  2. Najmanje jedan URL postoji u web_search_call.action.sources ili an output_text URL citat.
  3. Konačni status odgovora je completed.
  4. Izvori podržavaju tvrdnje koje namjeravate koristiti.
  5. Važne činjenice poželjno je potvrditi službenim ili primarnim izvorom.

Nemojte tečan odgovor smatrati dokazom da je pretraga pokrenuta. Provjerite izlaz alata programski.

Rješavanje pogrešaka i ponovni pokušaji

  • Sačuvajte tijela odgovora koja nisu 2xx. Sadrže korisne poruke za provjeru valjanosti pružatelja usluga.
  • Ne pokušavajte ponovno HTTP 400 bez promjene odbijenog zahtjeva.
  • Pokušaj ponovno prijelazno 429, 502, 503, 504i kvarovi transporta s eksponencijalnim odmakom i podrhtavanjem.
  • Koristite ključ idempotencije u vlastitoj aplikaciji kada bi ponovni pokušaj mogao pokrenuti poslovnu radnju.
  • Uskladite vremenska ograničenja aplikacije, PHP-a, obrnutog proxyja i pristupnika za duge zahtjeve pretraživanja.
  • Nikada ne izlažite API ključeve ili snimljene neobrađene podatke krajnjim korisnicima.

Povezane stranice