I built a custom chat assistant into my app using php & cURL (Wordpress environment). Streaming works great in the prototype of a single file. However, when I try to implement streaming using a REST API to protect the keys I am beyond my depth of expertise (which is admittedly, very shallow). Every attempt fails with streaming (cannot keep network connection open) and hosting (SG) swears everything is configured to work.
Questions:
1. PHP REST API / STREAM EXAMPLE: Have you created or seen a php rest api that feeds assistant responses, using the streaming feature, to a front end chatbox?
2. AM I CRAZY: My limited knowledge suggests this is pretty much the only way to protect / hide keys from clients. Am I crazy? Is there a better solution that is more elegantly simple?
OVERVIEW, CODE & DETAILS
sendMessage
function sendMessage() {
const message = $('#vlp-ai2-input').val().trim();
if (!message) {
console.error('No message provided.');
return;
}
$('#vlp-ai2-input').val(''); // Clear the input box
const chatOutput = $('.vlp-ai2-chat-output');
chatOutput.append(`<p>You: ${message}</p>`);
const assistantMessage = $('<p>').addClass('assistant-temp').text('');
chatOutput.append(assistantMessage);
fetch('<*REST RELATIVE URL HERE*>', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message }),
custom_token: vlpAiData.custom_token, // Include the token in the request
})
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let assistantText = '';
function processChunk() {
reader.read().then(({ done, value }) => {
if (done) {
chatOutput.find('.assistant-temp').removeClass('assistant-temp');
return;
}
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n').filter((line) => line.trim());
lines.forEach((line) => {
if (line.startsWith('data:')) {
const data = line.slice(5).trim();
if (data === '[DONE]') return;
try {
const json = JSON.parse(data);
if (
json.object === 'thread.message.delta' &&
Array.isArray(json.delta?.content)
) {
json.delta.content.forEach((block) => {
if (block.type === 'text' && block.text?.value) {
assistantText += block.text.value;
chatOutput.find('.assistant-temp').text(assistantText);
}
});
}
} catch (e) {
console.error('Error parsing chunk:', e);
}
}
});
processChunk();
});
}
processChunk();
})
.catch((error) => {
console.error('Fetch error:', error);
});
}
PHP REST CODE
<?php
define('WP_USE_THEMES', false);
require_once($_SERVER['DOCUMENT_ROOT'] . '/wp-load.php');
// Kept getting errors on output before headers so forced a flush
while (ob_get_level()) {
if (ob_get_level() > 0) {
ob_flush();
}
flush();
}
header("Content-Type: application/json");
header("Transfer-Encoding: chunked");
header("Cache-Control: no-cache");
header("Access-Control-Allow-Origin: *");
$input = json_decode(file_get_contents('php://input'), true);
if (empty($input['message'])) {
echo json_encode(['error' => 'Message is required']);
exit;
}
// Prepare OpenAI API request
$api_key = '<*MY API KEY*>';
$assistant_id = '<*ASSISTANT ID*>';
$openai_request = [
'assistant_id' => $assistant_id,
'thread' => [
'messages' => [
['role' => 'user', 'content' => $input['message']],
],
],
'stream' => true,
];
// Make the OpenAI API call
$ch = curl_init('https://api.openai.com/v1/threads/runs');
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $api_key,
'Content-Type: application/json',
'OpenAI-Beta: assistants=v2',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($openai_request));
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($curl, $chunk) {
return strlen($chunk);
});
curl_exec($ch);
// Error Handling
if (curl_errno($ch)) {
echo json_encode(['error' => 'OpenAI API error: ' . curl_error($ch)]);
}
curl_close($ch);
Yes, I realize I may still be crazy, regardless of your answers, but alas… Thanks in advance, for any responses that permit me to retain some of my hair before I pull it all out…
Thanks all!