Responses API web_search
Brug hostet websøgning med PHP cURL og afkode sikkert enten JSON- eller SSE-svar.
Responses API web_search
Brug den hostede web_search værktøj, når et svar afhænger af aktuelle offentlige oplysninger: seneste udgivelser, dokumentationsopdateringer, priser, tidsplaner, regler, produktændringer eller andre fakta, der kan have ændret sig efter modeluddannelsesperioden.
Denne side beskriver web_search på POST /v1/responses. Den er anderledes end den selvstændige `POST /v1/web-søgning` endepunkt.
Anbefalet anmodning
Send et kanonisk Responses API input-array. Kræv værktøjet, når svaret skal baseres på levende kilder.
{
"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
}
Hvorfor disse felter betyder noget
toolsgør hostet søgning tilgængelig for modellen.tool_choice: requiredforhindrer et svar, der kun er i hukommelsen, når live verifikation er obligatorisk.include: ["web_search_call.action.sources"]anmoder API'et om at inkludere de URL'er, som søgeopkaldet overvejer.stream: falseanmoder om et ikke-streaming svar, men kompatible gateways kan stadig returnere en Responses hændelsesstrøm. Din klient skal understøtte begge formater.
Tilføj ikke udbyderspecifikke generationsparametre, medmindre den valgte model dokumenterer understøttelse af dem. Rutede udbydere kan afvise parametre som f.eks temperature, background, eller output-token kontroller.
Komplet PHP cURL eksempel
Følgende eksempel sender anmodningen, bevarer upstream-fejlteksten og afkoder enten et normalt JSON-svar eller et Server-Sent Events-svar.
<?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;
}
Vigtig: stream: false kan stadig returnere SSE
stream: false udtrykker den ønskede responstilstand. En kompatibel proxy eller dirigeret opstrøms kan stadig serialisere den afsluttede svar-livscyklus som text/event-stream med arrangementer som:
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",...}}
Løb ikke json_decode() direkte på hele kroppen, indtil du har fastslået, om det er JSON eller SSE. For SSE, indsamling afsluttet response.output_item.done genstande og finalen response.completed metadata.
Kildevalidering
For aktuelle informationsarbejdsgange skal du validere alle følgende, før du accepterer svaret:
- Mindst én fuldført
web_search_calleksisterer. - Der findes mindst én URL i
web_search_call.action.sourceseller enoutput_textURL-henvisning. - Den endelige svarstatus er
completed. - Kilderne understøtter de påstande, du agter at bruge.
- Vigtige fakta bekræftes helst af en officiel eller primær kilde.
Behandl ikke et flydende svar som bevis på, at søgningen kørte. Kontroller værktøjets output programmatisk.
Fejlhåndtering og genforsøg
- Bevar ikke-2xx respons organer. De indeholder nyttige udbydervalideringsmeddelelser.
- Forsøg ikke HTTP igen
400uden at ændre den afviste anmodning. - Prøv forbigående igen
429,502,503,504, og transportfejl med eksponentiel backoff og jitter. - Brug en idempotensnøgle i din egen applikation, når et genforsøg kan udløse en forretningshandling.
- Hold applikations-, PHP-, reverse-proxy- og gateway-timeouts på linje med lange søgeanmodninger.
- Udsæt aldrig API-nøgler eller indfangede rå nyttelaster for slutbrugere.