Files
2026-05-25 01:12:43 +03:00

1374 lines
35 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
Лучше меньше категорий и источников, но чтобы бот не выдумывал статьи и всегда показывал, откуда взял правовую норму.
```