АПИ за одговоре веб_сеарцх
Користите хостовану веб претрагу са ПХП цУРЛ и безбедно декодирајте ЈСОН или ССЕ одговоре.
Респонсес АПИ web_search
Користите хостовано web_search алат када одговор зависи од актуелних јавних информација: недавна издања, ажурирања документације, цене, распореди, прописи, промене производа или друге чињенице које су се можда промениле након периода обуке модела.
Ова страница описује web_search на POST /v1/responses. Разликује се од самосталног `ПОСТ /в1/веб-сеарцх` крајња тачка.
Препоручени захтев
Пошаљите канонски Респонсес АПИ улазни низ. Захтијевајте алат када одговор мора бити утемељен у живим изворима.
{
"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"]тражи од АПИ-ја да укључи УРЛ-ове које разматра позив претраге.stream: falseзахтева одговор који се не стримује, али компатибилни мрежни пролази могу и даље да врате ток догађаја Одговори. Ваш клијент мора да подржава оба формата.
Немојте додавати параметре генерисања специфичне за провајдера осим ако их одабрани модел не подржава. Рутирани провајдери могу одбити параметре као што су temperature, background, или контроле излазног токена.
Комплетан ПХП цУРЛ пример
Следећи пример шаље захтев, чува тело грешке узводно и декодира или нормалан ЈСОН одговор или одговор Сервер-посланих догађаја.
<?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 може и даље вратити ССЕ
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() директно на цело тело док не утврдите да ли је ЈСОН или ССЕ. За ССЕ, прикупљање је завршено response.output_item.done ставке и коначне response.completed метаподаци.
Потврда извора
За токове рада са актуелним информацијама, потврдите све од следећег пре него што прихватите одговор:
- Најмање један завршен
web_search_callпостоји. - Најмање једна УРЛ адреса постоји у
web_search_call.action.sourcesили анoutput_textУРЛ цитирање. - Коначни статус одговора је
completed. - Извори подржавају тврдње које намеравате да користите.
- Пожељно је да важне чињенице потврди званични или примарни извор.
Не третирајте течан одговор као доказ да је претрага успела. Програмски проверите излаз алата.
Руковање грешкама и поновни покушаји
- Сачувати тела одговора која нису 2кк. Они садрже корисне поруке о валидацији добављача.
- Не покушавајте поново ХТТП
400без промене одбијеног захтева. - Покушај поново пролазно
429,502,503,504и кварови у транспорту са експоненцијалним повлачењем и подрхтавањем. - Користите кључ идемпотенције у сопственој апликацији када би поновни покушај могао да покрене пословну радњу.
- Нека временска ограничења апликације, ПХП-а, обрнутог проксија и мрежног пролаза буду усклађена за дуге захтеве за претрагу.
- Никада не излажите АПИ кључеве или снимљене необрађене корисне податке крајњим корисницима.