Reacties-API web_search

Gebruik gehoste internetzoekopdrachten met PHP cURL en decodeer veilig JSON- of SSE-reacties.

Reacties-API web_search

Gebruik de gehoste web_search tool wanneer een antwoord afhangt van actuele publieke informatie: recente releases, documentatie-updates, prijzen, schema's, regelgeving, productwijzigingen of andere feiten die mogelijk zijn veranderd na de modeltrainingsperiode.

Deze pagina beschrijft web_search op POST /v1/responses. Het is anders dan de stand-alone `POST /v1/web-search` eindpunt.

Aanbevolen verzoek

Verzend een canonieke Responses API-invoerarray. Vereis de tool wanneer het antwoord gebaseerd moet zijn op live bronnen.

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

Waarom deze velden ertoe doen

  • tools maakt gehost zoeken beschikbaar voor het model.
  • tool_choice: required voorkomt een antwoord dat alleen uit het geheugen bestaat wanneer live verificatie verplicht is.
  • include: ["web_search_call.action.sources"] vraagt ​​de API om de URL's op te nemen die in aanmerking worden genomen bij de zoekopdracht.
  • stream: false vraagt ​​om een ​​niet-streamingantwoord, maar compatibele gateways kunnen nog steeds een responsgebeurtenisstroom retourneren. Uw klant moet beide formaten ondersteunen.

Voeg geen providerspecifieke generatieparameters toe, tenzij de geselecteerde modeldocumenten deze ondersteunen. Gerouteerde providers kunnen parameters zoals temperature, backgroundof uitvoertokenbesturingselementen.

Compleet PHP cURL-voorbeeld

In het volgende voorbeeld wordt de aanvraag verzonden, de upstream-fouttekst behouden en een normaal JSON-antwoord of een door de server verzonden gebeurtenissen-antwoord gedecodeerd.

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

Belangrijk: stream: false kan nog steeds SSE retourneren

stream: false drukt de gevraagde antwoordmodus uit. Een compatibele proxy of stroomopwaarts gerouteerd kan de voltooide levenscyclus van reacties nog steeds serialiseren als text/event-stream met evenementen als:

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

Niet rennen json_decode() direct op de gehele body totdat je hebt vastgesteld of het JSON of SSE is. Voor SSE is het verzamelen voltooid response.output_item.done artikelen en de finale response.completed metagegevens.

Bronvalidatie

Voor workflows met actuele informatie valideert u al het volgende voordat u het antwoord accepteert:

  1. Minstens één voltooid web_search_call bestaat.
  2. Er bestaat ten minste één URL in web_search_call.action.sources of een output_text URL-citatie.
  3. De uiteindelijke reactiestatus is completed.
  4. De bronnen ondersteunen de beweringen die u wilt gebruiken.
  5. Belangrijke feiten worden bij voorkeur bevestigd door een officiële of primaire bron.

Beschouw een vloeiend antwoord niet als bewijs dat de zoekopdracht heeft plaatsgevonden. Controleer de gereedschapsuitvoer programmatisch.

Foutafhandeling en nieuwe pogingen

  • Bewaar niet-2xx-reactieteksten. Ze bevatten nuttige providervalidatieberichten.
  • Probeer HTTP niet opnieuw 400 zonder het afgewezen verzoek te wijzigen.
  • Tijdelijk opnieuw proberen 429, 502, 503, 504en transportstoringen met exponentiële vertraging en jitter.
  • Gebruik een idempotency-sleutel in uw eigen toepassing wanneer een nieuwe poging een zakelijke actie zou kunnen veroorzaken.
  • Houd applicatie-, PHP-, reverse-proxy- en gateway-time-outs op één lijn voor lange zoekverzoeken.
  • Stel API-sleutels of vastgelegde onbewerkte payloads nooit bloot aan eindgebruikers.

Gerelateerde pagina's