API de réponses web_search
Utilisez la recherche Web hébergée avec PHP cURL et décodez en toute sécurité les réponses JSON ou SSE.
API de réponses web_search
Utilisez l'hébergeur web_search outil lorsqu'une réponse dépend d'informations publiques actuelles : versions récentes, mises à jour de la documentation, prix, calendriers, réglementations, modifications de produits ou autres faits susceptibles d'avoir changé après la période de formation du modèle.
Cette page décrit web_search sur POST /v1/responses. C'est différent du autonome `POST /v1/web-search` point final.
Demande recommandée
Envoyez un tableau d’entrée canonique de l’API Responses. Exigez l’outil lorsque la réponse doit être fondée sur des sources en direct.
{
"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
}
Pourquoi ces champs sont importants
toolsrend la recherche hébergée disponible pour le modèle.tool_choice: requiredempêche une réponse en mémoire uniquement lorsque la vérification en direct est obligatoire.include: ["web_search_call.action.sources"]demande à l'API d'inclure les URL prises en compte par l'appel de recherche.stream: falsedemande une réponse sans streaming, mais les passerelles compatibles peuvent toujours renvoyer un flux d'événements Responses. Votre client doit prendre en charge les deux formats.
N'ajoutez pas de paramètres de génération spécifiques au fournisseur, sauf si les documents du modèle sélectionné les prennent en charge. Les fournisseurs routés peuvent rejeter des paramètres tels que temperature, background, ou des contrôles de jeton de sortie.
Exemple complet de cURL PHP
L'exemple suivant envoie la requête, conserve le corps de l'erreur en amont et décode soit une réponse JSON normale, soit une réponse d'événements envoyés par le serveur.
<?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;
}
Important: stream: false peut toujours retourner SSE
stream: false exprime le mode de réponse demandé. Un proxy compatible ou routé en amont peut toujours sérialiser le cycle de vie des réponses terminé comme text/event-stream avec des événements tels que :
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",...}}
Ne cours pas json_decode() directement sur l'ensemble du corps jusqu'à ce que vous ayez déterminé s'il s'agit de JSON ou de SSE. Pour l'ESS, collecte terminée response.output_item.done articles et la finale response.completed métadonnées.
Validation des sources
Pour les workflows d'informations actuelles, validez tous les éléments suivants avant d'accepter la réponse :
- Au moins un terminé
web_search_callexiste. - Au moins une URL existe dans
web_search_call.action.sourcesou unoutput_textCitation d'URL. - Le statut final de la réponse est
completed. - Les sources soutiennent les affirmations que vous avez l'intention d'utiliser.
- Les faits importants sont de préférence confirmés par une source officielle ou primaire.
Ne considérez pas une réponse fluide comme une preuve que la recherche a été effectuée. Vérifiez la sortie de l'outil par programme.
Gestion des erreurs et tentatives
- Préservez les corps de réponse non-2xx. Ils contiennent des messages de validation de fournisseur utiles.
- Ne réessayez pas HTTP
400sans modifier la demande rejetée. - Réessayer transitoire
429,502,503,504, et des échecs de transport avec un recul et une gigue exponentiels. - Utilisez une clé d'idempotence dans votre propre application lorsqu'une nouvelle tentative pourrait déclencher une action commerciale.
- Gardez les délais d'attente des applications, PHP, proxy inverse et passerelle alignés pour les longues requêtes de recherche.
- N’exposez jamais les clés API ou les charges utiles brutes capturées aux utilisateurs finaux.