Responses API web_search
Използвайте хоствано уеб търсене с PHP cURL и безопасно декодирайте JSON или SSE отговори.
API за отговори web_search
Използвайте хостваните web_search инструмент, когато отговорът зависи от текущата публична информация: скорошни версии, актуализации на документация, цени, графици, разпоредби, промени в продукта или други факти, които може да са се променили след периода на обучение на модела.
Тази страница описва web_search на POST /v1/responses. Различава се от самостоятелния `POST /v1/уеб-търсене` крайна точка.
Препоръчителна заявка
Изпратете каноничен масив за въвеждане на API за отговори. Изисквайте инструмента, когато отговорът трябва да се основава на живи източници.
{
"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
}
Защо тези полета имат значение
toolsправи хостваното търсене достъпно за модела.tool_choice: requiredпредотвратява отговор само за памет, когато проверката на живо е задължителна.include: ["web_search_call.action.sources"]иска от API да включи URL адресите, разглеждани от извикването за търсене.stream: falseизисква отговор без поточно предаване, но съвместимите шлюзове все още могат да върнат поток от събития за отговори. Вашият клиент трябва да поддържа и двата формата.
Не добавяйте специфични за доставчика параметри за генериране, освен ако избраният модел документира поддръжка за тях. Маршрутизираните доставчици могат да отхвърлят параметри като temperature, background, или контроли за изходен токен.
Пълен пример за PHP cURL
Следващият пример изпраща заявката, запазва тялото на грешката нагоре и декодира нормален JSON отговор или отговор на изпратени от сървъра събития.
<?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;
}
Важно: stream: false все още може да върне SSE
stream: false изразява искания режим на отговор. Съвместим прокси или маршрутизиран нагоре по веригата все още може да сериализира завършения жизнен цикъл на отговорите като text/event-stream със събития като:
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",...}}
Не бягайте json_decode() директно върху цялото тяло, докато не определите дали е JSON или SSE. За SSE събирането е завършено response.output_item.done т. и финал response.completed метаданни.
Проверка на източника
За работни потоци с текуща информация проверете всичко от следното, преди да приемете отговора:
- Поне един завършен
web_search_callсъществува. - Съществува поне един URL адрес в
web_search_call.action.sourcesили анoutput_textURL цитиране. - Състоянието на крайния отговор е
completed. - Източниците подкрепят твърденията, които възнамерявате да използвате.
- Важните факти за предпочитане са потвърдени от официален или първичен източник.
Не приемайте свободния отговор като доказателство, че търсенето е извършено. Проверете изхода на инструмента програмно.
Обработка на грешки и повторни опити
- Запазване на не-2xx отговорни тела. Те съдържат полезни съобщения за проверка на доставчика.
- Не опитвайте отново HTTP
400без промяна на отхвърлената заявка. - Повторен опит за преходен процес
429,502,503,504и транспортни повреди с експоненциално забавяне и трептене. - Използвайте ключ за идемпотентност в собственото си приложение, когато повторен опит може да задейства бизнес действие.
- Поддържайте времето за изчакване на приложението, PHP, обратния прокси и шлюза в съответствие за дълги заявки за търсене.
- Никога не излагайте API ключове или уловени необработени полезни данни на крайните потребители.