hi. i’m trying to implement function calling to Assistant API but i’m having problem calling the function since it’s on the client side. here’s my code example:
openAIconfig.js (server-side):
require('dotenv').config();
const OpenAI = require('openai');
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const rateLimit = require('express-rate-limit');
const winston = require('winston');
const app = express();
// Environment variables
const port = process.env.PORT || 3000;
const apiKey = process.env.API_KEY;
// OpenAI client setup
const openai = new OpenAI({ apiKey });
// Logger setup (using winston)
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
// Middleware for rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
});
// Express server setup
app.use(cors());
app.use(bodyParser.json());
app.use(limiter); // Apply the rate limiter to all requests
const path = require('path');
// Serve static files from the 'public' directory
app.use(express.static(path.join(__dirname, 'public')));
// Function to handle conversation with GPT
async function askGPT(question, threadId) {
try {
if (!threadId) {
const thread = await openai.beta.threads.create();
threadId = thread.id;
}
await openai.beta.threads.messages.create(threadId, {
role: "user",
content: question
});
const run = await openai.beta.threads.runs.create(threadId, { assistant_id: 'asst_123' });
let runStatus = await openai.beta.threads.runs.retrieve(threadId, run.id);
while (runStatus.status !== 'completed' && runStatus.status !== 'requires_action') {
console.log("Run is not yet completed, checking again in 2 seconds");
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait for 2 seconds
runStatus = await openai.beta.threads.runs.retrieve(threadId, run.id);
}
// Check if the run requires action
if (runStatus.status === 'requires_action') {
console.log("Requires Action");
const requiredActions = runStatus.required_action.submit_tool_outputs.tool_calls;
console.log(requiredActions);
// Handle the required actions
} else if (runStatus.status === 'completed') {
// Handle the completed run
const messagesResponse = await openai.beta.threads.messages.list(threadId);
const aiMessages = messagesResponse.data.filter(msg => msg.run_id === run.id && msg.role === "assistant");
return { response: aiMessages[aiMessages.length - 1].content[0].text.value, threadId };
}
} catch (error) {
logger.error('Error in askGPT:', error);
throw new Error('An error occurred while processing your request.');
}
}
// Endpoint to handle chat requests
app.post('/ask', async (req, res) => {
const { question, threadId } = req.body;
if (typeof question !== 'string' || question.trim() === '') {
return res.status(400).send('Invalid question');
}
try {
const { response, newThreadId } = await askGPT(question, threadId);
res.json({ answer: response, threadId: newThreadId });
} catch (error) {
logger.error('Error in /ask endpoint:', error);
res.status(500).send('Error processing your request');
}
});
// Start the server
app.listen(port, () => {
logger.info(`Server running on http://localhost:${port}`);
});
script.js (client-side):
document.getElementById('open-modal').addEventListener('click', function() {
document.getElementById('chat-modal').style.display = 'flex';
});
document.getElementById('close-modal').addEventListener('click', function() {
document.getElementById('chat-modal').style.display = 'none';
});
document.getElementById('send-button').addEventListener('click', sendQuestion);
document.getElementById('new-thread-button').addEventListener('click', newThread);
function startTolstoyWidget() {
window.tolstoyWidget.start();
}
function sendQuestion() {
const userInput = document.getElementById('user-input').value;
const chatOutput = document.getElementById('chat-output');
const loadingMessage = document.getElementById('loading-message');
if (userInput.trim() !== '') {
chatOutput.innerHTML += `<div class="user">${userInput}</div>`;
loadingMessage.style.display = 'block';
fetch('/ask', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ question: userInput }),
})
.then(response => response.json())
.then(data => {
loadingMessage.style.display = 'none';
const formattedResponse = formatResponse(data.answer);
chatOutput.innerHTML += `<div class="ai">${formattedResponse}</div>`;
chatOutput.scrollTop = chatOutput.scrollHeight;
})
.catch((error) => {
console.error('Error:', error);
loadingMessage.style.display = 'none';
});
document.getElementById('user-input').value = '';
}
}
function formatResponse(response) {
// Check for numbered lists
if (/^\d+\./m.test(response)) {
// Split by lines and filter out empty lines
const lines = response.split('\n').filter(line => line.trim() !== '');
// Convert lines into list items, checking for numbered list format
const listItems = lines.map((line) => {
if (/^\d+\./.test(line.trim())) {
return `<li>${line.trim().substring(line.indexOf(' ') + 1)}</li>`;
} else {
return line; // Return line as-is if it doesn't match the list format
}
}).join('');
// Wrap in <ol> tag if any list item is detected
if (listItems.includes('<li>')) {
return `<ol>${listItems}</ol>`;
}
}
// Check for bullet points
if (response.includes('\n- ')) {
const items = response.split('\n- ').slice(1); // Split and remove the first empty element
const listItems = items.map(item => `<li>${item.trim()}</li>`).join('');
return `<ul>${listItems}</ul>`;
}
// Return the response as-is if no list patterns are detected
return response;
}
function newThread() {
document.getElementById('chat-output').innerHTML = '';
fetch('/reset', { method: 'POST' })
.catch((error) => {
console.error('Error resetting conversation:', error);
});
}
so the function i’m trying to run is the startTolstoyWidget(), but right now i’m not sure how can fire it.
i already defined the same function in open AI playground. now when the run status becomes requires_action, the next step would be completing the run by submitting tool outputs to run. but after that i’m not sure what will be the next step now.
i can understand the documentation but only if the function is in the server side, but if it’s already in the client side, i can’t wrap my head around it. any ideas? thank you!