User Tools

Site Tools


wiki:ai:azure_chatbot_with_open_ai_and_slack_integration
Approved 2025/09/26 17:46 by ddehamer (version: 1) | Approver: @ai-us-principals

Azure chatbot with open AI and slack integration

Complete all the steps in the documentation Azure chatbot with Open AI ( These steps are run in local )

npm install botbuilder-adapter-slack npm install @slack/web-api

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 - MODIFIED TO HANDLE SLACK URL VERIFICATION
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());
    
    // SLACK URL VERIFICATION HANDLER
    if (req.body && req.body.type === 'url_verification') {
        console.log('=== SLACK URL VERIFICATION ===');
        console.log('Challenge received:', req.body.challenge);
        
        // Respond with the challenge to verify the URL
        res.send(req.body.challenge);
        return;
    }
    
    // SLACK EVENT CALLBACK HANDLER
    if (req.body && req.body.type === 'event_callback') {
        console.log('=== SLACK EVENT RECEIVED ===');
        console.log('Event type:', req.body.event?.type);
        console.log('Event data:', JSON.stringify(req.body.event, null, 2));
        
        const event = req.body.event;
        
        // Handle message events
        if (event.type === 'message' && !event.bot_id) {
            try {
                console.log('Processing Slack message...');
                
                // Get user message
                const userMessage = event.text;
                const userId = event.user;
                const channel = event.channel;
                
                console.log(`Message from user ${userId}: ${userMessage}`);
                
                // Call your bot's logic using the new method
                const botResponse = await myBot.processSlackMessage(userMessage);
                
                // Send response back to Slack
                await sendSlackMessage(channel, botResponse);
                
                console.log('Slack message processed successfully');
            } catch (error) {
                console.error('Error processing Slack message:', error);
                // Send error response to Slack
                try {
                    await sendSlackMessage(event.channel, 'Sorry, I encountered an error processing your message.');
                } catch (sendError) {
                    console.error('Failed to send error message to Slack:', sendError);
                }
            }
        }
        
        // Acknowledge the event
        res.send('OK');
        return;
    }

    try {
        // Regular Bot Framework processing
        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,
            hasSlackToken: !!process.env.SLACK_BOT_TOKEN
        }
    });
    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');
    }
    
    if (!process.env.SLACK_BOT_TOKEN) {
        console.log('⚠️  WARNING: Slack bot token not set');
    } else {
        console.log('✅ Slack bot token is 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);
    });
});

// SLACK MESSAGE SENDER - UPDATED
async function sendSlackMessage(channel, message) {
    const { WebClient } = require('@slack/web-api');
    
    if (!process.env.SLACK_BOT_TOKEN) {
        console.error('SLACK_BOT_TOKEN not set in environment variables');
        return;
    }
    
    const slack = new WebClient(process.env.SLACK_BOT_TOKEN);
    
    try {
        console.log(`Sending message to Slack channel ${channel}: ${message}`);
        const result = await slack.chat.postMessage({
            channel: channel,
            text: message
        });
        console.log('Message sent successfully to Slack:', result.ts);
    } catch (error) {
        console.error('Error sending Slack message:', error);
    }
}

==== Bot.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 ===');
    }

    // NEW METHOD: Process Slack messages directly
    async processSlackMessage(userMessage) {
        console.log('=== PROCESSING SLACK MESSAGE ===');
        console.log('Message:', userMessage);

        try {
            // Test echo functionality
            if (userMessage && userMessage.toLowerCase().includes('test echo')) {
                return `Echo: ${userMessage}`;
            }

            console.log('Calling Azure OpenAI for Slack message...');
            
            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 for Slack');
            
            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 for Slack:', aiResponse);
            
            if (!aiResponse) {
                throw new Error('Empty response content from OpenAI');
            }

            return aiResponse;

        } catch (error) {
            console.error('=== SLACK MESSAGE ERROR ===');
            console.error('Error type:', error.constructor.name);
            console.error('Error message:', error.message);
            
            if (error.response) {
                console.error('HTTP Status:', error.response.status);
                console.error('Response data:', JSON.stringify(error.response.data, null, 2));
            }

            // Return appropriate error message
            if (error.message.includes('timeout')) {
                return 'The request timed out. Please try again with a shorter message.';
            } else if (error.message.includes('rate')) {
                return 'Service is currently busy. Please wait a moment and try again.';
            } else if (error.response?.status === 401) {
                return 'Authentication error. Please contact support.';
            } else if (error.response?.status === 404) {
                return 'Service configuration error. Please contact support.';
            } else {
                return 'Sorry, I encountered an error processing your request. Please try again.';
            }
        }
    }

    // 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;

Slack configuration

1. Create a Slack App

Go to api.slack.com/apps Click “Create New App” → “From scratch” Give your app a name and select your workspace Click “Create App”

2. Configure Bot Permissions

In your app settings, go to “OAuth & Permissions” Under “Scopes” → “Bot Token Scopes”, add these permissions:

chat:write (send messages) app_mentions:read (read mentions) channels:read (read channel info) im:read (read DMs) im:write (send DMs)

3. Install the App to Your Workspace

Still in “OAuth & Permissions”, click “Install to Workspace” Authorize the app Copy the “Bot User OAuth Token” (starts with xoxb-)

4. Enable Events

Go to “Event Subscriptions” and toggle it on You'll need a public URL for your app (use ngrok for local development) Set the Request URL to https://your-ngrok-url.com/slack/events Under “Subscribe to bot events”, add:

app_mention message.im

5. Update Your Azure Bot Configuration

In Azure Portal, go to your Bot Service Go to “Channels” and add “Slack” channel Enter your Slack app credentials:

Client ID (from Slack app “Basic Information”) Client Secret (from Slack app “Basic Information”) Verification Token (from Slack app “Basic Information”)

6. Update env variables

SLACK_BOT_TOKEN=xoxb-your-bot-token SLACK_CLIENT_SIGNING_SECRET=your-signing-secret SLACK_CLIENT_ID=your-client-id SLACK_CLIENT_SECRET=your-client-secret

7. Set the Request URL in Slack

Copy your ngrok URL (e.g., https://abc123.ngrok.io) In your Slack app “Event Subscriptions”, set Request URL to: https://abc123.ngrok.io/api/slack/messages Slack will verify the URL

8. OAuth Slack App Configuration Make sure your Slack app has all required settings: In your Slack App (api.slack.com):

Go to “OAuth & Permissions” Add redirect URL: https://slack.botframework.com Make sure you have a Bot User created (“App Home” → “Bot Users”) Install the app to your workspace first

9. Test the app by tagging the bot in the messages in the channel. You should be able to see the logs in the local as well

wiki/ai/azure_chatbot_with_open_ai_and_slack_integration.txt · Last modified: by ymurugesan