API de respostas web_search

Use pesquisa na web hospedada com PHP cURL e decodifique com segurança as respostas JSON ou SSE.

API de respostas web_search

Use o hospedado web_search ferramenta quando uma resposta depende de informações públicas atuais: lançamentos recentes, atualizações de documentação, preços, cronogramas, regulamentos, alterações de produtos ou outros fatos que possam ter mudado após o período de treinamento do modelo.

Esta página descreve web_search sobre POST /v1/responses. É diferente do autônomo `POST /v1/pesquisa na web` ponto final.

Solicitação recomendada

Envie uma matriz de entrada da API de respostas canônicas. Exija a ferramenta quando a resposta precisar ser baseada em fontes ativas.

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

Por que esses campos são importantes

  • tools disponibiliza a pesquisa hospedada para o modelo.
  • tool_choice: required evita uma resposta somente de memória quando a verificação ao vivo é obrigatória.
  • include: ["web_search_call.action.sources"] pede à API para incluir os URLs considerados pela chamada de pesquisa.
  • stream: false solicita uma resposta sem streaming, mas gateways compatíveis ainda podem retornar um fluxo de eventos Responses. Seu cliente deve suportar ambos os formatos.

Não adicione parâmetros de geração específicos do provedor, a menos que o modelo selecionado documente suporte para eles. Provedores roteados podem rejeitar parâmetros como temperature, backgroundou controles de token de saída.

Exemplo completo de PHP cURL

O exemplo a seguir envia a solicitação, preserva o corpo do erro upstream e decodifica uma resposta JSON normal ou uma resposta de eventos enviados pelo servidor.

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

Importante: stream: false ainda pode retornar SSE

stream: false expressa o modo de resposta solicitado. Um proxy compatível ou upstream roteado ainda pode serializar o ciclo de vida completo das Respostas como text/event-stream com eventos como:

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

Não corra json_decode() diretamente em todo o corpo até determinar se é JSON ou SSE. Para SSE, coleta concluída response.output_item.done itens e o final response.completed metadados.

Validação de origem

Para fluxos de trabalho com informações atuais, valide todos os itens a seguir antes de aceitar a resposta:

  1. Pelo menos um concluído web_search_call existe.
  2. Existe pelo menos um URL em web_search_call.action.sources ou um output_text Citação de URL.
  3. O status da resposta final é completed.
  4. As fontes apoiam as afirmações que você pretende usar.
  5. Fatos importantes são preferencialmente confirmados por fonte oficial ou primária.

Não trate uma resposta fluente como prova de que a pesquisa foi realizada. Verifique a saída da ferramenta programaticamente.

Tratamento de erros e novas tentativas

  • Preservar corpos de resposta não-2xx. Eles contêm mensagens úteis de validação do provedor.
  • Não tente HTTP novamente 400 sem alterar a solicitação rejeitada.
  • Tentar novamente transitório 429, 502, 503, 504e falhas de transporte com backoff e jitter exponenciais.
  • Use uma chave de idempotência em seu próprio aplicativo quando uma nova tentativa puder desencadear uma ação comercial.
  • Mantenha os tempos limite de aplicativos, PHP, proxy reverso e gateway alinhados para solicitações de pesquisa longas.
  • Nunca exponha chaves de API ou cargas brutas capturadas aos usuários finais.

Páginas relacionadas