API de respostes web_search
Utilitzeu la cerca web allotjada amb PHP cURL i descodifiqueu de forma segura les respostes JSON o SSE.
API de respostes web_search
Utilitzeu l'allotjament web_search eina quan una resposta depèn de la informació pública actual: llançaments recents, actualitzacions de documentació, preus, horaris, regulacions, canvis de producte o altres fets que poden haver canviat després del període de formació del model.
Aquesta pàgina descriu web_search activat POST /v1/responses. És diferent de l'autònom `POST /v1/web-search` punt final.
Sol·licitud recomanada
Envieu una matriu d'entrada de l'API de Respostes canònica. Requereix l'eina quan la resposta s'ha de basar en fonts en directe.
{
"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
}
Per què són importants aquests camps
toolsposa a disposició del model la cerca allotjada.tool_choice: requiredimpedeix una resposta només de memòria quan la verificació en directe és obligatòria.include: ["web_search_call.action.sources"]demana a l'API que inclogui els URL considerats per la trucada de cerca.stream: falsesol·licita una resposta que no és transmissió, però les passarel·les compatibles encara poden retornar un flux d'esdeveniments de Respostes. El vostre client ha de suportar els dos formats.
No afegiu paràmetres de generació específics del proveïdor tret que els documents del model seleccionat els admetin. Els proveïdors encaminats poden rebutjar paràmetres com ara temperature, background, o controls de testimoni de sortida.
Exemple complet de PHP cURL
L'exemple següent envia la sol·licitud, conserva el cos de l'error amunt i descodifica una resposta JSON normal o una resposta d'esdeveniments enviats pel 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;
}
Important: stream: false encara pot tornar SSE
stream: false expressa el mode de resposta sol·licitat. Un servidor intermediari compatible o encaminat aigües amunt encara pot serialitzar el cicle de vida de Respostes completat com text/event-stream amb esdeveniments com:
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 córrer json_decode() directament a tot el cos fins que hàgiu determinat si és JSON o SSE. Per a SSE, recollida completada response.output_item.done elements i la final response.completed metadades.
Validació de la font
Per als fluxos de treball d'informació actual, valideu tot el següent abans d'acceptar la resposta:
- Almenys un completat
web_search_callexisteix. - Hi ha almenys un URL
web_search_call.action.sourceso unoutput_textCitació d'URL. - L'estat de resposta final és
completed. - Les fonts donen suport a les afirmacions que voleu utilitzar.
- Els fets importants es confirmen preferentment per una font oficial o primària.
No tracteu una resposta fluïda com una prova que la cerca s'ha fet. Comproveu la sortida de l'eina amb programació.
Gestió d'errors i reintents
- Preservar els cossos de resposta no 2xx. Contenen missatges útils de validació del proveïdor.
- No torneu a intentar HTTP
400sense modificar la sol·licitud rebutjada. - Torna-ho a provar transitori
429,502,503,504, i errors de transport amb retrocés exponencial i fluctuació. - Utilitzeu una clau d'idempotència a la vostra pròpia aplicació quan un nou intent podria desencadenar una acció empresarial.
- Mantingueu alineats els temps d'espera de l'aplicació, PHP, proxy invers i passarel·la per a sol·licituds de cerca llargues.
- No exposeu mai les claus de l'API ni les càrregues útils en brut capturades als usuaris finals.