Responses API web_search
Použijte hostované vyhledávání na webu s PHP cURL a bezpečně dekódujte odpovědi JSON nebo SSE.
Responses API web_search
Použijte hostovaný web_search nástroj, když odpověď závisí na aktuálních veřejných informacích: poslední verze, aktualizace dokumentace, ceny, plány, předpisy, změny produktu nebo jiné skutečnosti, které se mohly změnit po období školení modelu.
Tato stránka popisuje web_search na POST /v1/responses. Liší se od samostatného `POST /v1/web-search` koncový bod.
Doporučený požadavek
Odešlete vstupní pole kanonického rozhraní API odpovědí. Vyžadovat nástroj, když musí být odpověď uzemněna v živých zdrojích.
{
"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
}
Proč na těchto polích záleží
toolszpřístupňuje modelu hostované vyhledávání.tool_choice: requiredzabraňuje odpovědi pouze v paměti, když je živé ověření povinné.include: ["web_search_call.action.sources"]požádá rozhraní API, aby zahrnulo adresy URL zvažované vyhledávacím voláním.stream: falsepožaduje odpověď bez streamování, ale kompatibilní brány mohou stále vracet proud událostí Responses. Váš klient musí podporovat oba formáty.
Nepřidávejte parametry generování specifické pro poskytovatele, pokud je vybraný model nepodporuje. Směrovaní poskytovatelé mohou odmítnout parametry jako např temperature, backgroundnebo ovládací prvky výstupního tokenu.
Kompletní příklad cURL PHP
Následující příklad odešle požadavek, zachová tělo chyby proti proudu a dekóduje buď normální odpověď JSON, nebo odpověď událostí odeslaných serverem.
<?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;
}
Důležité: stream: false může stále vrátit SSE
stream: false vyjadřuje požadovaný režim odezvy. Kompatibilní proxy nebo směrovaný upstream může stále serializovat dokončený životní cyklus odpovědí jako text/event-stream s událostmi jako:
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",...}}
Neutíkej json_decode() přímo na celé tělo, dokud nezjistíte, zda se jedná o JSON nebo SSE. Pro SSE, sběr dokončen response.output_item.done položky a finále response.completed metadata.
Ověření zdroje
U pracovních postupů s aktuálními informacemi ověřte před přijetím odpovědi všechny následující:
- Alespoň jedna dokončena
web_search_callexistuje. - Existuje alespoň jedna adresa URL
web_search_call.action.sourcesnebo anoutput_textURL citace. - Konečný stav odpovědi je
completed. - Zdroje podporují tvrzení, která hodláte použít.
- Důležitá fakta jsou přednostně potvrzena oficiálním nebo primárním zdrojem.
Nepovažujte plynulou odpověď za důkaz, že vyhledávání proběhlo. Zkontrolujte výstup nástroje programově.
Zpracování chyb a opakování
- Zachovat těla odpovědí jiná než 2xx. Obsahují užitečné zprávy o ověření poskytovatele.
- Nezkoušejte HTTP znovu
400beze změny zamítnuté žádosti. - Opakujte přechodné
429,502,503,504a transportní poruchy s exponenciálním ustupováním a jitterem. - Použijte klíč idempotence ve své vlastní aplikaci, pokud by opakovaný pokus mohl spustit obchodní akci.
- Udržujte časové limity aplikací, PHP, reverzního proxy a brány zarovnané pro dlouhé požadavky na vyhledávání.
- Nikdy nevystavujte klíče API nebo zachycené nezpracované datové části koncovým uživatelům.