1374 lines
35 KiB
Markdown
1374 lines
35 KiB
Markdown
|
|
# MVP Telegram-бота: юридический ИИ-консультант по законам РФ
|
|||
|
|
|
|||
|
|
Версия: 1.0
|
|||
|
|
География: только Российская Федерация
|
|||
|
|
Основная функция: юридическая консультация через RAG по базе законов РФ
|
|||
|
|
LLM-интеграция: OpenRouter или любой OpenAI-compatible API
|
|||
|
|
Язык интерфейса: русский
|
|||
|
|
Платформа: Telegram
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Что делаем в MVP
|
|||
|
|
|
|||
|
|
MVP — это Telegram-бот, который:
|
|||
|
|
|
|||
|
|
1. принимает юридический вопрос пользователя;
|
|||
|
|
2. уточняет категорию права и регион РФ;
|
|||
|
|
3. формирует поисковые запросы для RAG;
|
|||
|
|
4. ищет релевантные нормы в локальной базе законов;
|
|||
|
|
5. отвечает простым языком;
|
|||
|
|
6. показывает найденные источники;
|
|||
|
|
7. сохраняет консультацию в историю.
|
|||
|
|
|
|||
|
|
Главная ценность:
|
|||
|
|
|
|||
|
|
> Пользователь задает вопрос, бот находит применимые нормы закона РФ, объясняет их простыми словами и дает базовый план действий.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. Что НЕ входит в MVP
|
|||
|
|
|
|||
|
|
В MVP НЕ делаем:
|
|||
|
|
|
|||
|
|
- проверку PDF/DOCX-документов;
|
|||
|
|
- генерацию исков;
|
|||
|
|
- генерацию договоров;
|
|||
|
|
- генерацию претензий;
|
|||
|
|
- оплату и тарифы;
|
|||
|
|
- личный кабинет юриста;
|
|||
|
|
- передачу живому специалисту;
|
|||
|
|
- голосовые сообщения;
|
|||
|
|
- OCR;
|
|||
|
|
- сложную админ-панель;
|
|||
|
|
- судебную практику как обязательный источник;
|
|||
|
|
- автоматическое полное обновление всей правовой базы.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Главное меню Telegram-бота
|
|||
|
|
|
|||
|
|
Минимальное меню:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
⚖️ Задать вопрос
|
|||
|
|
📚 Мои консультации
|
|||
|
|
👤 Профиль
|
|||
|
|
ℹ️ Помощь
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Раздел «⚖️ Задать вопрос»
|
|||
|
|
|
|||
|
|
### 4.1. Категории MVP
|
|||
|
|
|
|||
|
|
После нажатия «Задать вопрос» бот показывает категории:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Выберите категорию вопроса:
|
|||
|
|
|
|||
|
|
💼 Работа
|
|||
|
|
🛒 Защита прав потребителей
|
|||
|
|
🏠 Жилье / аренда
|
|||
|
|
👪 Семья
|
|||
|
|
💰 Долги / займы
|
|||
|
|
📄 Договоры
|
|||
|
|
⚖️ Суд / процесс
|
|||
|
|
❓ Другое
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Для MVP не нужно покрывать все право. Достаточно основных бытовых категорий.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4.2. Базовый сценарий консультации
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
A[Пользователь нажал Задать вопрос] --> B[Выбор категории]
|
|||
|
|
B --> C[Бот просит описать ситуацию]
|
|||
|
|
C --> D[Пользователь пишет вопрос]
|
|||
|
|
D --> E[Бот уточняет регион РФ]
|
|||
|
|
E --> F[Пользователь указывает регион]
|
|||
|
|
F --> G[Создание консультации в БД]
|
|||
|
|
G --> H[LLM классифицирует вопрос]
|
|||
|
|
H --> I[LLM формирует RAG-запросы]
|
|||
|
|
I --> J[Hybrid search по базе законов]
|
|||
|
|
J --> K[Rerank найденных чанков]
|
|||
|
|
K --> L[LLM формирует ответ только по источникам]
|
|||
|
|
L --> M[Бот отправляет ответ]
|
|||
|
|
M --> N[Сохранение ответа и источников в БД]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4.3. Сообщения бота
|
|||
|
|
|
|||
|
|
#### Старт
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Здравствуйте. Я юридический ИИ-консультант по законам РФ.
|
|||
|
|
|
|||
|
|
Я могу помочь:
|
|||
|
|
— разобраться в ситуации;
|
|||
|
|
— найти применимые нормы закона;
|
|||
|
|
— объяснить их простыми словами;
|
|||
|
|
— дать базовый план действий.
|
|||
|
|
|
|||
|
|
Ответ носит информационный характер и не заменяет консультацию юриста.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Запрос описания
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Опишите ситуацию одним сообщением.
|
|||
|
|
|
|||
|
|
Постарайтесь указать:
|
|||
|
|
— что произошло;
|
|||
|
|
— когда произошло;
|
|||
|
|
— с кем спор;
|
|||
|
|
— чего вы хотите добиться.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Запрос региона
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Укажите регион РФ, где произошла ситуация.
|
|||
|
|
|
|||
|
|
Например:
|
|||
|
|
Москва
|
|||
|
|
Санкт-Петербург
|
|||
|
|
Краснодарский край
|
|||
|
|
Республика Татарстан
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Формат ответа бота
|
|||
|
|
|
|||
|
|
Бот должен отвечать в одном стабильном формате:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
⚖️ Краткий вывод
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
📌 Что говорит закон
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
✅ Что можно сделать
|
|||
|
|
1. ...
|
|||
|
|
2. ...
|
|||
|
|
3. ...
|
|||
|
|
|
|||
|
|
⚠️ Риски и ограничения
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
📚 Найденные источники
|
|||
|
|
1. ...
|
|||
|
|
2. ...
|
|||
|
|
3. ...
|
|||
|
|
|
|||
|
|
❗ Важно
|
|||
|
|
Я ИИ-консультант, а не адвокат. Ответ носит информационный характер. Для суда, крупных сумм, уголовных дел и сложных споров лучше обратиться к юристу.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. Главное правило RAG
|
|||
|
|
|
|||
|
|
Строгое правило:
|
|||
|
|
|
|||
|
|
> Если источник не найден в RAG — бот не имеет права ссылаться на него.
|
|||
|
|
|
|||
|
|
Нельзя писать:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Возможно, применяется статья 450 ГК РФ.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Нужно писать только так:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Найденные источники:
|
|||
|
|
— ГК РФ, статья 450 — основания изменения и расторжения договора
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Если источник не найден:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Я не нашел в базе надежную норму по этому вопросу. Могу дать только общий комментарий без ссылки на конкретную статью.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. Как RAG будет искать подходящие нормы
|
|||
|
|
|
|||
|
|
### 7.1. Входные данные
|
|||
|
|
|
|||
|
|
На вход RAG-пайплайна идут:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"user_question": "Работодатель не выплатил зарплату за последний месяц. Что делать?",
|
|||
|
|
"category": "labor",
|
|||
|
|
"region": "Москва",
|
|||
|
|
"jurisdiction": "RU",
|
|||
|
|
"user_type": "physical_person"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 7.2. Шаг 1 — классификация вопроса
|
|||
|
|
|
|||
|
|
LLM получает вопрос и возвращает структурированный JSON:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"legal_domain": "labor_law",
|
|||
|
|
"jurisdiction": "RU",
|
|||
|
|
"region": "Москва",
|
|||
|
|
"issue_type": "salary_delay",
|
|||
|
|
"needs_clarification": false,
|
|||
|
|
"search_intents": [
|
|||
|
|
"срок выплаты заработной платы",
|
|||
|
|
"ответственность работодателя за задержку зарплаты",
|
|||
|
|
"компенсация за задержку заработной платы",
|
|||
|
|
"жалоба в трудовую инспекцию"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Если данных мало:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"legal_domain": "consumer_protection",
|
|||
|
|
"needs_clarification": true,
|
|||
|
|
"questions": [
|
|||
|
|
"Товар был с недостатком или качественный?",
|
|||
|
|
"Сколько дней прошло с покупки?",
|
|||
|
|
"Покупка была онлайн или в магазине?"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
В этом случае бот сначала задает уточняющие вопросы, а не запускает финальный ответ.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 7.3. Шаг 2 — генерация поисковых запросов
|
|||
|
|
|
|||
|
|
LLM формирует 3–5 поисковых запросов:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"queries": [
|
|||
|
|
"ТК РФ срок выплаты заработной платы",
|
|||
|
|
"ТК РФ задержка заработной платы компенсация",
|
|||
|
|
"ответственность работодателя за невыплату зарплаты",
|
|||
|
|
"куда жаловаться если не выплатили зарплату"
|
|||
|
|
],
|
|||
|
|
"filters": {
|
|||
|
|
"jurisdiction": "RU",
|
|||
|
|
"law_type": ["labor"],
|
|||
|
|
"is_active": true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Важно: запросы генерирует LLM, но поиск выполняется не по интернету, а по локальной базе законов.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 7.4. Шаг 3 — hybrid search
|
|||
|
|
|
|||
|
|
Для юридического поиска нельзя полагаться только на embeddings.
|
|||
|
|
|
|||
|
|
Нужно использовать гибридный поиск:
|
|||
|
|
|
|||
|
|
1. `vector search` — поиск по смыслу;
|
|||
|
|
2. `full-text / BM25` — поиск по точным словам;
|
|||
|
|
3. `metadata filters` — фильтр по юрисдикции, отрасли права, актуальности;
|
|||
|
|
4. `reranker` — финальная сортировка найденных чанков.
|
|||
|
|
|
|||
|
|
Минимальная схема:
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart LR
|
|||
|
|
A[LLM search queries] --> B[Vector search]
|
|||
|
|
A --> C[Full-text search]
|
|||
|
|
B --> D[Merge results]
|
|||
|
|
C --> D
|
|||
|
|
D --> E[Metadata filters]
|
|||
|
|
E --> F[Reranker]
|
|||
|
|
F --> G[Top 3-7 chunks for answer]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 7.5. Шаг 4 — rerank
|
|||
|
|
|
|||
|
|
Поиск может вернуть 20–50 фрагментов. В ответ LLM нельзя отправлять всё.
|
|||
|
|
|
|||
|
|
Рекомендуемый режим MVP:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
vector_top_k = 20
|
|||
|
|
text_top_k = 20
|
|||
|
|
merged_top_k = 30
|
|||
|
|
rerank_top_k = 5
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
В финальный промпт LLM отправляем только 3–7 самых релевантных чанков.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 7.6. Шаг 5 — генерация ответа
|
|||
|
|
|
|||
|
|
LLM получает:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"user_question": "...",
|
|||
|
|
"category": "...",
|
|||
|
|
"region": "...",
|
|||
|
|
"retrieved_sources": [
|
|||
|
|
{
|
|||
|
|
"source_title": "Трудовой кодекс РФ",
|
|||
|
|
"article": "136",
|
|||
|
|
"article_title": "Порядок, место и сроки выплаты заработной платы",
|
|||
|
|
"text": "..."
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"source_title": "Трудовой кодекс РФ",
|
|||
|
|
"article": "236",
|
|||
|
|
"article_title": "Материальная ответственность работодателя за задержку выплаты заработной платы",
|
|||
|
|
"text": "..."
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Системное правило для LLM:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Отвечай только на основании retrieved_sources.
|
|||
|
|
Не придумывай статьи, номера законов и судебную практику.
|
|||
|
|
Если источников недостаточно, прямо скажи, что данных в базе недостаточно.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. Раздел «📚 Мои консультации»
|
|||
|
|
|
|||
|
|
Пользователь видит список своих консультаций:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
📚 Ваши консультации:
|
|||
|
|
|
|||
|
|
1. Невыплата зарплаты — 23.05.2026
|
|||
|
|
2. Возврат товара — 22.05.2026
|
|||
|
|
3. Аренда квартиры — 21.05.2026
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Кнопки по консультации:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
👁 Открыть
|
|||
|
|
🔁 Продолжить
|
|||
|
|
🗑 Удалить
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.1. Продолжить консультацию
|
|||
|
|
|
|||
|
|
Если пользователь нажал «Продолжить», новые сообщения добавляются к старой консультации.
|
|||
|
|
|
|||
|
|
В RAG отправляется:
|
|||
|
|
|
|||
|
|
1. новый вопрос;
|
|||
|
|
2. краткая история консультации;
|
|||
|
|
3. старые найденные источники;
|
|||
|
|
4. при необходимости — новый поиск.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 9. Раздел «👤 Профиль»
|
|||
|
|
|
|||
|
|
Минимальный профиль:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
👤 Профиль
|
|||
|
|
|
|||
|
|
Страна: Россия
|
|||
|
|
Регион: Москва
|
|||
|
|
Тип пользователя: Физлицо
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Кнопки:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
🌍 Изменить регион
|
|||
|
|
👔 Изменить тип пользователя
|
|||
|
|
🗑 Удалить мои данные
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Тип пользователя:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Физлицо
|
|||
|
|
ИП
|
|||
|
|
ООО
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Для MVP тип пользователя нужен только как контекст для LLM.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10. Раздел «ℹ️ Помощь»
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
ℹ️ Как пользоваться ботом
|
|||
|
|
|
|||
|
|
1. Нажмите «Задать вопрос».
|
|||
|
|
2. Выберите категорию.
|
|||
|
|
3. Опишите ситуацию.
|
|||
|
|
4. Укажите регион РФ.
|
|||
|
|
5. Получите ответ с найденными источниками.
|
|||
|
|
|
|||
|
|
Пример хорошего вопроса:
|
|||
|
|
«Работодатель не выплатил зарплату за апрель. Работаю официально, Москва. Что делать?»
|
|||
|
|
|
|||
|
|
Бот не заменяет юриста и не гарантирует результат спора.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 11. Архитектура MVP
|
|||
|
|
|
|||
|
|
### 11.1. Минимальная архитектура
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
TG[Telegram] --> BOT[bot-service aiogram]
|
|||
|
|
BOT --> DB[(PostgreSQL)]
|
|||
|
|
BOT --> RAG[RAG service]
|
|||
|
|
RAG --> VDB[(pgvector / Qdrant)]
|
|||
|
|
RAG --> DB
|
|||
|
|
RAG --> LLM[OpenRouter / OpenAI-compatible API]
|
|||
|
|
BOT --> LLM
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Для самого простого MVP можно сделать монолит:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
one Python app:
|
|||
|
|
— aiogram bot
|
|||
|
|
— FastAPI optional
|
|||
|
|
— RAG logic
|
|||
|
|
— PostgreSQL access
|
|||
|
|
— OpenAI-compatible client
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 11.2. Рекомендуемый стек
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Python 3.12+
|
|||
|
|
aiogram 3.x
|
|||
|
|
SQLAlchemy 2.x
|
|||
|
|
Alembic
|
|||
|
|
PostgreSQL 16+
|
|||
|
|
pgvector или Qdrant
|
|||
|
|
Redis optional
|
|||
|
|
OpenAI Python SDK
|
|||
|
|
OpenRouter или OpenAI-compatible LLM
|
|||
|
|
Docker Compose
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 12. Хранилище
|
|||
|
|
|
|||
|
|
### 12.1. Таблицы MVP
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
users
|
|||
|
|
consultations
|
|||
|
|
messages
|
|||
|
|
law_sources
|
|||
|
|
law_chunks
|
|||
|
|
rag_queries
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 12.2. users
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE users (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
telegram_id BIGINT UNIQUE NOT NULL,
|
|||
|
|
username TEXT,
|
|||
|
|
first_name TEXT,
|
|||
|
|
country TEXT NOT NULL DEFAULT 'Россия',
|
|||
|
|
region TEXT,
|
|||
|
|
user_type TEXT NOT NULL DEFAULT 'physical_person',
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`user_type`:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
physical_person
|
|||
|
|
individual_entrepreneur
|
|||
|
|
company
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 12.3. consultations
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE consultations (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|||
|
|
category TEXT NOT NULL,
|
|||
|
|
title TEXT,
|
|||
|
|
region TEXT,
|
|||
|
|
status TEXT NOT NULL DEFAULT 'active',
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 12.4. messages
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE messages (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
consultation_id BIGINT NOT NULL REFERENCES consultations(id) ON DELETE CASCADE,
|
|||
|
|
role TEXT NOT NULL,
|
|||
|
|
content TEXT NOT NULL,
|
|||
|
|
sources_json JSONB,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`role`:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
user
|
|||
|
|
assistant
|
|||
|
|
system
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 12.5. law_sources
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE law_sources (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
title TEXT NOT NULL,
|
|||
|
|
source_type TEXT NOT NULL,
|
|||
|
|
jurisdiction TEXT NOT NULL DEFAULT 'RU',
|
|||
|
|
law_type TEXT,
|
|||
|
|
document_number TEXT,
|
|||
|
|
adoption_date DATE,
|
|||
|
|
publication_date DATE,
|
|||
|
|
effective_date DATE,
|
|||
|
|
source_url TEXT,
|
|||
|
|
official_publication_number TEXT,
|
|||
|
|
version_hash TEXT,
|
|||
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|||
|
|
loaded_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Примеры `law_type`:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
civil
|
|||
|
|
labor
|
|||
|
|
consumer
|
|||
|
|
family
|
|||
|
|
housing
|
|||
|
|
tax
|
|||
|
|
procedural
|
|||
|
|
administrative
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 12.6. law_chunks
|
|||
|
|
|
|||
|
|
Для pgvector:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE EXTENSION IF NOT EXISTS vector;
|
|||
|
|
|
|||
|
|
CREATE TABLE law_chunks (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
source_id BIGINT NOT NULL REFERENCES law_sources(id) ON DELETE CASCADE,
|
|||
|
|
chunk_index INT NOT NULL,
|
|||
|
|
article_number TEXT,
|
|||
|
|
article_title TEXT,
|
|||
|
|
chunk_text TEXT NOT NULL,
|
|||
|
|
metadata JSONB,
|
|||
|
|
embedding vector(1536),
|
|||
|
|
tsv tsvector,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Индексы:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE INDEX law_chunks_source_id_idx ON law_chunks(source_id);
|
|||
|
|
CREATE INDEX law_chunks_article_number_idx ON law_chunks(article_number);
|
|||
|
|
CREATE INDEX law_chunks_tsv_idx ON law_chunks USING GIN(tsv);
|
|||
|
|
CREATE INDEX law_chunks_embedding_idx ON law_chunks USING ivfflat (embedding vector_cosine_ops);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Размерность `embedding vector(1536)` нужно заменить под выбранную embedding-модель.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 12.7. rag_queries
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE rag_queries (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
consultation_id BIGINT REFERENCES consultations(id) ON DELETE CASCADE,
|
|||
|
|
user_message_id BIGINT REFERENCES messages(id) ON DELETE CASCADE,
|
|||
|
|
generated_queries JSONB NOT NULL,
|
|||
|
|
retrieved_chunks JSONB NOT NULL,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Нужно для отладки качества RAG.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 13. LLM-интеграция OpenRouter / OpenAI-compatible
|
|||
|
|
|
|||
|
|
OpenRouter поддерживает OpenAI-compatible API-стиль. Поэтому в коде лучше сразу писать через `openai` Python SDK с настраиваемыми:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
OPENAI_BASE_URL
|
|||
|
|
OPENAI_API_KEY
|
|||
|
|
LLM_MODEL
|
|||
|
|
EMBEDDING_MODEL
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 13.1. Пример клиента
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
from openai import OpenAI
|
|||
|
|
import os
|
|||
|
|
|
|||
|
|
client = OpenAI(
|
|||
|
|
api_key=os.getenv("OPENAI_API_KEY"),
|
|||
|
|
base_url=os.getenv("OPENAI_BASE_URL", "https://openrouter.ai/api/v1"),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
response = client.chat.completions.create(
|
|||
|
|
model=os.getenv("LLM_MODEL", "openai/gpt-4.1-mini"),
|
|||
|
|
messages=[
|
|||
|
|
{"role": "system", "content": "Ты юридический ИИ-консультант по законам РФ."},
|
|||
|
|
{"role": "user", "content": "Работодатель не выплатил зарплату. Что делать?"}
|
|||
|
|
],
|
|||
|
|
temperature=0.2,
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Для OpenAI напрямую:
|
|||
|
|
|
|||
|
|
```env
|
|||
|
|
OPENAI_BASE_URL=https://api.openai.com/v1
|
|||
|
|
OPENAI_API_KEY=...
|
|||
|
|
LLM_MODEL=gpt-4.1-mini
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Для OpenRouter:
|
|||
|
|
|
|||
|
|
```env
|
|||
|
|
OPENAI_BASE_URL=https://openrouter.ai/api/v1
|
|||
|
|
OPENAI_API_KEY=...
|
|||
|
|
LLM_MODEL=openai/gpt-4.1-mini
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 14. Structured output для классификации
|
|||
|
|
|
|||
|
|
Классификацию лучше получать строго в JSON.
|
|||
|
|
|
|||
|
|
### 14.1. Prompt: классификатор
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Ты классификатор юридических вопросов по законам РФ.
|
|||
|
|
|
|||
|
|
Верни только JSON без markdown.
|
|||
|
|
|
|||
|
|
Поля:
|
|||
|
|
- legal_domain
|
|||
|
|
- issue_type
|
|||
|
|
- jurisdiction
|
|||
|
|
- region
|
|||
|
|
- needs_clarification
|
|||
|
|
- clarification_questions
|
|||
|
|
- search_queries
|
|||
|
|
- filters
|
|||
|
|
|
|||
|
|
Правила:
|
|||
|
|
1. jurisdiction всегда RU.
|
|||
|
|
2. Если данных недостаточно, needs_clarification = true.
|
|||
|
|
3. search_queries должны быть пригодны для поиска по базе законов.
|
|||
|
|
4. Не придумывай статьи.
|
|||
|
|
5. Не давай юридический ответ на этом этапе.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 14.2. Пример результата
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"legal_domain": "labor",
|
|||
|
|
"issue_type": "salary_delay",
|
|||
|
|
"jurisdiction": "RU",
|
|||
|
|
"region": "Москва",
|
|||
|
|
"needs_clarification": false,
|
|||
|
|
"clarification_questions": [],
|
|||
|
|
"search_queries": [
|
|||
|
|
"ТК РФ сроки выплаты заработной платы",
|
|||
|
|
"ТК РФ задержка заработной платы компенсация",
|
|||
|
|
"ответственность работодателя за задержку зарплаты"
|
|||
|
|
],
|
|||
|
|
"filters": {
|
|||
|
|
"law_type": ["labor"],
|
|||
|
|
"jurisdiction": "RU",
|
|||
|
|
"is_active": true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 15. Prompt для финального ответа
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Ты юридический ИИ-консультант по законам РФ.
|
|||
|
|
|
|||
|
|
Твоя задача — ответить пользователю простым языком на основании найденных источников.
|
|||
|
|
|
|||
|
|
Жесткие правила:
|
|||
|
|
1. Используй только SOURCES.
|
|||
|
|
2. Не придумывай статьи, номера законов, судебную практику и сроки.
|
|||
|
|
3. Если источников недостаточно, прямо скажи об этом.
|
|||
|
|
4. Не обещай победу в суде.
|
|||
|
|
5. Не выдавай себя за адвоката.
|
|||
|
|
6. Не помогай обходить закон.
|
|||
|
|
7. В конце добавь дисклеймер.
|
|||
|
|
|
|||
|
|
Формат ответа:
|
|||
|
|
|
|||
|
|
⚖️ Краткий вывод
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
📌 Что говорит закон
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
✅ Что можно сделать
|
|||
|
|
1. ...
|
|||
|
|
2. ...
|
|||
|
|
3. ...
|
|||
|
|
|
|||
|
|
⚠️ Риски и ограничения
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
📚 Найденные источники
|
|||
|
|
1. [Название, статья, краткое описание]
|
|||
|
|
2. ...
|
|||
|
|
|
|||
|
|
❗ Важно
|
|||
|
|
Ответ носит информационный характер и не заменяет консультацию юриста.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 16. Источники законов РФ для базы
|
|||
|
|
|
|||
|
|
### 16.1. Основной официальный источник
|
|||
|
|
|
|||
|
|
Основной источник для MVP:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Официальный интернет-портал правовой информации
|
|||
|
|
https://pravo.gov.ru/
|
|||
|
|
https://publication.pravo.gov.ru/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Что важно:
|
|||
|
|
|
|||
|
|
- это официальный контур опубликования правовых актов РФ;
|
|||
|
|
- на `publication.pravo.gov.ru` есть раздел официального опубликования;
|
|||
|
|
- есть раздел открытых данных;
|
|||
|
|
- есть API-интерфейс;
|
|||
|
|
- есть поиск по документам.
|
|||
|
|
|
|||
|
|
Практически для MVP:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
1. Использовать publication.pravo.gov.ru как первичный источник новых опубликованных актов.
|
|||
|
|
2. Забрать нужные кодексы и федеральные законы.
|
|||
|
|
3. Сохранять source_url, дату публикации, номер документа, hash версии.
|
|||
|
|
4. Не пытаться на первом этапе спарсить всё право РФ.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Полезные URL:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
https://publication.pravo.gov.ru/
|
|||
|
|
https://publication.pravo.gov.ru/OpenData
|
|||
|
|
https://publication.pravo.gov.ru/help
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 16.2. Дополнительный официальный источник
|
|||
|
|
|
|||
|
|
Дополнительный источник:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Портал Минюста России «Нормативные правовые акты в Российской Федерации»
|
|||
|
|
https://pravo.minjust.ru/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Что полезно:
|
|||
|
|
|
|||
|
|
- федеральное законодательство;
|
|||
|
|
- законодательство субъектов РФ;
|
|||
|
|
- муниципальные акты;
|
|||
|
|
- текущие и предыдущие редакции НПА.
|
|||
|
|
|
|||
|
|
Полезные URL:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
https://pravo.minjust.ru/
|
|||
|
|
https://pravo.minjust.ru/about_project
|
|||
|
|
https://pravo.minjust.ru/about_project/setting_portal
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 16.3. Что парсить для MVP
|
|||
|
|
|
|||
|
|
Не нужно сразу загружать все законы.
|
|||
|
|
|
|||
|
|
Для MVP достаточно:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
1. Конституция РФ
|
|||
|
|
2. Гражданский кодекс РФ
|
|||
|
|
3. Трудовой кодекс РФ
|
|||
|
|
4. Семейный кодекс РФ
|
|||
|
|
5. Жилищный кодекс РФ
|
|||
|
|
6. Закон РФ «О защите прав потребителей»
|
|||
|
|
7. Гражданский процессуальный кодекс РФ — базовые статьи
|
|||
|
|
8. КоАП РФ — только базовые бытовые составы, если нужен административный блок
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Для категорий MVP этого достаточно:
|
|||
|
|
|
|||
|
|
| Категория в боте | Источники |
|
|||
|
|
|---|---|
|
|||
|
|
| Работа | ТК РФ |
|
|||
|
|
| Потребители | Закон о защите прав потребителей, ГК РФ |
|
|||
|
|
| Жилье / аренда | ГК РФ, ЖК РФ |
|
|||
|
|
| Семья | СК РФ |
|
|||
|
|
| Долги / займы | ГК РФ |
|
|||
|
|
| Договоры | ГК РФ |
|
|||
|
|
| Суд / процесс | ГПК РФ |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 16.4. Почему не стоит «парсить вообще всё» в MVP
|
|||
|
|
|
|||
|
|
Полная база законодательства РФ — это сложно, потому что:
|
|||
|
|
|
|||
|
|
- много редакций документов;
|
|||
|
|
- есть федеральный, региональный и муниципальный уровни;
|
|||
|
|
- есть акты, утратившие силу;
|
|||
|
|
- у документов есть изменения и редакции;
|
|||
|
|
- важна дата актуальности;
|
|||
|
|
- часть пользовательских вопросов требует не только закон, но и практику применения.
|
|||
|
|
|
|||
|
|
Для MVP правильнее сделать узкую, но качественную базу:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Кодексы + ключевые федеральные законы + метаданные + актуальность + хороший поиск.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 17. Ingestion pipeline законов
|
|||
|
|
|
|||
|
|
### 17.1. Схема загрузки
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
A[Источник: publication.pravo.gov.ru / pravo.minjust.ru] --> B[Fetcher]
|
|||
|
|
B --> C[Raw storage]
|
|||
|
|
C --> D[Text extractor / HTML parser]
|
|||
|
|
D --> E[Normalizer]
|
|||
|
|
E --> F[Split by articles]
|
|||
|
|
F --> G[Chunker]
|
|||
|
|
G --> H[Embeddings]
|
|||
|
|
H --> I[(Vector DB / pgvector)]
|
|||
|
|
E --> J[(PostgreSQL law_sources)]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 17.2. Этапы
|
|||
|
|
|
|||
|
|
#### 1. Fetcher
|
|||
|
|
|
|||
|
|
Задача:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
— скачать HTML/PDF/текст документа;
|
|||
|
|
— сохранить оригинальный файл;
|
|||
|
|
— сохранить URL;
|
|||
|
|
— сохранить дату загрузки;
|
|||
|
|
— сохранить hash.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. Raw storage
|
|||
|
|
|
|||
|
|
Сохранять оригиналы обязательно:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
/data/raw_laws/
|
|||
|
|
pravo/
|
|||
|
|
2026-05-23/
|
|||
|
|
document_0001.html
|
|||
|
|
document_0001.json
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3. Normalizer
|
|||
|
|
|
|||
|
|
Приводит документ к единому виду:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"title": "Трудовой кодекс Российской Федерации",
|
|||
|
|
"document_number": "197-ФЗ",
|
|||
|
|
"adoption_date": "2001-12-30",
|
|||
|
|
"source_url": "...",
|
|||
|
|
"text": "...",
|
|||
|
|
"articles": []
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 4. Split by articles
|
|||
|
|
|
|||
|
|
Кодексы нужно резать по статьям.
|
|||
|
|
|
|||
|
|
Пример структуры:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"article_number": "136",
|
|||
|
|
"article_title": "Порядок, место и сроки выплаты заработной платы",
|
|||
|
|
"text": "..."
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 5. Chunker
|
|||
|
|
|
|||
|
|
Если статья длинная, делить на чанки.
|
|||
|
|
|
|||
|
|
Рекомендация:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
chunk_size = 1000-1800 tokens
|
|||
|
|
chunk_overlap = 100-200 tokens
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Для коротких статей — один chunk = одна статья.
|
|||
|
|
|
|||
|
|
#### 6. Embeddings
|
|||
|
|
|
|||
|
|
Для каждого чанка:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
embedding = embedding_model(chunk_text + article_title + source_title)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 7. Full-text index
|
|||
|
|
|
|||
|
|
Дополнительно делать `tsvector` для PostgreSQL:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
UPDATE law_chunks
|
|||
|
|
SET tsv = to_tsvector('russian', chunk_text);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 18. Актуальность законов
|
|||
|
|
|
|||
|
|
Для каждого источника хранить:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
source_url
|
|||
|
|
publication_date
|
|||
|
|
effective_date
|
|||
|
|
loaded_at
|
|||
|
|
version_hash
|
|||
|
|
is_active
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Если документ обновился:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
1. старая версия помечается is_active = false;
|
|||
|
|
2. новая версия добавляется как новая запись;
|
|||
|
|
3. чанки старой версии исключаются из поиска;
|
|||
|
|
4. история консультаций сохраняет ссылки на версию, которая использовалась на момент ответа.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 19. Retrieval API внутри проекта
|
|||
|
|
|
|||
|
|
Минимальная функция:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
async def retrieve_law_chunks(
|
|||
|
|
queries: list[str],
|
|||
|
|
law_types: list[str],
|
|||
|
|
jurisdiction: str = "RU",
|
|||
|
|
top_k: int = 5,
|
|||
|
|
) -> list[LawChunk]:
|
|||
|
|
...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Внутри:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
1. Создать embedding для каждого query.
|
|||
|
|
2. Выполнить vector search.
|
|||
|
|
3. Выполнить full-text search.
|
|||
|
|
4. Слить результаты.
|
|||
|
|
5. Убрать дубли.
|
|||
|
|
6. Отфильтровать is_active = true.
|
|||
|
|
7. Прогнать reranker.
|
|||
|
|
8. Вернуть top_k чанков.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 20. Пример SQL для hybrid search
|
|||
|
|
|
|||
|
|
### 20.1. Full-text search
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
SELECT
|
|||
|
|
lc.id,
|
|||
|
|
lc.chunk_text,
|
|||
|
|
lc.article_number,
|
|||
|
|
lc.article_title,
|
|||
|
|
ls.title AS source_title,
|
|||
|
|
ts_rank(lc.tsv, plainto_tsquery('russian', :query)) AS score
|
|||
|
|
FROM law_chunks lc
|
|||
|
|
JOIN law_sources ls ON ls.id = lc.source_id
|
|||
|
|
WHERE
|
|||
|
|
ls.jurisdiction = 'RU'
|
|||
|
|
AND ls.is_active = true
|
|||
|
|
AND lc.tsv @@ plainto_tsquery('russian', :query)
|
|||
|
|
ORDER BY score DESC
|
|||
|
|
LIMIT 20;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 20.2. Vector search
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
SELECT
|
|||
|
|
lc.id,
|
|||
|
|
lc.chunk_text,
|
|||
|
|
lc.article_number,
|
|||
|
|
lc.article_title,
|
|||
|
|
ls.title AS source_title,
|
|||
|
|
1 - (lc.embedding <=> :query_embedding) AS score
|
|||
|
|
FROM law_chunks lc
|
|||
|
|
JOIN law_sources ls ON ls.id = lc.source_id
|
|||
|
|
WHERE
|
|||
|
|
ls.jurisdiction = 'RU'
|
|||
|
|
AND ls.is_active = true
|
|||
|
|
ORDER BY lc.embedding <=> :query_embedding
|
|||
|
|
LIMIT 20;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 21. FSM-состояния aiogram
|
|||
|
|
|
|||
|
|
Минимальные состояния:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class AskQuestion(StatesGroup):
|
|||
|
|
choosing_category = State()
|
|||
|
|
waiting_question = State()
|
|||
|
|
waiting_region = State()
|
|||
|
|
processing = State()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Сценарий:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
/start
|
|||
|
|
-> main_menu
|
|||
|
|
|
|||
|
|
Задать вопрос
|
|||
|
|
-> choosing_category
|
|||
|
|
|
|||
|
|
Выбрана категория
|
|||
|
|
-> waiting_question
|
|||
|
|
|
|||
|
|
Пользователь написал вопрос
|
|||
|
|
-> waiting_region
|
|||
|
|
|
|||
|
|
Пользователь указал регион
|
|||
|
|
-> processing
|
|||
|
|
-> RAG
|
|||
|
|
-> answer
|
|||
|
|
-> main_menu
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 22. Ограничения MVP
|
|||
|
|
|
|||
|
|
### 22.1. Лимиты
|
|||
|
|
|
|||
|
|
Чтобы не сжечь бюджет LLM:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Free MVP:
|
|||
|
|
— 5 консультаций в день на пользователя
|
|||
|
|
— максимум 5 сообщений в одной консультации
|
|||
|
|
— максимум 7 retrieved chunks в одном ответе
|
|||
|
|
— timeout LLM-запроса 60 секунд
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 22.2. Rate limit
|
|||
|
|
|
|||
|
|
Ключ:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
rate_limit:{telegram_id}:{date}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Хранить можно в Redis или PostgreSQL.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 23. Логирование
|
|||
|
|
|
|||
|
|
Логировать:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
telegram_id
|
|||
|
|
consultation_id
|
|||
|
|
category
|
|||
|
|
region
|
|||
|
|
user_question
|
|||
|
|
generated_queries
|
|||
|
|
retrieved_chunk_ids
|
|||
|
|
llm_model
|
|||
|
|
tokens_input
|
|||
|
|
tokens_output
|
|||
|
|
latency_ms
|
|||
|
|
errors
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Не логировать лишние персональные данные без необходимости.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 24. Safety rules
|
|||
|
|
|
|||
|
|
Бот не должен:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
— гарантировать победу в суде;
|
|||
|
|
— обещать точный исход дела;
|
|||
|
|
— советовать подделывать документы;
|
|||
|
|
— помогать скрывать доходы;
|
|||
|
|
— помогать обходить закон;
|
|||
|
|
— составлять фиктивные схемы;
|
|||
|
|
— выдавать себя за адвоката;
|
|||
|
|
— давать категоричные инструкции по уголовным делам без рекомендации юриста.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
В сложных случаях:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
По этому вопросу лучше обратиться к юристу/адвокату, потому что ошибка может повлечь серьезные последствия.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 25. Минимальный docker-compose
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
services:
|
|||
|
|
bot:
|
|||
|
|
build: .
|
|||
|
|
env_file:
|
|||
|
|
- .env
|
|||
|
|
depends_on:
|
|||
|
|
- postgres
|
|||
|
|
restart: unless-stopped
|
|||
|
|
|
|||
|
|
postgres:
|
|||
|
|
image: pgvector/pgvector:pg16
|
|||
|
|
environment:
|
|||
|
|
POSTGRES_DB: legal_bot
|
|||
|
|
POSTGRES_USER: legal_bot
|
|||
|
|
POSTGRES_PASSWORD: legal_bot_password
|
|||
|
|
volumes:
|
|||
|
|
- postgres_data:/var/lib/postgresql/data
|
|||
|
|
ports:
|
|||
|
|
- "5432:5432"
|
|||
|
|
restart: unless-stopped
|
|||
|
|
|
|||
|
|
volumes:
|
|||
|
|
postgres_data:
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 26. .env
|
|||
|
|
|
|||
|
|
```env
|
|||
|
|
BOT_TOKEN=
|
|||
|
|
|
|||
|
|
DATABASE_URL=postgresql+asyncpg://legal_bot:legal_bot_password@postgres:5432/legal_bot
|
|||
|
|
|
|||
|
|
OPENAI_BASE_URL=https://openrouter.ai/api/v1
|
|||
|
|
OPENAI_API_KEY=
|
|||
|
|
LLM_MODEL=openai/gpt-4.1-mini
|
|||
|
|
|
|||
|
|
EMBEDDING_PROVIDER=openai
|
|||
|
|
EMBEDDING_MODEL=text-embedding-3-small
|
|||
|
|
|
|||
|
|
DAILY_CONSULTATION_LIMIT=5
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 27. MVP roadmap
|
|||
|
|
|
|||
|
|
### Этап 1 — Telegram shell
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
— /start
|
|||
|
|
— главное меню
|
|||
|
|
— выбор категории
|
|||
|
|
— ввод вопроса
|
|||
|
|
— ввод региона
|
|||
|
|
— сохранение пользователя
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Этап 2 — база законов
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
— загрузить 5–8 основных источников
|
|||
|
|
— распарсить по статьям
|
|||
|
|
— сохранить law_sources
|
|||
|
|
— сохранить law_chunks
|
|||
|
|
— построить embeddings
|
|||
|
|
— построить full-text index
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Этап 3 — RAG
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
— классификатор вопроса
|
|||
|
|
— генератор search_queries
|
|||
|
|
— hybrid search
|
|||
|
|
— rerank
|
|||
|
|
— финальный ответ с источниками
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Этап 4 — история
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
— список консультаций
|
|||
|
|
— открыть консультацию
|
|||
|
|
— продолжить консультацию
|
|||
|
|
— удалить консультацию
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Этап 5 — защита от мусора
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
— лимиты
|
|||
|
|
— логирование
|
|||
|
|
— обработка ошибок LLM
|
|||
|
|
— fallback если RAG ничего не нашел
|
|||
|
|
— дисклеймер
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 28. Критерии готовности MVP
|
|||
|
|
|
|||
|
|
MVP можно считать готовым, если:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
1. Пользователь может задать вопрос.
|
|||
|
|
2. Бот уточняет категорию и регион.
|
|||
|
|
3. LLM генерирует поисковые запросы.
|
|||
|
|
4. RAG возвращает реальные статьи из базы.
|
|||
|
|
5. Ответ содержит только найденные источники.
|
|||
|
|
6. История консультации сохраняется.
|
|||
|
|
7. Пользователь может открыть старую консультацию.
|
|||
|
|
8. Если источников нет, бот честно говорит об этом.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 29. Короткое ТЗ в одну фразу
|
|||
|
|
|
|||
|
|
Сделать Telegram-бота на Python/aiogram, который консультирует пользователей по законам РФ: принимает вопрос, уточняет категорию и регион, через OpenAI-compatible LLM формирует RAG-запросы, ищет по локальной базе законов РФ через hybrid search, отвечает простым языком с найденными источниками и сохраняет консультации в историю.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 30. Главный принцип разработки
|
|||
|
|
|
|||
|
|
Не делать «ChatGPT в Telegram».
|
|||
|
|
|
|||
|
|
Делать юридический workflow:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Категория → Вопрос → Регион → Классификация → RAG → Ответ с источниками → История
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Самое важное качество MVP:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Лучше меньше категорий и источников, но чтобы бот не выдумывал статьи и всегда показывал, откуда взял правовую норму.
|
|||
|
|
```
|