In this lab we will be building an azure chat bot which is integrated with Open AI to answer the user's questions. This bot can be integrated to slack,teams etc. In this lab we will integrate it with slack for testing
This is the folder structure that needs to be created
azure-openai-bot/
├── bot.js ├── index.js ├── node_modules/ ├── package.json ├── package-lock.json
On the Azure side create the below
1. Azure Open AI service - Obtain the key, endpoint and the deployment name. 2. Create a new Azure bot from the bot service with multi tenant ( single tenant does not work but multi tenant is out of support after july end. Have to investigate what could be the workaround)
Env variables
AZURE_OPENAI_ENDPOINT= AZURE_OPENAI_KEY=2 AZURE_OPENAI_DEPLOYMENT_NAME= PORT=3978
Save above env variables as .env
To run in local
npm init -y npm install botbuilder restify dotenv openai npm install --save-dev nodemon npm install -g ngrok ngrok http 3978 -- To run ngrok
Once you got the ngrok url update it in configuration in the Azure Bot service
{
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}
}
const { ActivityHandler, MessageFactory } = require('botbuilder');
const OpenAI = require('openai');
class AzureOpenAIBot extends ActivityHandler {
constructor() {
super();
console.log('=== BOT INITIALIZATION ===');
console.log('Azure OpenAI Endpoint:', process.env.AZURE_OPENAI_ENDPOINT ? 'Set' : 'Not set');
console.log('Azure OpenAI Key:', process.env.AZURE_OPENAI_KEY ? 'Set (length: ' + process.env.AZURE_OPENAI_KEY.length + ')' : 'Not set');
console.log('Deployment Name:', process.env.AZURE_OPENAI_DEPLOYMENT_NAME || 'Not set');
// Initialize OpenAI client configured for Azure OpenAI
try {
this.openai = new OpenAI({
apiKey: process.env.AZURE_OPENAI_KEY,
baseURL: `${process.env.AZURE_OPENAI_ENDPOINT}/openai/deployments/${process.env.AZURE_OPENAI_DEPLOYMENT_NAME}`,
defaultQuery: { 'api-version': '2024-02-15-preview' },
defaultHeaders: {
'api-key': process.env.AZURE_OPENAI_KEY,
}
});
console.log('OpenAI client initialized successfully');
console.log('Base URL:', `${process.env.AZURE_OPENAI_ENDPOINT}/openai/deployments/${process.env.AZURE_OPENAI_DEPLOYMENT_NAME}`);
} catch (error) {
console.error('Failed to initialize OpenAI client:', error);
}
// Set system message for the bot's personality
this.systemMessage = `You are a helpful assistant for a company called TechCorp.
You help answer questions about our products, services, and general support.
Keep responses concise and professional. If you don't know something specific
about TechCorp, say so and offer to connect them with a human agent.`;
this.onMessage(async (context, next) => {
console.log('=== MESSAGE RECEIVED ===');
console.log('Timestamp:', new Date().toISOString());
console.log('User message:', context.activity.text);
console.log('Activity type:', context.activity.type);
console.log('Channel ID:', context.activity.channelId);
const userMessage = context.activity.text;
// First, try a simple echo to test basic functionality
if (userMessage && userMessage.toLowerCase().includes('test echo')) {
console.log('Echo test requested');
try {
await context.sendActivity(MessageFactory.text(`Echo: ${userMessage}`));
console.log('Echo response sent successfully');
} catch (echoError) {
console.error('Even simple echo failed:', echoError);
}
await next();
return;
}
try {
console.log('About to call Azure OpenAI...');
// Add timeout to the OpenAI call
const openaiCall = this.openai.chat.completions.create({
messages: [
{ role: "system", content: this.systemMessage },
{ role: "user", content: userMessage }
],
max_tokens: 150,
temperature: 0.7
});
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('OpenAI API timeout after 30 seconds')), 30000)
);
const response = await Promise.race([openaiCall, timeoutPromise]);
console.log('OpenAI response received');
console.log('Response choices length:', response.choices?.length || 0);
if (!response.choices || response.choices.length === 0) {
throw new Error('No choices returned from OpenAI API');
}
const aiResponse = response.choices[0].message.content;
console.log('AI response content:', aiResponse);
if (!aiResponse) {
throw new Error('Empty response content from OpenAI');
}
await context.sendActivity(MessageFactory.text(aiResponse));
console.log('Response sent to user successfully');
} catch (error) {
console.error('=== ERROR DETAILS ===');
console.error('Error type:', error.constructor.name);
console.error('Error message:', error.message);
console.error('Error stack:', error.stack);
// Check for specific Azure OpenAI errors
if (error.response) {
console.error('HTTP Status:', error.response.status);
console.error('Response data:', JSON.stringify(error.response.data, null, 2));
}
// Check for network/connection errors
if (error.code) {
console.error('Error code:', error.code);
}
// Always try to send an error response to the user
try {
let errorMessage = 'Sorry, I encountered an error processing your request. Please try again.';
// Provide more specific error messages for common issues
if (error.message.includes('timeout')) {
errorMessage = 'The request timed out. Please try again with a shorter message.';
} else if (error.message.includes('rate')) {
errorMessage = 'Service is currently busy. Please wait a moment and try again.';
} else if (error.response?.status === 401) {
errorMessage = 'Authentication error. Please contact support.';
} else if (error.response?.status === 404) {
errorMessage = 'Service configuration error. Please contact support.';
}
await context.sendActivity(MessageFactory.text(errorMessage));
console.log('Error message sent to user');
} catch (sendError) {
console.error('Failed to send error message to user:', sendError);
}
}
console.log('=== MESSAGE PROCESSING COMPLETE ===');
await next();
});
this.onMembersAdded(async (context, next) => {
console.log('=== MEMBER ADDED EVENT ===');
console.log('Members added count:', context.activity.membersAdded?.length || 0);
const welcomeText = 'Hello! I\'m your TechCorp assistant powered by Azure OpenAI. How can I help you today?';
try {
for (let cnt = 0; cnt < context.activity.membersAdded.length; ++cnt) {
if (context.activity.membersAdded[cnt].id !== context.activity.recipient.id) {
console.log('Sending welcome message to:', context.activity.membersAdded[cnt].id);
await context.sendActivity(MessageFactory.text(welcomeText));
}
}
console.log('Welcome messages sent successfully');
} catch (error) {
console.error('Error sending welcome message:', error);
}
await next();
});
// Add error handler for the activity handler
this.onTurnError = async (context, error) => {
console.error('=== TURN ERROR ===');
console.error('Turn error:', error);
console.error('Context activity:', JSON.stringify(context.activity, null, 2));
try {
await context.sendActivity(MessageFactory.text('Sorry, an unexpected error occurred.'));
} catch (sendError) {
console.error('Failed to send turn error message:', sendError);
}
};
console.log('=== BOT INITIALIZATION COMPLETE ===');
}
// Add a method to test OpenAI connection separately
async testOpenAIConnection() {
console.log('=== TESTING OPENAI CONNECTION ===');
try {
const response = await this.openai.chat.completions.create({
messages: [
{ role: "user", content: "Hello, this is a test message." }
],
max_tokens: 10,
temperature: 0.1
});
console.log('OpenAI test successful:', response.choices[0].message.content);
return true;
} catch (error) {
console.error('OpenAI test failed:');
console.error('Error:', error.message);
if (error.response) {
console.error('Status:', error.response.status);
console.error('Data:', error.response.data);
}
return false;
}
}
}
module.exports.AzureOpenAIBot = AzureOpenAIBot;
module.exports.AzureOpenAIBot = AzureOpenAIBot;
==== index.js ====
const path = require('path');
const restify = require('restify');
const { BotFrameworkAdapter } = require('botbuilder');
const { AzureOpenAIBot } = require('./bot');
// Load environment variables
const ENV_FILE = path.join(__dirname, '.env');
require('dotenv').config({ path: ENV_FILE });
console.log('=== ENVIRONMENT CHECK ===');
console.log('NODE_ENV:', process.env.NODE_ENV);
console.log('Dotenv file path:', ENV_FILE);
console.log('=== BOT FRAMEWORK CREDENTIALS ===');
console.log('MicrosoftAppId:', process.env.MicrosoftAppId || 'NOT SET');
console.log('MicrosoftAppPassword:', process.env.MicrosoftAppPassword ? 'SET (length: ' + process.env.MicrosoftAppPassword.length + ')' : 'NOT SET');
console.log('Port:', process.env.port || process.env.PORT || 3978);
// Create HTTP server
const server = restify.createServer();
server.use(restify.plugins.bodyParser());
console.log('=== CREATING BOT FRAMEWORK ADAPTER ===');
// Create adapter
const adapter = new BotFrameworkAdapter({
appId: process.env.MicrosoftAppId,
appPassword: process.env.MicrosoftAppPassword
});
console.log('Adapter created successfully');
// Error handler
adapter.onTurnError = async (context, error) => {
console.error('=== ADAPTER TURN ERROR ===');
console.error('Error message:', error.message);
console.error('Error stack:', error.stack);
try {
await context.sendTraceActivity(
'OnTurnError Trace',
`${error}`,
'https://www.botframework.com/schemas/error',
'TurnError'
);
await context.sendActivity('The bot encountered an error or bug.');
} catch (sendError) {
console.error('Failed to send error message:', sendError);
}
};
// Add this test function after creating the adapter
async function testBotAuthentication() {
console.log('=== TESTING BOT AUTHENTICATION ===');
const { MicrosoftAppCredentials } = require('botframework-connector');
try {
if (!process.env.MicrosoftAppId || !process.env.MicrosoftAppPassword) {
console.log('❌ Missing credentials');
return false;
}
console.log('Testing with App ID:', process.env.MicrosoftAppId.substring(0, 8) + '...');
console.log('Password length:', process.env.MicrosoftAppPassword.length);
const credentials = new MicrosoftAppCredentials(
process.env.MicrosoftAppId,
process.env.MicrosoftAppPassword
);
const token = await credentials.getToken();
console.log('✅ Authentication successful - token obtained');
console.log('Token starts with:', token.substring(0, 20) + '...');
return true;
} catch (error) {
console.log('❌ Authentication failed:');
console.log('Error message:', error.message);
console.log('Error code:', error.code);
console.log('Full error:', error);
return false;
}
}
// Call this when server starts (add this line after creating the bot)
testBotAuthentication();
// Create bot
console.log('=== CREATING BOT INSTANCE ===');
const myBot = new AzureOpenAIBot();
console.log('Bot instance created successfully');
// Listen for incoming requests
server.post('/api/messages', async (req, res) => {
console.log('=== INCOMING REQUEST ===');
console.log('Method:', req.method);
console.log('URL:', req.url);
console.log('Headers:', JSON.stringify(req.headers, null, 2));
console.log('Request timestamp:', new Date().toISOString());
try {
// Use processActivity instead of process
await adapter.processActivity(req, res, async (context) => {
console.log('Processing activity with bot...');
await myBot.run(context);
console.log('Bot processing completed');
});
} catch (error) {
console.error('=== REQUEST PROCESSING ERROR ===');
console.error('Error:', error.message);
console.error('Stack:', error.stack);
// Send error response if not already sent
if (!res.headersSent) {
res.status(500);
res.json({ error: 'Internal server error' });
}
}
});
// Test endpoint to verify server is running
server.get('/api/health', (req, res, next) => {
console.log('Health check requested');
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
environment: {
hasAppId: !!process.env.MicrosoftAppId,
hasAppPassword: !!process.env.MicrosoftAppPassword,
hasOpenAIKey: !!process.env.AZURE_OPENAI_KEY,
hasOpenAIEndpoint: !!process.env.AZURE_OPENAI_ENDPOINT
}
});
return next();
});
const port = process.env.PORT || 3978;
server.listen(port, () => {
console.log('=== SERVER STARTED ===');
console.log(`Bot started, listening on port ${port}`);
console.log(`Health check: http://localhost:${port}/api/health`);
console.log(`Messages endpoint: http://localhost:${port}/api/messages`);
console.log(`Using Azure OpenAI deployment: ${process.env.AZURE_OPENAI_DEPLOYMENT_NAME}`);
// Test if we can access environment variables
if (!process.env.MicrosoftAppId || !process.env.MicrosoftAppPassword) {
console.log('⚠️ WARNING: Bot Framework credentials not set - authentication will fail');
} else {
console.log('✅ Bot Framework credentials are set');
}
if (!process.env.AZURE_OPENAI_KEY || !process.env.AZURE_OPENAI_ENDPOINT) {
console.log('⚠️ WARNING: Azure OpenAI credentials not set');
} else {
console.log('✅ Azure OpenAI credentials are set');
}
});
// Handle server errors
server.on('error', (error) => {
console.error('=== SERVER ERROR ===');
console.error('Error:', error);
});
// Graceful shutdown
process.on('SIGINT', () => {
console.log('Shutting down gracefully...');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
To run in Azure
1. Create a webapp in azure ( without database) (Node 20 with windows)
2. Add all the env variables below in the azure webapp. Do not zip the env variables
3. Add the below web.config
<configuration>
<system.webServer>
<handlers>
<add name="iisnode" path="index.js" verb="*" modules="iisnode"/>
</handlers>
<rewrite>
<rules>
<rule name="Node.js" stopProcessing="true">
<match url=".*"/>
<action type="Rewrite" url="index.js"/>
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
4. Update the package.json
{
"name": "azure-openai-bot",
"version": "1.0.0",
"description": "Azure OpenAI Bot with Slack integration",
"main": "index.js",
"engines": {
"node": "22.x",
"npm": ">=9.0.0"
},
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"keywords": [
"azure",
"openai",
"bot",
"slack",
"chatbot"
],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"@azure/core-auth": "^1.9.0",
"@azure/openai": "^2.0.0",
"@slack/web-api": "^7.9.3",
"botbuilder": "^4.17.0-blobs1",
"dotenv": "^16.5.0",
"openai": "^5.7.0",
"restify": "^11.1.0"
},
"devDependencies": {
"nodemon": "^3.1.10"
}
}
5. Zip the contents
6. Deploy using CLI
az webapp deploy --resource-group xxx --name xx --src-path deployment123.zip --type zip
7. Verify the log stream for deployment logs to ensure deployment succeeds