Responses API web_search

Použijte hostované vyhledávání na webu s PHP cURL a bezpečně dekódujte odpovědi JSON nebo SSE.

Responses API web_search

Použijte hostovaný web_search nástroj, když odpověď závisí na aktuálních veřejných informacích: poslední verze, aktualizace dokumentace, ceny, plány, předpisy, změny produktu nebo jiné skutečnosti, které se mohly změnit po období školení modelu.

Tato stránka popisuje web_search na POST /v1/responses. Liší se od samostatného `POST /v1/web-search` koncový bod.

Doporučený požadavek

Odešlete vstupní pole kanonického rozhraní API odpovědí. Vyžadovat nástroj, když musí být odpověď uzemněna v živých zdrojích.

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

Proč na těchto polích záleží

  • tools zpřístupňuje modelu hostované vyhledávání.
  • tool_choice: required zabraňuje odpovědi pouze v paměti, když je živé ověření povinné.
  • include: ["web_search_call.action.sources"] požádá rozhraní API, aby zahrnulo adresy URL zvažované vyhledávacím voláním.
  • stream: false požaduje odpověď bez streamování, ale kompatibilní brány mohou stále vracet proud událostí Responses. Váš klient musí podporovat oba formáty.

Nepřidávejte parametry generování specifické pro poskytovatele, pokud je vybraný model nepodporuje. Směrovaní poskytovatelé mohou odmítnout parametry jako např temperature, backgroundnebo ovládací prvky výstupního tokenu.

Kompletní příklad cURL PHP

Následující příklad odešle požadavek, zachová tělo chyby proti proudu a dekóduje buď normální odpověď JSON, nebo odpověď událostí odeslaných serverem.

<?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 stále vrátit SSE

stream: false vyjadřuje požadovaný režim odezvy. Kompatibilní proxy nebo směrovaný upstream může stále serializovat dokončený životní cyklus odpovědí jako text/event-stream s událostmi jako:

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

Neutíkej json_decode() přímo na celé tělo, dokud nezjistíte, zda se jedná o JSON nebo SSE. Pro SSE, sběr dokončen response.output_item.done položky a finále response.completed metadata.

Ověření zdroje

U pracovních postupů s aktuálními informacemi ověřte před přijetím odpovědi všechny následující:

  1. Alespoň jedna dokončena web_search_call existuje.
  2. Existuje alespoň jedna adresa URL web_search_call.action.sources nebo an output_text URL citace.
  3. Konečný stav odpovědi je completed.
  4. Zdroje podporují tvrzení, která hodláte použít.
  5. Důležitá fakta jsou přednostně potvrzena oficiálním nebo primárním zdrojem.

Nepovažujte plynulou odpověď za důkaz, že vyhledávání proběhlo. Zkontrolujte výstup nástroje programově.

Zpracování chyb a opakování

  • Zachovat těla odpovědí jiná než 2xx. Obsahují užitečné zprávy o ověření poskytovatele.
  • Nezkoušejte HTTP znovu 400 beze změny zamítnuté žádosti.
  • Opakujte přechodné 429, 502, 503, 504a transportní poruchy s exponenciálním ustupováním a jitterem.
  • Použijte klíč idempotence ve své vlastní aplikaci, pokud by opakovaný pokus mohl spustit obchodní akci.
  • Udržujte časové limity aplikací, PHP, reverzního proxy a brány zarovnané pro dlouhé požadavky na vyhledávání.
  • Nikdy nevystavujte klíče API nebo zachycené nezpracované datové části koncovým uživatelům.

Související stránky