Reacties-API web_search
Gebruik gehoste internetzoekopdrachten met PHP cURL en decodeer veilig JSON- of SSE-reacties.
Reacties-API web_search
Gebruik de gehoste web_search tool wanneer een antwoord afhangt van actuele publieke informatie: recente releases, documentatie-updates, prijzen, schema's, regelgeving, productwijzigingen of andere feiten die mogelijk zijn veranderd na de modeltrainingsperiode.
Deze pagina beschrijft web_search op POST /v1/responses. Het is anders dan de stand-alone `POST /v1/web-search` eindpunt.
Aanbevolen verzoek
Verzend een canonieke Responses API-invoerarray. Vereis de tool wanneer het antwoord gebaseerd moet zijn op live bronnen.
{
"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
}
Waarom deze velden ertoe doen
toolsmaakt gehost zoeken beschikbaar voor het model.tool_choice: requiredvoorkomt een antwoord dat alleen uit het geheugen bestaat wanneer live verificatie verplicht is.include: ["web_search_call.action.sources"]vraagt de API om de URL's op te nemen die in aanmerking worden genomen bij de zoekopdracht.stream: falsevraagt om een niet-streamingantwoord, maar compatibele gateways kunnen nog steeds een responsgebeurtenisstroom retourneren. Uw klant moet beide formaten ondersteunen.
Voeg geen providerspecifieke generatieparameters toe, tenzij de geselecteerde modeldocumenten deze ondersteunen. Gerouteerde providers kunnen parameters zoals temperature, backgroundof uitvoertokenbesturingselementen.
Compleet PHP cURL-voorbeeld
In het volgende voorbeeld wordt de aanvraag verzonden, de upstream-fouttekst behouden en een normaal JSON-antwoord of een door de server verzonden gebeurtenissen-antwoord gedecodeerd.
<?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;
}
Belangrijk: stream: false kan nog steeds SSE retourneren
stream: false drukt de gevraagde antwoordmodus uit. Een compatibele proxy of stroomopwaarts gerouteerd kan de voltooide levenscyclus van reacties nog steeds serialiseren als text/event-stream met evenementen als:
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",...}}
Niet rennen json_decode() direct op de gehele body totdat je hebt vastgesteld of het JSON of SSE is. Voor SSE is het verzamelen voltooid response.output_item.done artikelen en de finale response.completed metagegevens.
Bronvalidatie
Voor workflows met actuele informatie valideert u al het volgende voordat u het antwoord accepteert:
- Minstens één voltooid
web_search_callbestaat. - Er bestaat ten minste één URL in
web_search_call.action.sourcesof eenoutput_textURL-citatie. - De uiteindelijke reactiestatus is
completed. - De bronnen ondersteunen de beweringen die u wilt gebruiken.
- Belangrijke feiten worden bij voorkeur bevestigd door een officiële of primaire bron.
Beschouw een vloeiend antwoord niet als bewijs dat de zoekopdracht heeft plaatsgevonden. Controleer de gereedschapsuitvoer programmatisch.
Foutafhandeling en nieuwe pogingen
- Bewaar niet-2xx-reactieteksten. Ze bevatten nuttige providervalidatieberichten.
- Probeer HTTP niet opnieuw
400zonder het afgewezen verzoek te wijzigen. - Tijdelijk opnieuw proberen
429,502,503,504en transportstoringen met exponentiële vertraging en jitter. - Gebruik een idempotency-sleutel in uw eigen toepassing wanneer een nieuwe poging een zakelijke actie zou kunnen veroorzaken.
- Houd applicatie-, PHP-, reverse-proxy- en gateway-time-outs op één lijn voor lange zoekverzoeken.
- Stel API-sleutels of vastgelegde onbewerkte payloads nooit bloot aan eindgebruikers.