مصطفی جعفرزاده
مصطفی جعفرزاده
خواندن ۹ دقیقه·۲ ماه پیش

چت‌بات مبتنی بر هوش مصنوعی با Node.js: پیاده‌سازی مکالمات شاخه‌دار و یادگیری تقویتی


مقدمه

در دنیای امروز، چتباتها به یکی از ابزارهای مهم برای تعاملات دیجیتالی و ارائه خدمات به کاربران تبدیل شدهاند. هوش مصنوعی و مدلهای زبانی مانند ChatGPT قابلیتهای بینظیری در ایجاد مکالمات طبیعی و تعاملی ارائه میدهند. با ترکیب این فناوریها با محیطهایی مانند Node.js و MongoDB، میتوان چتباتهایی ساخت که هم قدرتمند و هم مقیاسپذیر باشند.

یکی از چالشهای بزرگ در طراحی چتباتها، مدیریت مکالمات شاخهدار و پاسخگویی دقیق به نیازهای مختلف کاربران است. برای رفع این چالش، ابزارهایی مانند XState برای مدیریت وضعیت مکالمه و Opossum برای مدیریت خطاها و پیادهسازی Circuit Breaker استفاده میشوند. در این مقاله، ما به بررسی جزئیات ساخت یک چتبات هوشمند با بهرهگیری از تکنولوژیهای مدرن مانند ChatGPT، MongoDB و معماریهای مقیاسپذیر در Node.js میپردازیم.

هدف این پروژه ارائهی یک بستر چتبات پیشرفته است که با استفاده از مکالمات شاخهدار، مدیریت خطاهای API و یادگیری تقویتی میتواند تجربهای شخصیسازیشده و بهینه برای کاربران فراهم کند.

در ادامه، مفاهیم اصلی استفاده شده در این پروژه را توضیح داده و سپس به پیادهسازی بخشهای مختلف آن خواهیم پرداخت. این مقاله نسخهی اولیهی این پروژه را بررسی میکند و در نسخههای بعدی بهبودهای بیشتری اضافه خواهد شد.

*نکته مهم:در تهیه این مقاله از هوش مصنوعی کمک گرفته شده است

مفاهیم کلیدی

1. Circuit Breaker: یک الگوی طراحی برای جلوگیری از ارسال درخواستهای مکرر به یک سرویس در حال خطا. در این پروژه از Opossum برای پیادهسازی Circuit Breaker استفاده شده تا اطمینان حاصل شود که در صورت بروز خطا در ارتباط با API، درخواستهای بعدی بهطور موقت متوقف میشوند.

2. XState: کتابخانهای برای مدیریت ماشینهای حالت (State Machines) است که به ما امکان میدهد وضعیتهای مختلف مکالمه را بهصورت شاخهدار مدیریت کنیم.

3.مکالمات شاخهدار (Branching Conversations)

مکالمات شاخهدار یکی از روشهای مهم در طراحی سیستمهای مکالمهای است که هدف آن هدایت کاربر به پاسخهای متفاوت بر اساس ورودیهای او و مسیرهای مختلف مکالمه است. به زبان ساده، در مکالمات شاخهدار، مسیر مکالمه بهجای خطی بودن، به شکل درختی سازماندهی شده است؛ به این معنی که هر مرحله از مکالمه میتواند کاربر را به مسیرهای متفاوتی هدایت کند، دقیقاً مانند شاخههای یک درخت.

4. پیشپردازش و پسپردازش مکالمات: برای بهبود کیفیت مکالمات و مدیریت بهتر ورودی و خروجیها، از توابع پیشپردازش و پسپردازش استفاده میکنیم.

ساختار پروژه


