Odpowiedzi API web_search
Use hosted web search with PHP cURL and safely decode either JSON or SSE responses.
API odpowiedzi web_search
Skorzystaj z hosta web_search narzędzie, gdy odpowiedź zależy od aktualnych informacji publicznych: ostatnich wydań, aktualizacji dokumentacji, cen, harmonogramów, przepisów, zmian w produktach lub innych faktów, które mogły ulec zmianie po okresie szkolenia modelowego.
Ta strona opisuje web_search NA POST /v1/responses. Różni się od wersji samodzielnej `POST /v1/wyszukiwarka internetowa` punkt końcowy.
Zalecane żądanie
Wyślij kanoniczną tablicę wejściową interfejsu API odpowiedzi. Wymagaj narzędzia, gdy odpowiedź musi opierać się na żywych źródłach.
{
"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
}
Dlaczego te pola są ważne
toolsudostępnia modelowi wyszukiwanie hostowane.tool_choice: requiredzapobiega odpowiedzi opartej wyłącznie na pamięci, gdy weryfikacja na żywo jest obowiązkowa.include: ["web_search_call.action.sources"]prosi interfejs API o uwzględnienie adresów URL uwzględnionych w wywołaniu wyszukiwania.stream: falseżąda odpowiedzi innej niż przesyłana strumieniowo, ale kompatybilne bramy mogą nadal zwracać strumień zdarzeń Odpowiedzi. Twój klient musi obsługiwać oba formaty.
Nie dodawaj parametrów generowania specyficznych dla dostawcy, chyba że dokumenty wybranego modelu je obsługują. Dostawcy trasowani mogą odrzucić parametry takie jak temperature, backgroundlub kontrolki tokenów wyjściowych.
Kompletny przykład PHP cURL
Poniższy przykład wysyła żądanie, zachowuje treść błędu nadrzędnego i dekoduje normalną odpowiedź JSON lub odpowiedź dotyczącą zdarzeń wysłanych przez serwer.
<?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;
}
Ważny: stream: false może nadal zwrócić SSE
stream: false wyraża żądany tryb odpowiedzi. Zgodny serwer proxy lub przekierowany serwer nadrzędny może nadal serializować ukończony cykl życia odpowiedzi jako text/event-stream z takimi wydarzeniami jak:
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",...}}
Nie biegaj json_decode() bezpośrednio na całym ciele, dopóki nie ustalisz, czy jest to JSON, czy SSE. W przypadku SSE zbieranie zakończone response.output_item.done elementy i finał response.completed metadane.
Weryfikacja źródła
W przypadku przepływów pracy związanych z informacjami bieżącymi przed zaakceptowaniem odpowiedzi sprawdź wszystkie poniższe elementy:
- Przynajmniej jeden ukończony
web_search_callistnieje. - Co najmniej jeden adres URL istnieje w
web_search_call.action.sourcesluboutput_textCytat z adresu URL. - Ostateczny status odpowiedzi to
completed. - Źródła potwierdzają twierdzenia, z których zamierzasz skorzystać.
- Ważne fakty najlepiej potwierdzać z oficjalnego lub pierwotnego źródła.
Nie traktuj płynnej odpowiedzi jako dowodu, że wyszukiwanie się odbyło. Sprawdź programowo wyjście narzędzia.
Obsługa błędów i ponowne próby
- Zachowaj treści odpowiedzi inne niż 2xx. Zawierają przydatne komunikaty sprawdzające dostawcę.
- Nie próbuj ponownie używać protokołu HTTP
400bez zmiany odrzuconego żądania. - Ponów próbę przejściową
429,502,503,504oraz awarie transportu z wykładniczym cofaniem i jitterem. - Użyj klucza idempotentności we własnej aplikacji, gdy ponowna próba może wyzwolić akcję biznesową.
- Utrzymuj limity czasu aplikacji, PHP, odwrotnego proxy i bramy dostosowane do długich żądań wyszukiwania.
- Nigdy nie ujawniaj kluczy API ani przechwyconych nieprzetworzonych ładunków użytkownikom końcowym.