add tickets creation

This commit is contained in:
Max
2025-06-14 00:16:02 +03:00
parent 1aeb62d490
commit ca81e19d14
4 changed files with 123 additions and 27 deletions

View File

@@ -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);

View File

@@ -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<typeof this.schema>,
runManager?: CallbackManagerForToolRun,
parentConfig?: ToolRunnableConfig<Record<string, any>>
): Promise<string> {
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 'Произошла техническая ошибка при создании заявки. Пожалуйста, попробуйте позже.';
}
}
}

View File

@@ -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<SupportResponse> {
public async processMessage(userMessage: string, apartmentId?: string): Promise<SupportResponse> {
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
};

View File

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