project-root/
├── src/
│ ├── api/
│ │ ├── apiRoutes.js مسیرهای API
│ │
│ ├── chatGPT/
│ │ ├── gptClient.js ارتباط با ChatGPT API با Circuit Breaker و مدیریت خطا با Opossum
│ │
│ ├── conversation/
│ │ ├── conversationStateMachine.js مدیریت مکالمات شاخهدار با XState به صورت ماژولار
│ │ ├── conversationStateService.js ذخیره و بازیابی وضعیت مکالمه از دیتابیس
│ │
│ ├── feedback/
│ │ ├── feedbackService.js جمعآوری و تحلیل بازخوردهای کاربران
│ │
│ ├── learning/
│ │ ├── reinforcementLearning.js یادگیری تقویتی برای بهبود مکالمات
│ │
│ ├── responses/
│ │ ├── responseService.js سرویس مدیریت پاسخها و شاخههای مکالمات
│ │
│ ├── utils/
│ │ ├── conversationPreprocessor.js پیشپردازش و پسپردازش ورودیها و خروجیها
│ │
│ ├── database/
│ │ ├── models/feedbackModel.js مدل بازخوردها برای ذخیرهسازی
│ │ ├── models/conversationModel.js مدل ذخیرهسازی وضعیت مکالمه
│ │
├── config/
│ ├── mongoConfig.js تنظیمات اتصال به MongoDB
├── tests/ فایلهای تست واحد
│ ├── gptClient.test.js تستهای مربوط به gptClient.js
├── logs/ دایرکتوری لاگها
│ ├── error.log فایل لاگ خطاها
│ ├── combined.log فایل لاگهای عمومی
├── server.js استارت سرور
├── .env تنظیمات محیطی (API Keys, MongoDB credentials)
├── package.json فایل پکیجهای Node.js
└── .gitignore فایلهای نادیدهگیری گیت

1. پیکربندی اتصال به MongoDB

برای اتصال به MongoDB از کتابخانه Mongoose استفاده شده است. با استفاده از این پیکربندی، پروژه بهصورت امن و با مدیریت خطا به دیتابیس متصل میشود.


const mongoose = require('mongoose');
const connectToMongoDB = async () => {
try {
const connectionString = mongodb+srv://${process.env.DB_USER}:${process.env.DB_PASS}@${process.env.DB_HOST}/${process.env.DB_NAME}?retryWrites=true&w=majority;
await mongoose.connect(connectionString, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false
});
console.log('Connected to MongoDB successfully');
} catch (error) {
console.error('MongoDB connection error:', error);
process.exit(1);
}
};
module.exports = connectToMongoDB;

2. تنظیمات محیطی (.env)

تنظیمات اتصال به دیتابیس و API کلیدهای حساس در فایل .env ذخیره میشود. این فایل شامل اطلاعاتی مانند کاربر، رمز عبور، و نام دیتابیس است.


DB_USER=myDatabaseUser
DB_PASS=mySecurePassword
DB_HOST=cluster0.mongodb.net
DB_NAME=myDatabaseName
GPT_API_KEY=your-gpt-api-key
PORT=3000

3. فایل اصلی سرور (server.js)

فایل اصلی سرور با استفاده از Express.js مدیریت درخواستها را انجام میدهد و از winston برای لاگگیری و از rate-limit برای محدود کردن تعداد درخواستها استفاده میشود.


const express = require('express');
const rateLimit = require('express-rate-limit');
const winston = require('winston');
const connectToMongoDB = require('./config/mongoConfig');
const app = express();
// تنظیمات بهبود یافته لاگگیری
const logger = winston.createLogger({
level: 'warn', // فقط لاگهای سطح warn و بالاتر ثبت میشوند
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }), // فقط خطاها در error.log ذخیره میشوند
new winston.transports.File({ filename: 'logs/combined.log', level: 'warn' }) // لاگهای سطح warn و بالاتر در combined.log ذخیره میشوند
]
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple(),
level: 'warn' // در حالت توسعه، فقط لاگهای warn و بالاتر در کنسول نمایش داده میشوند
}));
}
// فقط لاگگیری خطاهای مهم و رویدادهای خاص
app.use((req, res, next) => {
res.on('finish', () => {
if (res.statusCode >= 400) {
logger.warn(Error: ${req.method} ${req.url} - ${res.statusCode});
}
});
next();
});
connectToMongoDB();
app.use(express.json());
const apiRoutes = require('./src/api/apiRoutes');
app.use('/api', apiRoutes);
// تنظیم محدودیت درخواستها
const limiter = rateLimit({
windowMs: 15 60 1000, // 15 دقیقه
max: 100, // حداکثر 100 درخواست در هر 15 دقیقه برای هر IP
message: 'Too many requests from this IP, please try again later.'
});
app.use(limiter);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
logger.info(Server is running on port ${PORT});
});

