Responses API web_search

Použite hosťované vyhľadávanie na webe s PHP cURL a bezpečne dekódujte odpovede JSON alebo SSE.

Responses API web_search

Použite hostované web_search nástroj, keď odpoveď závisí od aktuálnych verejných informácií: najnovšie vydania, aktualizácie dokumentácie, ceny, plány, predpisy, zmeny produktov alebo iné skutočnosti, ktoré sa mohli zmeniť po období školenia modelu.

Táto stránka popisuje web_search na POST /v1/responses. Líši sa od samostatného `POST /v1/web-search` koncový bod.

Odporúčaná žiadosť

Odošlite kanonické vstupné pole Responses API. Vyžadovať nástroj, keď musí byť odpoveď uzemnená v živých zdrojoch.

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

Prečo na týchto poliach záleží

  • tools sprístupňuje modelu hosťované vyhľadávanie.
  • tool_choice: required zabraňuje odpovedi iba v pamäti, keď je povinné overenie naživo.
  • include: ["web_search_call.action.sources"] požiada rozhranie API, aby zahrnulo adresy URL, ktoré zohľadňuje volanie vyhľadávania.
  • stream: false požaduje odpoveď bez streamovania, ale kompatibilné brány môžu stále vracať tok udalostí odpovedí. Váš klient musí podporovať oba formáty.

Nepridávajte parametre generovania špecifické pre poskytovateľa, pokiaľ ich vybraný model nepodporuje. Smerovaní poskytovatelia môžu odmietnuť parametre ako napr temperature, backgroundalebo ovládacie prvky výstupného tokenu.

Kompletný príklad cURL PHP

Nasledujúci príklad odošle požiadavku, zachová telo chyby upstream a dekóduje buď normálnu odpoveď JSON, alebo odpoveď 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;
}

Dôležité: stream: false môže ešte vrátiť SSE

stream: false vyjadruje požadovaný režim odpovede. Kompatibilný proxy alebo smerovaný upstream môže stále serializovať dokončený životný cyklus odpovedí ako text/event-stream s udalosťami ako:

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

Neutekaj json_decode() priamo na celé telo, kým nezistíte, či ide o JSON alebo SSE. Pre SSE, zber dokončený response.output_item.done položky a finále response.completed metaúdaje.

Overenie zdroja

V prípade pracovných postupov s aktuálnymi informáciami pred prijatím odpovede overte všetky nasledovné:

  1. Aspoň jeden dokončený web_search_call existuje.
  2. Existuje aspoň jedna adresa URL web_search_call.action.sources alebo an output_text Citácia URL.
  3. Konečný stav odpovede je completed.
  4. Zdroje podporujú tvrdenia, ktoré chcete použiť.
  5. Dôležité skutočnosti sú prednostne potvrdené oficiálnym alebo primárnym zdrojom.

Nepovažujte plynulú odpoveď za dôkaz, že vyhľadávanie prebehlo. Programovo skontrolujte výstup nástroja.

Spracovanie chýb a opakované pokusy

  • Zachovať telá odpovede iné ako 2xx. Obsahujú užitočné overovacie správy poskytovateľa.
  • Nepokúšajte sa znova HTTP 400 bez zmeny zamietnutej žiadosti.
  • Opakujte prechodné 429, 502, 503, 504a zlyhania transportu s exponenciálnym ustupovaním a jitterom.
  • Ak by opakovaný pokus mohol spustiť obchodnú akciu, použite kľúč idempotencie vo svojej vlastnej aplikácii.
  • Udržujte časové limity aplikácií, PHP, reverzného proxy a brány zarovnané pre dlhé požiadavky na vyhľadávanie.
  • Nikdy nevystavujte kľúče API alebo zachytené nespracované dáta koncovým používateľom.

Súvisiace stránky