Hi, I created my assistant by feeding it JSON via the vector store, I gave it instructions and limited its scope of action.
The result is great on the playground, about 2 to 3 sec to answer.
I try to install it on my site, via a chatbot, it works but it takes 5 to 10 sec to answer me, which is very long.
I spent over 2 weeks trying dozens and dozens of scripts before coming back for help.
I can’t manage to stream the answer and make it appear as I go along, so I don’t have to wait 10sec.
Note that I don’t know compositor or node. I only work in “basic” php.
Thank you very much in advance, I’m close to my goal!
(I had to remove the urls to avoid being blocked by the forum)
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
header('X-Accel-Buffering: no');
require_once 'config.php';
function sendSSE($data) {
echo "data: " . json_encode($data) . "\n\n";
ob_flush();
flush();
}
if($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$input = json_decode(file_get_contents('php://input'), true);
$question = $input['question'] ?? '';
$curl = curl_init();
// Configuration de base de cURL
$headers = [
'Authorization: Bearer ' . OPENAI_API_KEY,
'Content-Type: application/json',
'OpenAI-Beta: assistants=v2'
];
// 1. Créer un thread
curl_setopt_array($curl, [
CURLOPT_URL => API V1 THREADS,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => '{}' // Corps vide mais nécessaire
]);
$threadResponse = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$threadData = json_decode($threadResponse, true);
if (!$threadData || !isset($threadData['id'])) {
throw new Exception('Erreur création thread: ' . $threadResponse);
}
$threadId = $threadData['id'];
// 2. Ajouter le message
curl_setopt_array($curl, [
CURLOPT_URL => API V1 THREADS /threads/{$threadId}/messages,
CURLOPT_POSTFIELDS => json_encode([
'role' => 'user',
'content' => $question
])
]);
$messageResponse = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
// 3. Lancer l'assistant
curl_setopt_array($curl, [
CURLOPT_URL => API V1 THREADS /threads/{$threadId}/runs,
CURLOPT_POSTFIELDS => json_encode([
'assistant_id' => ASSISTANT_ID
])
]);
$runResponse = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$runData = json_decode($runResponse, true);
if (!$runData || !isset($runData['id'])) {
throw new Exception('Erreur lancement assistant: ' . $runResponse);
}
$runId = $runData['id'];
// 4. Vérifier l'état
$attempts = 0;
$maxAttempts = 30; // Augmenté à 30 secondes
do {
curl_setopt_array($curl, [
CURLOPT_URL => API V1 THREAD $threadId}/runs/{$runId},
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_POSTFIELDS => null
]);
$statusResponse = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$statusData = json_decode($statusResponse, true);
if (!$statusData || !isset($statusData['status'])) {
throw new Exception('Réponse de status invalide');
}
if ($statusData['status'] === 'completed') {
// 5. Récupérer les messages
curl_setopt_array($curl, [
CURLOPT_URL => API V1 THREADS {$threadId}/messages,
CURLOPT_CUSTOMREQUEST => 'GET'
]);
$messagesResponse = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$messagesData = json_decode($messagesResponse, true);
if (isset($messagesData['data'][0]['content'][0]['text']['value'])) {
$response = $messagesData['data'][0]['content'][0]['text']['value'];
$words = explode(' ', $response);
foreach ($words as $word) {
sendSSE(['content' => $word . ' ']);
usleep(40000);
}
sendSSE(['done' => true]);
break;
}
throw new Exception('Format de réponse invalide');
} elseif ($statusData['status'] === 'failed') {
throw new Exception('Génération échouée: ' . ($statusData['last_error']['message'] ?? 'Raison inconnue'));
} elseif ($statusData['status'] === 'error') {
throw new Exception('Erreur pendant la génération: ' . ($statusData['last_error']['message'] ?? 'Raison inconnue'));
}
$attempts++;
if ($attempts >= $maxAttempts) {
throw new Exception('Timeout après 30 secondes');
}
usleep(500000); // 500ms entre chaque vérification
} while (true);
} catch (Exception $e) {
sendSSE(['error' => 'Erreur: ' . $e->getMessage()]);
} finally {
if (isset($curl)) {
curl_close($curl);
}
}
}
I’ve added a “typing” effect, but alas it does “typing”, only after receiving the whole response, so not very useful.
$(document).ready(function() {
let currentEventSource = null;
let currentBotMessage = null;
function appendMessage(message, isUser) {
const messageDiv = $('<div>').addClass('message-container ' + (isUser ? 'user-container' : 'bot-container'));
const messageContent = $('<div>')
.addClass('message ' + (isUser ? 'user-msg' : 'bot-msg'));
if (isUser) {
messageContent.text(message); // Pour les messages utilisateur, on garde text()
} else {
messageContent.html(message); // Pour le bot, on utilise html()
}
messageDiv.append(messageContent);
if (isUser) {
currentBotMessage = null;
$("#chatbox").append(messageDiv);
} else if (currentBotMessage === null) {
currentBotMessage = messageContent;
$("#chatbox").append(messageDiv);
}
$("#chatbox").scrollTop($("#chatbox")[0].scrollHeight);
}
function sendMessage() {
const question = $("#userInput").val().trim();
if (question === "") return;
// Désactiver l'entrée et le bouton
$("#userInput").val("").prop("disabled", true);
$("#sendBtn").prop("disabled", true);
$("#typingIndicator").show();
// Afficher le message de l'utilisateur
appendMessage(question, true);
// Fermer l'EventSource précédent s'il existe
if (currentEventSource) {
currentEventSource.close();
}
// Créer une nouvelle requête fetch avec streaming
fetch('chatbot.php', { // C'est ce fichier qui va gérer notre requête
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ question: question })
})
.then(response => {
console.log('Réponse reçue:', response);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
function processText({ done, value }) {
if (done) {
$("#typingIndicator").hide();
$("#userInput").prop("disabled", false);
$("#sendBtn").prop("disabled", false);
$("#userInput").focus();
return;
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
lines.forEach(line => {
if (line.startsWith('data: ')) {
try {
const data = JSON.parse(line.slice(5));
if (data.content) {
if (currentBotMessage) {
const currentText = currentBotMessage.html() || '';
currentBotMessage.html(currentText + data.content);
} else {
appendMessage(data.content, false);
}
$("#chatbox").scrollTop($("#chatbox")[0].scrollHeight);
}
} catch (e) {
console.error('Error parsing SSE data:', e);
}
}
});
return reader.read().then(processText);
}
return reader.read().then(processText);
})
.catch(error => {
console.error('Error:', error);
$("#typingIndicator").hide();
$("#userInput").prop("disabled", false);
$("#sendBtn").prop("disabled", false);
appendMessage("Désolé, une erreur s'est produite.", false);
});
}
$("#sendBtn").click(sendMessage);
$("#userInput").keypress(function(event) {
if (event.which === 13 && !$("#sendBtn").prop("disabled")) {
event.preventDefault();
sendMessage();
}
});
});