4. ارتباط با ChatGPT API با Opossum

در این بخش از Circuit Breaker برای مدیریت درخواستها به ChatGPT API استفاده شده است. Circuit Breaker برای جلوگیری از ارسال مکرر درخواستهای ناموفق طراحی شده است.


const axios = require('axios');
const opossum = require('opossum');
const { preprocessInput, postprocessResponse } = require('../utils/conversationPreprocessor');
// تابع برای درخواست به GPT API
const gptClientRequest = async (userId, userInput) => {
const cleanInput = preprocessInput(userInput);
const headers = { 'Authorization': Bearer ${process.env.GPT_API_KEY} };
const data = { prompt: cleanInput, model: 'gpt-4', max_tokens: 300 };
const response = await axios.post('https://api.openai.com/v1/completions', data, { headers });
return postprocessResponse(response.data.choices[0].text.trim());
};
// پیکربندی Circuit Breaker
const circuitBreakerOptions = {
timeout: 5000, // محدودیت زمانی
درخواستها (5 ثانیه)
errorThresholdPercentage: 50, // اگر 50 درصد درخواستها با خطا مواجه شدند، Circuit Breaker باز میشود
resetTimeout: 10000 // پس از 10 ثانیه تلاش میکند Circuit Breaker را دوباره ببندد
};
const gptClientCircuitBreaker = new opossum(gptClientRequest, circuitBreakerOptions);
// مدیریت رویدادهای Circuit Breaker برای لاگگیری و مانیتور کردن
gptClientCircuitBreaker.on('open', () => {
console.warn('Circuit Breaker is OPEN! Too many failures.');
});
gptClientCircuitBreaker.on('halfOpen', () => {
console.info('Circuit Breaker is HALF OPEN, trying to recover.');
});
gptClientCircuitBreaker.on('close', () => {
console.info('Circuit Breaker is CLOSED. Requests are allowed again.');
});
gptClientCircuitBreaker.on('fallback', (result) => {
console.warn('Fallback called:', result);
});
gptClientCircuitBreaker.on('reject', () => {
console.warn('Request rejected by Circuit Breaker.');
});
gptClientCircuitBreaker.fallback(() => 'The service is temporarily unavailable. Please try again later.');
// تابع نهایی برای استفاده در پروژه
const safeGptClient = async (userId, userInput) => {
try {
const result = await gptClientCircuitBreaker.fire(userId, userInput);
return result;
} catch (error) {
console.error('GPT Client Error:', error.message);
return 'Error occurred while processing your request. Please try again later.';
}
};
module.exports = safeGptClient;

5. سرویس پاسخ مکالمات شاخهدار

این سرویس برای مدیریت مکالمات پیچیده و شاخهدار با استفاده از XState و ChatGPT طراحی شده است.


