Responses API web_search

Uporabite gostujoče spletno iskanje s PHP cURL in varno dekodirajte odgovore JSON ali SSE.

Responses API web_search

Uporabite gostovano web_search orodje, ko je odgovor odvisen od trenutnih javnih informacij: nedavne izdaje, posodobitve dokumentacije, cene, urniki, predpisi, spremembe izdelkov ali druga dejstva, ki so se morda spremenila po obdobju usposabljanja za model.

Ta stran opisuje web_search na POST /v1/responses. Razlikuje se od samostojnega `POST /v1/web-search` končna točka.

Priporočena zahteva

Pošljite kanonično vhodno matriko Responses API. Zahtevajte orodje, ko mora odgovor temeljiti na živih virih.

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

Zakaj so ta polja pomembna

  • tools omogoča gostujoče iskanje na voljo modelu.
  • tool_choice: required prepreči odgovor samo za spomin, ko je preverjanje v živo obvezno.
  • include: ["web_search_call.action.sources"] od API-ja zahteva, da vključi URL-je, ki jih obravnava iskalni klic.
  • stream: false zahteva nepretočni odgovor, vendar lahko združljivi prehodi še vedno vrnejo tok dogodkov Responses. Vaša stranka mora podpirati oba formata.

Ne dodajajte parametrov generiranja, specifičnih za ponudnika, razen če izbrani model dokumentira njihovo podporo. Usmerjeni ponudniki lahko zavrnejo parametre, kot je npr temperature, backgroundali kontrolniki izhodnih žetonov.

Celoten primer PHP cURL

Naslednji primer pošlje zahtevo, ohrani telo napake navzgor in dekodira običajen odgovor JSON ali odgovor dogodkov, poslanih s strežnika.

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

Pomembno: stream: false lahko še vedno vrne SSE

stream: false izraža zahtevani način odgovora. Združljiv proxy ali usmerjen navzgor lahko še vedno serializira dokončani življenjski cikel odzivov kot text/event-stream with events such as:

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

Do not run json_decode() neposredno na celotno telo, dokler ne ugotovite, ali je JSON ali SSE. Za SSE zbiranje končano response.output_item.done predmetov in konč response.completed metapodatki.

Preverjanje vira

Za poteke dela s trenutnimi informacijami potrdite vse naslednje, preden sprejmete odgovor:

  1. Vsaj ena dokončana web_search_call obstaja.
  2. Vsaj en URL obstaja v web_search_call.action.sources ali an output_text URL citat.
  3. Stanje končnega odgovora je completed.
  4. Viri podpirajo trditve, ki jih nameravate uporabiti.
  5. Pomembna dejstva so po možnosti potrjena s strani uradnega ali primarnega vira.

Ne obravnavajte tekočega odgovora kot dokaz, da je iskanje potekalo. Programsko preverite rezultate orodja.

Obravnava napak in ponovni poskusi

  • Ohranite odzivna telesa, ki niso 2xx. Vsebujejo uporabna sporočila za preverjanje ponudnika.
  • Ne poskusite znova HTTP 400 brez spreminjanja zavrnjene zahteve.
  • Poskusi znova prehodno 429, 502, 503, 504in napake pri transportu z eksponentnim odmikom in tresenjem.
  • Uporabite ključ za idempotenco v svoji aplikaciji, ko bi lahko ponovni poskus sprožil poslovno dejanje.
  • Za dolge iskalne zahteve naj bodo časovne omejitve aplikacij, PHP, povratnega proxyja in prehoda usklajene.
  • Nikoli ne razkrijte ključev API ali zajetih neobdelanih uporabnih obremenitev končnim uporabnikom.

Povezane strani