From ca81e19d14ab5064dcfab15d241dc9de6ba2d927 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 14 Jun 2025 00:16:02 +0300 Subject: [PATCH] add tickets creation --- .../kfu-m-24-1/sber_mobile/DB_Scheme.txt | 3 +- .../support-ai-agent/create-ticket-tool.ts | 66 +++++++++++++++++ .../support-ai-agent/support-agent.ts | 73 +++++++++++++------ .../kfu-m-24-1/sber_mobile/supportApi.js | 8 +- 4 files changed, 123 insertions(+), 27 deletions(-) create mode 100644 server/routers/kfu-m-24-1/sber_mobile/support-ai-agent/create-ticket-tool.ts diff --git a/server/routers/kfu-m-24-1/sber_mobile/DB_Scheme.txt b/server/routers/kfu-m-24-1/sber_mobile/DB_Scheme.txt index 0a7b25a..367f42a 100644 --- a/server/routers/kfu-m-24-1/sber_mobile/DB_Scheme.txt +++ b/server/routers/kfu-m-24-1/sber_mobile/DB_Scheme.txt @@ -170,7 +170,7 @@ CREATE TABLE payment_service_details ( CREATE TABLE tickets ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES auth.users(id), - building_id UUID NOT NULL REFERENCES buildings(id), + apartment_id UUID NOT NULL REFERENCES apartments(id), title TEXT NOT NULL, description TEXT NOT NULL, status TEXT NOT NULL CHECK (status IN ('open', 'in_progress', 'resolved')), @@ -197,6 +197,7 @@ CREATE INDEX idx_votes_initiative ON votes(initiative_id); CREATE INDEX idx_messages_chat ON messages(chat_id); CREATE INDEX idx_cameras_building ON cameras(building_id); CREATE INDEX idx_tickets_user ON tickets(user_id); +CREATE INDEX idx_tickets_apartment ON tickets(apartment_id); CREATE INDEX idx_apartments_building ON apartments(building_id); CREATE INDEX idx_apartment_residents_apartment ON apartment_residents(apartment_id); CREATE INDEX idx_apartment_residents_user ON apartment_residents(user_id); diff --git a/server/routers/kfu-m-24-1/sber_mobile/support-ai-agent/create-ticket-tool.ts b/server/routers/kfu-m-24-1/sber_mobile/support-ai-agent/create-ticket-tool.ts new file mode 100644 index 0000000..ff88787 --- /dev/null +++ b/server/routers/kfu-m-24-1/sber_mobile/support-ai-agent/create-ticket-tool.ts @@ -0,0 +1,66 @@ +import { StructuredTool, ToolRunnableConfig } from '@langchain/core/tools'; +import { z } from 'zod'; +import { CallbackManagerForToolRun } from '@langchain/core/callbacks/manager'; +import { getSupabaseClient } from '../supabaseClient'; + +export class CreateTicketTool extends StructuredTool { + name = 'create_ticket'; + description = 'Создает заявку в системе. ВАЖНО: используй этот инструмент ТОЛЬКО после получения явного согласия пользователя на создание заявки с конкретным текстом.'; + + schema = z.object({ + title: z.string().describe('Заголовок заявки'), + description: z.string().describe('Подробное описание проблемы'), + category: z.string().describe('Категория заявки (например: ремонт, уборка, техническая_поддержка, жалоба)'), + }); + + private userId: string; + private apartmentId: string; + + constructor(userId: string, apartmentId: string) { + super(); + this.userId = userId; + this.apartmentId = apartmentId; + } + + protected async _call( + arg: z.infer, + runManager?: CallbackManagerForToolRun, + parentConfig?: ToolRunnableConfig> + ): Promise { + try { + if (!this.apartmentId) { + return 'Не удалось определить вашу квартиру. Обратитесь к администратору для создания заявки.'; + } + + const supabase = getSupabaseClient(); + + const { data: ticket, error } = await supabase + .from('tickets') + .insert({ + user_id: this.userId, + apartment_id: this.apartmentId, + title: arg.title, + description: arg.description, + category: arg.category, + status: 'open' + }) + .select() + .single(); + + if (error) { + return 'Произошла ошибка при создании заявки. Попробуйте позже или обратитесь к администратору.'; + } + + return `Заявка успешно создана! +Номер заявки: ${ticket.id} +Заголовок: ${ticket.title} +Статус: Открыта +Дата создания: ${new Date(ticket.created_at).toLocaleString('ru-RU')} + +Ваша заявка принята в работу. Мы свяжемся с вами в ближайшее время.`; + + } catch (error) { + return 'Произошла техническая ошибка при создании заявки. Пожалуйста, попробуйте позже.'; + } + } +} \ No newline at end of file diff --git a/server/routers/kfu-m-24-1/sber_mobile/support-ai-agent/support-agent.ts b/server/routers/kfu-m-24-1/sber_mobile/support-ai-agent/support-agent.ts index 56578ec..93816e6 100644 --- a/server/routers/kfu-m-24-1/sber_mobile/support-ai-agent/support-agent.ts +++ b/server/routers/kfu-m-24-1/sber_mobile/support-ai-agent/support-agent.ts @@ -5,6 +5,7 @@ import { MemorySaver } from '@langchain/langgraph'; import gigachat from './gigachat'; import { SupportContextTool } from './support-context-tool'; import { KnowledgeBaseTool } from './knowledge-base-tool'; +import { CreateTicketTool } from './create-ticket-tool'; export interface SupportAgentConfig { temperature?: number; @@ -51,36 +52,52 @@ export class SupportAgent { } private getDefaultSystemPrompt(): string { - return `Ты - профессиональный агент службы поддержки. + return `Ты - профессиональный агент службы поддержки управляющей компании. -Твои основные задачи: -- Помогать пользователям решать их вопросы и проблемы -- Отвечать вежливо, профессионально и по существу -- Предоставлять четкие и понятные инструкции -- Проявлять эмпатию к проблемам пользователей -- Если не знаешь ответ, честно сообщить об этом и предложить альтернативные способы получения помощи +ОСНОВНЫЕ ПРИНЦИПЫ: +- Помогай только с реальными проблемами и вопросами, связанными с ЖКХ, управляющей компанией и приложением +- Будь вежливым, профессиональным и по существу +- Если вопрос неуместен, не связан с твоими обязанностями или является развлекательным - вежливо откажись и перенаправь к основным темам -У тебя есть доступ к двум инструментам: +ДОСТУПНЫЕ ИНСТРУМЕНТЫ: -1. get_support_context - получает историю предыдущих сообщений пользователя - ВСЕГДА используй этот инструмент ПЕРВЫМ ДЕЛОМ при получении каждого нового сообщения +1. get_support_context - получает историю сообщений пользователя + ВСЕГДА используй ПЕРВЫМ при каждом новом сообщении -2. search_knowledge_base - ищет информацию в базе знаний компании - Используй этот инструмент для вопросов о: - - Процессах оплаты и тарифах - - Подаче заявок и документооборота +2. search_knowledge_base - поиск в базе знаний компании + Используй ТОЛЬКО для серьезных вопросов о: + - Процессах оплаты ЖКХ и тарифах + - Подаче заявок и документообороте - Правилах и регламентах УК - Технических вопросах приложения - - Любых специфических вопросах о компании + - Процедурах и инструкциях компании -ВАЖНО: Сначала получи контекст, затем при необходимости найди информацию в базе знаний, и только после этого отвечай пользователю. +3. create_ticket - создание заявки в системе + Используй ТОЛЬКО когда: + - Пользователь сообщает о реальной проблеме (поломка, неисправность, жалоба) + - Проблема требует вмешательства УК или технических служб + - ОБЯЗАТЕЛЬНО сначала покажи пользователю полный текст заявки + - Получи ЯВНОЕ согласие пользователя перед созданием + - НЕ создавай заявки для консультационных вопросов -Если в истории есть предыдущие обращения, обязательно ссылайся на них в своем ответе. +ПРАВИЛА ИСПОЛЬЗОВАНИЯ ИНСТРУМЕНТОВ: +- НЕ используй search_knowledge_base и create_ticket для: + * Общих вопросов и болтовни + * Развлекательных запросов + * Вопросов не по теме ЖКХ/УК + * Простых консультаций, которые можно решить обычным ответом -Всегда отвечай на русском языке и старайся быть максимально полезным.`; +АЛГОРИТМ РАБОТЫ: +1. Получи контекст истории сообщений +2. Определи, является ли вопрос уместным и серьезным +3. Если нужна специфическая информация - найди в базе знаний +4. Если нужно создать заявку - покажи текст и получи согласие +5. Дай полный и полезный ответ + +Всегда отвечай на русском языке и фокусируйся на помощи с реальными проблемами ЖКХ.`; } - public async processMessage(userMessage: string): Promise { + public async processMessage(userMessage: string, apartmentId?: string): Promise { try { const messages: BaseMessage[] = []; @@ -91,7 +108,21 @@ export class SupportAgent { messages.push(new HumanMessage(userMessage)); - const response = await this.agent.invoke({ + // Создаем инструменты с актуальным apartmentId + const tools = [ + new SupportContextTool(this.userId), + new KnowledgeBaseTool(), + new CreateTicketTool(this.userId, apartmentId || '') + ]; + + // Пересоздаем агента с обновленными инструментами + const tempAgent = createReactAgent({ + llm: this.llm, + tools: tools, + checkpointSaver: this.memorySaver + }); + + const response = await tempAgent.invoke({ messages: messages }, { configurable: { @@ -102,7 +133,7 @@ export class SupportAgent { const lastMessage = response.messages[response.messages.length - 1]; return { - content: lastMessage.content || 'Извините, не удалось сформировать ответ.', + content: typeof lastMessage.content === 'string' ? lastMessage.content : 'Извините, не удалось сформировать ответ.', success: true }; diff --git a/server/routers/kfu-m-24-1/sber_mobile/supportApi.js b/server/routers/kfu-m-24-1/sber_mobile/supportApi.js index 39db5a7..1babe43 100644 --- a/server/routers/kfu-m-24-1/sber_mobile/supportApi.js +++ b/server/routers/kfu-m-24-1/sber_mobile/supportApi.js @@ -57,7 +57,7 @@ router.get('/support', async (req, res) => { // POST /api/support router.post('/support', async (req, res) => { const supabase = getSupabaseClient(); - const { user_id, message } = req.body; + const { user_id, message, apartment_id } = req.body; if (!user_id || !message) { return res.status(400).json({ error: 'user_id и message обязательны' }); @@ -76,8 +76,8 @@ router.post('/support', async (req, res) => { // Получаем агента для пользователя const agent = getUserAgent(user_id); - // Получаем ответ от AI-агента - const aiResponse = await agent.processMessage(message); + // Получаем ответ от AI-агента, передавая apartment_id + const aiResponse = await agent.processMessage(message, apartment_id); if (!aiResponse.success) { console.error('Ошибка AI-агента:', aiResponse.error); @@ -116,8 +116,6 @@ router.post('/support', async (req, res) => { } }); - - // DELETE /api/support/history/:userId - Очистка истории диалога router.delete('/support/history/:userId', async (req, res) => { const { userId } = req.params;