add support ai-agent
This commit is contained in:
2355
package-lock.json
generated
2355
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,9 @@
|
|||||||
"homepage": "https://bitbucket.org/online-mentor/multi-stub#readme",
|
"homepage": "https://bitbucket.org/online-mentor/multi-stub#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@supabase/supabase-js": "^2.49.4",
|
"@supabase/supabase-js": "^2.49.4",
|
||||||
|
"@langchain/community": "^0.3.41",
|
||||||
|
"@langchain/core": "^0.3.46",
|
||||||
|
"@langchain/langgraph": "^0.2.65",
|
||||||
"ai": "^4.1.13",
|
"ai": "^4.1.13",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"bcrypt": "^5.1.0",
|
"bcrypt": "^5.1.0",
|
||||||
@@ -34,8 +37,10 @@
|
|||||||
"express": "5.0.1",
|
"express": "5.0.1",
|
||||||
"express-jwt": "^8.5.1",
|
"express-jwt": "^8.5.1",
|
||||||
"express-session": "^1.18.1",
|
"express-session": "^1.18.1",
|
||||||
|
"gigachat": "^0.0.14",
|
||||||
"jsdom": "^25.0.1",
|
"jsdom": "^25.0.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"langchain-gigachat": "^0.0.11",
|
||||||
"mongodb": "^6.12.0",
|
"mongodb": "^6.12.0",
|
||||||
"mongoose": "^8.9.2",
|
"mongoose": "^8.9.2",
|
||||||
"mongoose-sequence": "^6.0.1",
|
"mongoose-sequence": "^6.0.1",
|
||||||
@@ -44,7 +49,7 @@
|
|||||||
"pbkdf2-password": "^1.2.1",
|
"pbkdf2-password": "^1.2.1",
|
||||||
"rotating-file-stream": "^3.2.5",
|
"rotating-file-stream": "^3.2.5",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"uuid": "^11.0.3"
|
"zod": "^3.24.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.17.0",
|
"@eslint/js": "^9.17.0",
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
const fetch = require('node-fetch');
|
|
||||||
|
|
||||||
const getSupabaseUrl = async () => {
|
const getSupabaseUrl = async () => {
|
||||||
const response = await fetch('https://admin.bro-js.ru/api/config/v1/dev');
|
const response = await fetch('https://admin.bro-js.ru/api/config/v1/dev');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# AI Support Agent
|
||||||
|
|
||||||
|
AI-агент поддержки интегрирован в существующий `supportApi.js`.
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
```
|
||||||
|
support-ai-agent/
|
||||||
|
├── gigachat.ts # Конфигурация GigaChat
|
||||||
|
├── support-agent.ts # Основной класс агента
|
||||||
|
└── README.md # Документация
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### POST `/support`
|
||||||
|
Отправить сообщение в службу поддержки (теперь с AI-агентом).
|
||||||
|
|
||||||
|
**Запрос:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"user_id": "string", // Обязательно
|
||||||
|
"message": "string", // Обязательно
|
||||||
|
"system_prompt": "string" // Опционально - настройка поведения агента
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"reply": "Ответ AI-агента",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST `/support/configure`
|
||||||
|
Настроить системный промпт для конкретного пользователя.
|
||||||
|
|
||||||
|
### DELETE `/support/history/:userId`
|
||||||
|
Очистить историю диалога пользователя.
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
- 🤖 Интеллектуальные ответы на основе GigaChat
|
||||||
|
- 💾 Сохранение всех сообщений в базу данных Supabase
|
||||||
|
- 🧠 Память контекста диалога для каждого пользователя
|
||||||
|
- ⚙️ Настраиваемые системные промпты
|
||||||
|
- 📊 Поддержка множественных пользователей
|
||||||
|
|
||||||
|
## Примеры системных промптов
|
||||||
|
|
||||||
|
### Техническая поддержка
|
||||||
|
```
|
||||||
|
Ты - специалист технической поддержки мобильного приложения "Умный дом".
|
||||||
|
Помогай пользователям решать проблемы, объясняй функции простым языком,
|
||||||
|
проводи диагностику пошагово. Всегда будь дружелюбным и терпеливым.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Общая поддержка клиентов
|
||||||
|
```
|
||||||
|
Ты - профессиональный агент службы поддержки. Помогай решать вопросы
|
||||||
|
пользователей, отвечай вежливо и по существу, проявляй эмпатию.
|
||||||
|
```
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Agent } from 'node:https';
|
||||||
|
import { GigaChat } from 'langchain-gigachat';
|
||||||
|
|
||||||
|
const httpsAgent = new Agent({
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Получаем GIGA_AUTH из переменной окружения (устанавливается в get-constants.js)
|
||||||
|
export const gigachat = new GigaChat({
|
||||||
|
model: 'GigaChat-2',
|
||||||
|
temperature: 0.7,
|
||||||
|
scope: 'GIGACHAT_API_PERS',
|
||||||
|
streaming: false,
|
||||||
|
credentials: process.env.GIGA_AUTH,
|
||||||
|
httpsAgent
|
||||||
|
});
|
||||||
|
|
||||||
|
export default gigachat;
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import { HumanMessage, AIMessage, SystemMessage, BaseMessage } from '@langchain/core/messages';
|
||||||
|
import { ChatPromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts';
|
||||||
|
import { createReactAgent } from '@langchain/langgraph/prebuilt';
|
||||||
|
import { MemorySaver } from '@langchain/langgraph';
|
||||||
|
import gigachat from './gigachat';
|
||||||
|
|
||||||
|
export interface SupportAgentConfig {
|
||||||
|
systemPrompt?: string;
|
||||||
|
temperature?: number;
|
||||||
|
threadId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SupportResponse {
|
||||||
|
content: string;
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SupportAgent {
|
||||||
|
private llm: any;
|
||||||
|
private memorySaver: MemorySaver;
|
||||||
|
private agent: any;
|
||||||
|
private systemPrompt: string;
|
||||||
|
private threadId: string;
|
||||||
|
|
||||||
|
constructor(config: SupportAgentConfig = {}) {
|
||||||
|
this.systemPrompt = config.systemPrompt || this.getDefaultSystemPrompt();
|
||||||
|
this.threadId = config.threadId || 'default';
|
||||||
|
this.memorySaver = new MemorySaver();
|
||||||
|
|
||||||
|
// Настраиваем модель с заданной температурой
|
||||||
|
this.llm = gigachat;
|
||||||
|
if (config.temperature !== undefined) {
|
||||||
|
this.llm.temperature = config.temperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем агента без инструментов для простого чата
|
||||||
|
this.agent = createReactAgent({
|
||||||
|
llm: this.llm,
|
||||||
|
tools: [],
|
||||||
|
checkpointSaver: this.memorySaver
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить системный промпт по умолчанию для агента поддержки
|
||||||
|
*/
|
||||||
|
private getDefaultSystemPrompt(): string {
|
||||||
|
return `Ты - профессиональный агент службы поддержки.
|
||||||
|
|
||||||
|
Твои основные задачи:
|
||||||
|
- Помогать пользователям решать их вопросы и проблемы
|
||||||
|
- Отвечать вежливо, профессионально и по существу
|
||||||
|
- Предоставлять четкие и понятные инструкции
|
||||||
|
- Проявлять эмпатию к проблемам пользователей
|
||||||
|
- Если не знаешь ответ, честно сообщить об этом и предложить альтернативные способы получения помощи
|
||||||
|
|
||||||
|
Всегда отвечай на русском языке и старайся быть максимально полезным.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновить системный промпт
|
||||||
|
*/
|
||||||
|
public updateSystemPrompt(newPrompt: string): void {
|
||||||
|
this.systemPrompt = newPrompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить текущий системный промпт
|
||||||
|
*/
|
||||||
|
public getSystemPrompt(): string {
|
||||||
|
return this.systemPrompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обработать сообщение пользователя и получить ответ
|
||||||
|
*/
|
||||||
|
public async processMessage(userMessage: string): Promise<SupportResponse> {
|
||||||
|
try {
|
||||||
|
// Создаем сообщения с системным промптом
|
||||||
|
const messages = [
|
||||||
|
new SystemMessage(this.systemPrompt),
|
||||||
|
new HumanMessage(userMessage)
|
||||||
|
];
|
||||||
|
|
||||||
|
// Получаем ответ от агента
|
||||||
|
const response = await this.agent.invoke({
|
||||||
|
messages: messages
|
||||||
|
}, {
|
||||||
|
configurable: {
|
||||||
|
thread_id: this.threadId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Извлекаем последнее сообщение от ассистента
|
||||||
|
const lastMessage = response.messages[response.messages.length - 1];
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: lastMessage.content || 'Извините, не удалось сформировать ответ.',
|
||||||
|
success: true
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при обработке сообщения:', error);
|
||||||
|
return {
|
||||||
|
content: 'Извините, произошла ошибка при обработке вашего запроса. Попробуйте позже.',
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Неизвестная ошибка'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Очистить историю диалога
|
||||||
|
*/
|
||||||
|
public async clearHistory(): Promise<void> {
|
||||||
|
this.memorySaver = new MemorySaver();
|
||||||
|
this.agent = createReactAgent({
|
||||||
|
llm: this.llm,
|
||||||
|
tools: [],
|
||||||
|
checkpointSaver: this.memorySaver
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Изменить ID потока (для работы с разными пользователями)
|
||||||
|
*/
|
||||||
|
public setThreadId(threadId: string): void {
|
||||||
|
this.threadId = threadId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,151 @@
|
|||||||
const router = require('express').Router();
|
const router = require('express').Router();
|
||||||
const { getSupabaseClient } = require('./supabaseClient');
|
const { getSupabaseClient } = require('./supabaseClient');
|
||||||
|
const { SupportAgent } = require('./support-ai-agent/support-agent');
|
||||||
|
|
||||||
|
// Хранилище агентов для разных пользователей
|
||||||
|
const userAgents = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить или создать агента для пользователя
|
||||||
|
*/
|
||||||
|
function getUserAgent(userId, systemPrompt) {
|
||||||
|
if (!userAgents.has(userId)) {
|
||||||
|
const config = {
|
||||||
|
threadId: userId,
|
||||||
|
temperature: 0.7
|
||||||
|
};
|
||||||
|
if (systemPrompt) {
|
||||||
|
config.systemPrompt = systemPrompt;
|
||||||
|
}
|
||||||
|
userAgents.set(userId, new SupportAgent(config));
|
||||||
|
}
|
||||||
|
return userAgents.get(userId);
|
||||||
|
}
|
||||||
|
|
||||||
// POST /api/support
|
// POST /api/support
|
||||||
router.post('/support', async (req, res) => {
|
router.post('/support', async (req, res) => {
|
||||||
const supabase = getSupabaseClient();
|
const supabase = getSupabaseClient();
|
||||||
const { user_id, message } = req.body;
|
const { user_id, message, system_prompt } = req.body;
|
||||||
if (!user_id || !message) return res.status(400).json({ error: 'user_id и message обязательны' });
|
|
||||||
const { error } = await supabase
|
if (!user_id || !message) {
|
||||||
|
return res.status(400).json({ error: 'user_id и message обязательны' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Сохраняем сообщение пользователя в базу данных
|
||||||
|
const { error: insertError } = await supabase
|
||||||
.from('support')
|
.from('support')
|
||||||
.insert({ user_id, message, is_from_user: true });
|
.insert({ user_id, message, is_from_user: true });
|
||||||
if (error) return res.status(400).json({ error: error.message });
|
|
||||||
res.json({ reply: 'Спасибо за ваше сообщение! Служба поддержки свяжется с вами в ближайшее время.' });
|
if (insertError) {
|
||||||
|
return res.status(400).json({ error: insertError.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем агента для пользователя
|
||||||
|
const agent = getUserAgent(user_id, system_prompt);
|
||||||
|
|
||||||
|
// Обновляем системный промпт если передан
|
||||||
|
if (system_prompt) {
|
||||||
|
agent.updateSystemPrompt(system_prompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем ответ от AI-агента
|
||||||
|
const aiResponse = await agent.processMessage(message);
|
||||||
|
|
||||||
|
if (!aiResponse.success) {
|
||||||
|
console.error('Ошибка AI-агента:', aiResponse.error);
|
||||||
|
return res.status(500).json({
|
||||||
|
error: 'Ошибка при генерации ответа',
|
||||||
|
reply: 'Извините, произошла ошибка. Попробуйте позже.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем ответ агента в базу данных
|
||||||
|
const { error: responseError } = await supabase
|
||||||
|
.from('support')
|
||||||
|
.insert({
|
||||||
|
user_id,
|
||||||
|
message: aiResponse.content,
|
||||||
|
is_from_user: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (responseError) {
|
||||||
|
console.error('Ошибка сохранения ответа:', responseError);
|
||||||
|
// Не возвращаем ошибку пользователю, так как ответ уже сгенерирован
|
||||||
|
}
|
||||||
|
|
||||||
|
// Возвращаем ответ пользователю
|
||||||
|
res.json({
|
||||||
|
reply: aiResponse.content,
|
||||||
|
success: true
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка в supportApi:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Внутренняя ошибка сервера',
|
||||||
|
reply: 'Извините, произошла ошибка. Попробуйте позже.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST /api/support/configure - Настройка системного промпта
|
||||||
|
router.post('/support/configure', async (req, res) => {
|
||||||
|
const { user_id, system_prompt } = req.body;
|
||||||
|
|
||||||
|
if (!user_id) {
|
||||||
|
return res.status(400).json({ error: 'user_id обязателен' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const agent = getUserAgent(user_id, system_prompt);
|
||||||
|
|
||||||
|
if (system_prompt) {
|
||||||
|
agent.updateSystemPrompt(system_prompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
message: 'Конфигурация агента обновлена',
|
||||||
|
current_system_prompt: agent.getSystemPrompt(),
|
||||||
|
success: true
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка в /support/configure:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Внутренняя ошибка сервера',
|
||||||
|
success: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// DELETE /api/support/history/:userId - Очистка истории диалога
|
||||||
|
router.delete('/support/history/:userId', async (req, res) => {
|
||||||
|
const { userId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (userAgents.has(userId)) {
|
||||||
|
const agent = userAgents.get(userId);
|
||||||
|
await agent.clearHistory();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
message: 'История диалога очищена',
|
||||||
|
success: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.json({
|
||||||
|
message: 'Агент для данного пользователя не найден',
|
||||||
|
success: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка в /support/history:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Внутренняя ошибка сервера',
|
||||||
|
success: false
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
Reference in New Issue
Block a user