Responses API web_search
Använd värdbaserad webbsökning med PHP cURL och avkoda säkert antingen JSON- eller SSE-svar.
Responses API web_search
Använd den värd web_search verktyg när ett svar beror på aktuell offentlig information: senaste utgåvor, dokumentationsuppdateringar, priser, scheman, förordningar, produktändringar eller andra fakta som kan ha ändrats efter modellutbildningsperioden.
Denna sida beskriver web_search på POST /v1/responses. Den skiljer sig från den fristående `POST /v1/web-search` slutpunkt.
Rekommenderad begäran
Skicka en kanonisk Responses API-inmatningsmatris. Kräv verktyget när svaret måste vara jordat i livekällor.
{
"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
}
Varför dessa fält är viktiga
toolsgör värdsökning tillgänglig för modellen.tool_choice: requiredförhindrar ett svar endast i minnet när liveverifiering är obligatorisk.include: ["web_search_call.action.sources"]ber API:et att inkludera webbadresserna som beaktas av sökanropet.stream: falsebegär ett icke-strömmande svar, men kompatibla gateways kan fortfarande returnera en Responses-händelseström. Din klient måste stödja båda formaten.
Lägg inte till leverantörsspecifika genereringsparametrar om inte den valda modellen dokumenterar stöd för dem. Routerade leverantörer kan avvisa parametrar som t.ex temperature, background, eller kontroller för utdatatoken.
Komplett PHP cURL exempel
Följande exempel skickar begäran, bevarar uppströmsfelkroppen och avkodar antingen ett normalt JSON-svar eller ett Server-Sent Event-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;
}
Viktig: stream: false kan fortfarande returnera SSE
stream: false uttrycker det begärda svarsläget. En kompatibel proxy eller dirigerad uppströms kan fortfarande serialisera den avslutade Responses-livscykeln som text/event-stream med evenemang 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",...}}
Spring inte json_decode() direkt på hela kroppen tills du har bestämt om det är JSON eller SSE. För SSE, insamling genomförd response.output_item.done föremål och finalen response.completed metadata.
Källvalidering
För aktuella arbetsflöden, validera alla följande innan du accepterar svaret:
- Minst en avklarad
web_search_callfinns. - Minst en webbadress finns i
web_search_call.action.sourceseller enoutput_textURL-hänvisning. - Den slutliga svarsstatusen är
completed. - Källorna stödjer de påståenden du tänker använda.
- Viktiga fakta bekräftas helst av en officiell eller primär källa.
Behandla inte ett flytande svar som ett bevis på att sökningen pågick. Kontrollera verktygsutgången programmatiskt.
Felhantering och försök igen
- Bevara icke-2xx-svarskroppar. De innehåller användbara leverantörsvalideringsmeddelanden.
- Försök inte igen med HTTP
400utan att ändra den avvisade begäran. - Försök transient igen
429,502,503,504, och transportfel med exponentiell backoff och jitter. - Använd en idempotensnyckel i din egen applikation när ett nytt försök kan utlösa en affärsåtgärd.
- Håll tidsgränserna för program, PHP, omvänd proxy och gateway anpassade för långa sökförfrågningar.
- Utsätt aldrig API-nycklar eller fångade rånyttolaster för slutanvändare.