Respuestas API web_search
Utilice la búsqueda web alojada con PHP cURL y decodifique de forma segura respuestas JSON o SSE.
API de respuestas web_search
Utilice el alojado web_search herramienta cuando una respuesta depende de información pública actual: lanzamientos recientes, actualizaciones de documentación, precios, cronogramas, regulaciones, cambios de productos u otros hechos que puedan haber cambiado después del período de capacitación del modelo.
Esta página describe web_search en POST /v1/responses. Es diferente del independiente. `POST /v1/búsqueda web` punto final.
Solicitud recomendada
Envíe una matriz de entrada canónica de la API de Responses. Exija la herramienta cuando la respuesta deba basarse en fuentes en vivo.
{
"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 qué estos campos son importantes
toolshace que la búsqueda alojada esté disponible para el modelo.tool_choice: requiredevita una respuesta de solo memoria cuando la verificación en vivo es obligatoria.include: ["web_search_call.action.sources"]solicita a la API que incluya las URL consideradas por la llamada de búsqueda.stream: falsesolicita una respuesta que no sea de transmisión, pero las puertas de enlace compatibles aún pueden devolver una secuencia de eventos de Respuestas. Su cliente debe soportar ambos formatos.
No agregue parámetros de generación específicos del proveedor a menos que los documentos del modelo seleccionado los admitan. Los proveedores enrutados pueden rechazar parámetros como temperature, backgroundo controles de token de salida.
Ejemplo completo de PHP cURL
El siguiente ejemplo envía la solicitud, conserva el cuerpo del error ascendente y decodifica una respuesta JSON normal o una respuesta de eventos enviados por el 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 todavía puede regresar SSE
stream: false expresa el modo de respuesta solicitado. Un proxy compatible o enrutado ascendente aún puede serializar el ciclo de vida de Respuestas completo como text/event-stream con 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",...}}
no corras json_decode() directamente en todo el cuerpo hasta que haya determinado si es JSON o SSE. Para SSE, recogida completada response.output_item.done elementos y el final response.completed metadatos.
Validación de fuente
Para flujos de trabajo de información actual, valide todo lo siguiente antes de aceptar la respuesta:
- Al menos uno completado
web_search_callexiste. - Existe al menos una URL en
web_search_call.action.sourceso unoutput_textCitación de URL. - El estado de la respuesta final es
completed. - Las fuentes respaldan las afirmaciones que pretende utilizar.
- Los hechos importantes son preferiblemente confirmados por una fuente oficial o primaria.
No trate una respuesta fluida como prueba de que se realizó la búsqueda. Verifique la salida de la herramienta mediante programación.
Manejo de errores y reintentos
- Conservar cuerpos de respuesta que no sean 2xx. Contienen mensajes útiles de validación de proveedores.
- No reintentar HTTP
400sin cambiar la solicitud rechazada. - Reintentar transitorio
429,502,503,504y fallas de transporte con retroceso y fluctuación exponencial. - Utilice una clave de idempotencia en su propia aplicación cuando un reintento pueda desencadenar una acción empresarial.
- Mantenga alineados los tiempos de espera de aplicaciones, PHP, proxy inverso y puerta de enlace para solicitudes de búsqueda largas.
- Nunca exponga claves API ni cargas útiles sin procesar capturadas a los usuarios finales.