const { conversationService, saveConversationState, resumeConversation } = require('../conversation/conversationStateService');
const gptClient = require('../chatGPT/gptClient');
const generateBranchingResponse = async (userId, userInput) => {
await resumeConversation(userId);
const currentState = conversationService.state.value;
if (currentState === 'options') {
if (userInput === '1') {
conversationService.send('SELECT_PRODUCT');
} else if (userInput === '2') {
conversationService.send('SELECT_SUPPORT');
} else {
return &quotInvalid option. Please try again.&quot
}
} else if (currentState === 'productInfo.describeIssue') {
const gptResponse = await gptClient(userId, Product Issue: ${userInput});
conversationService.send('SUBMIT_ISSUE');
await saveConversationState(userId, conversationService.state.value);
return gptResponse;
} else if (currentState === 'support.describeTechIssue') {
const gptResponse = await gptClient(userId, Technical Issue: ${userInput});
conversationService.send('SUBMIT_ISSUE');
await saveConversationState(userId, conversationService.state.value);
return gptResponse;
}
await saveConversationState(userId, conversationService.state.value);
return conversationService.state.meta.message;
};
module.exports = {
generateBranchingResponse
};

6. مسیرهای API

برای مدیریت مسیرهای API از Express استفاده شده است و از اعتبارسنجی ورودیها برای اطمینان از صحت دادههای کاربر بهره گرفته شده است.


const express = require('express');
const router = express.Router();
const { generateBranchingResponse } = require('../responses/responseService');
const { body, validationResult } = require('express-validator');
router.post('/support', [
body('query')
.isString()
.trim()
.escape()
.isLength({ min: 1, max: 500 })
.withMessage('Query must be between 1 and 500 characters long.'),
body('userId')
.isString()
.trim()
.escape()
.matches(/^[a-zA-Z0-9]{24}$/) // فرض بر این که userId یک ObjectId است
.withMessage('Invalid User ID format.'),
], async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { userId, query } = req.body;
try {
const responseMessage = await generateBranchingResponse(userId, query);
res.json({ response: responseMessage });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Something went wrong' });
}
});
module.exports = router;

7. تست واحد برای GPT Client

تستهای اولیه برای بررسی عملکرد و مدیریت خطاها در gptClient نوشته شده است.


const gptClient = require('../chatGPT/gptClient');
const axios = require('axios');
jest.mock('axios');
describe('GPT Client', () => {
it('should return processed GPT response on success', async () => {
axios.post.mockResolvedValue({
data: { choices: [{ text: 'Test response from GPT' }] }
});
const result = await gptClient('userId', 'Test input');
expect(result).toBe('Test response from GPT');
});
it('should handle API errors', async () => {
axios.post.mockRejectedValue({
response: { status: 500, data: { error: { message: 'Internal Server Error' } } }
});
const result = await gptClient('userId', 'Test input');
expect(result).toBe('API Error: Internal Server Error. Please try again later.');
});
it('should handle network errors', async () => {
axios.post.mockRejectedValue({ request: {} });
const result = await gptClient('userId', 'Test input');
expect(result).toBe('Network Error: Unable to connect to GPT API. Please check your connection.');
});
it('should handle internal errors', async () => {
axios.post.mockRejectedValue(new Error('Internal Error'));
const result = await gptClient('userId', 'Test input');
expect(result).toBe('Internal Error: Something went wrong while processing your request. Please try again later.');
});
});

نتیجهگیری

این پروژه با استفاده از تکنیکها و ابزارهای پیشرفته مانند Circuit Breaker، XState، و یادگیری تقویتی طراحی شده است. ساختار ماژولار پروژه، امکان توسعه و نگهداری بهتر را فراهم میکند. اگرچه این نسخه اولیه از پروژه است و نیاز به بهبودهایی در زمینه تستهای جامعتر و مدیریت کانکشنهای دیتابیس دارد، اما زیرساخت کلی پروژه برای مدیریت مکالمات هوشمند و تعاملات کاربر مناسب است.

این نسخه اولیه است و بهبودهای بیشتری در نسخههای آتی در نظر گرفته خواهد شد.

chatbotainodejsیادگیری تقویتیهوش مصنوعی
برنامه نویس علاقه مند به طراحی الگوریتم
شاید از این پست‌ها خوشتان بیاید