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)

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 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();