Демо API

Публичная неаутентифицированная конечная точка для функции «Попробуйте сами» на лендинге. Ограничение запросов по IP, доступен только сценарий onboarding.

Конечные точки

POST /api/v1/demo/chat

Отправка запроса на естественном языке о демонстрационной кодовой базе (PostgreSQL 17.6).

Аутентификация: не требуется (публичная конечная точка) Ограничение: 30 запросов/минуту на IP

Запрос

{
  "query": "Где определена главная функция?",
  "language": "ru"
}
Поле Тип Обязательно По умолчанию Описание
query string Да Вопрос на естественном языке (1–500 символов)
language string Нет "ru" Язык ответа ("en" или "ru")

Ответ (200)

Успешный запрос:

{
  "answer": "Главная функция определена в src/backend/main/main.c на строке 53...",
  "scenario_id": "onboarding",
  "processing_time_ms": 234.5
}

Отклонённый запрос (не по теме, неверный сценарий или заблокированное содержимое):

{
  "answer": "### Запрос заблокирован\n\nЭтот тип запроса не поддерживается в демо-версии...",
  "scenario_id": "demo_rejection",
  "processing_time_ms": 12.3
}

Внутренняя ошибка (LLM недоступна и т.д.):

{
  "answer": "К сожалению, система анализа временно недоступна. Попробуйте позже.",
  "scenario_id": "error",
  "processing_time_ms": 1502.7
}
Поле Тип Описание
answer string Ответ LLM, сообщение об отклонении или текст ошибки
scenario_id string "onboarding" — успех, "demo_rejection" — запрос отклонён, "error" — внутренняя ошибка
processing_time_ms float Время обработки на сервере

Ошибки

Статус Причина Тело ответа
400 Запрос слишком длинный (>500 символов) {"detail": "Запрос слишком длинный. Максимальная длина — 500 символов."}
422 Ошибка валидации Pydantic (пустой запрос, неверные типы) {"detail": [...]}
429 Превышено ограничение на число запросов {"detail": "Слишком много запросов"}
503 Демо-режим отключён {"detail": "Демонстрационная конечная точка отключена"}

Примечание: Нерелевантные запросы и запросы неверного сценария возвращают HTTP 200 с scenario_id: "demo_rejection" и дружественным сообщением в поле answer. Это НЕ HTTP-ошибки — такое поведение позволяет лендингу показывать полезные подсказки без срабатывания обработчиков ошибок.

GET /api/v1/demo/status

Проверка доступности и конфигурации демонстрационной конечной точки.

Аутентификация: не требуется

Ответ (200)

{
  "enabled": true,
  "rate_limit": "30/minute",
  "max_query_length": 500,
  "allowed_scenarios": ["onboarding"]
}

Конвейер валидации запросов

Входящие запросы проходят 3-этапную валидацию перед обработкой:

1. ЖЁСТКИЙ ОТКАЗ — явно вредоносное содержимое (regex-шаблоны из доменного плагина)
   └─ Возврат 200 с scenario_id="demo_rejection", rejection_reason="blocked_content"

2. НЕВЕРНЫЙ СЦЕНАРИЙ — легитимный запрос за пределами onboarding
   └─ Возврат 200 с scenario_id="demo_rejection", rejection_reason="wrong_scenario"

3. РЕЛЕВАНТНОСТЬ ДОМЕНУ — оценка по ключевым словам/шаблонам доменного плагина
   └─ Оценка ≥ 0.5 (порог LOW) → запрос принят, направлен в обработчик onboarding
   └─ Оценка < 0.5 → отклонение с scenario_id="demo_rejection", rejection_reason="off_topic"

ValidationResult

Внутренний dataclass ValidationResult (demo.py:122):

@dataclass
class ValidationResult:
    is_valid: bool
    confidence: float
    rejection_reason: Optional[str] = None  # "off_topic" | "wrong_scenario" | "blocked_content"
    detected_scenario: Optional[str] = None
    method: str = "keyword"

Пороги релевантности

Настраиваются в src/config/demo.yamlrelevance.thresholds:

Порог Значение Условие
high 0.9 3+ совпадения ключевых слов
medium 0.75 2 совпадения
low 0.5 1 совпадение — граница отклонения
minimal 0.1 0 совпадений

Запросы с оценкой ниже low (0.5) отклоняются как нерелевантные.

Методы доменного плагина

Шаблоны валидации загружаются из активного доменного плагина (DomainPluginV3):

Метод Возвращает Назначение
get_demo_keywords() List[str] Ключевые слова домена для оценки релевантности
get_hard_reject_patterns() List[str] Regex-шаблоны для жёсткого отклонения
get_wrong_scenario_patterns() List[Tuple[str, str]] Пары (шаблон, имя_сценария) для обнаружения неверного сценария

Кеширование

Демонстрационная конечная точка кеширует ответы для снижения нагрузки на LLM:

Кеш Размер TTL Ключ
Кеш ответов 100 записей (LRU) 30 минут query.lower().strip()

Примечание: Кеш проверяется до валидации. Повторные запросы (даже нерелевантные, которые были ранее обработаны) возвращают кешированные результаты без повторной валидации.

Конфигурация

config.yaml

api:
  demo:
    enabled: true                    # Включить/отключить демо-конечную точку
    rate_limit: 30/minute            # Ограничение запросов на IP
    max_query_length: 500            # Макс. длина запроса в символах
    allowed_scenarios:               # Разрешённые идентификаторы сценариев
      - onboarding

src/config/demo.yaml

Отдельная конфигурация для кеширования и оценки релевантности:

cache:
  dynamic_response:
    maxsize: 100           # Размер LRU-кеша ответов
    ttl_seconds: 1800      # 30 минут
  judge:
    maxsize: 1000          # Размер LRU-кеша (зарезервирован для будущего LLM-судьи)
    ttl_seconds: 3600      # 1 час

llm_judge:
  temperature: 0.1         # Зарезервировано для будущей реализации LLM-судьи
  max_tokens: 50
  model: yandexgpt-lite

relevance:
  thresholds:
    high: 0.9
    medium: 0.75
    low: 0.5               # Граница отклонения
    minimal: 0.1

Переменные окружения

Переменная По умолчанию Описание
DEMO_ENABLED true Включить/отключить демо-конечную точку
DEMO_RATE_LIMIT 30/minute Ограничение запросов на IP

Модели Pydantic

class DemoRequest(BaseModel):
    query: str = Field(..., min_length=1, max_length=500, description="User query")
    language: str = Field(default="ru", description="Response language")

class DemoResponse(BaseModel):
    answer: str = Field(..., description="Response from the system")
    scenario_id: str = Field(default="onboarding", description="Scenario used")
    processing_time_ms: float = Field(..., description="Processing time in milliseconds")

Примеры использования

curl

# Корректный запрос
curl -X POST http://localhost:8000/api/v1/demo/chat \
  -H "Content-Type: application/json" \
  -d '{"query": "Как работает MVCC?", "language": "ru"}'

# Проверка статуса
curl http://localhost:8000/api/v1/demo/status

JavaScript (лендинг)

const response = await fetch('/api/v1/demo/chat', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    query: document.getElementById('demo-input').value,
    language: 'ru'
  })
});

const data = await response.json();

if (response.ok) {
  if (data.scenario_id === 'demo_rejection') {
    // Запрос отклонён — показать дружественную подсказку
    showRejection(data.answer);
  } else if (data.scenario_id === 'error') {
    showError(data.answer);
  } else {
    showAnswer(data.answer);
  }
} else if (response.status === 429) {
  showRateLimit();
}

Безопасность

  • Без аутентификации — конечная точка публично доступна
  • Ограничение запросов — 30 запросов/минуту на IP предотвращает злоупотребление
  • Ограничение сценария — доступен только onboarding (без анализа безопасности, редактирования файлов и т.д.)
  • Валидация запросов — 3-этапный конвейер блокирует вредоносные и нерелевантные запросы
  • Только чтение — операции записи невозможны через демонстрационную конечную точку
  • Ключевые слова домена — загружаются из доменного плагина, не зашиты в код

Связанная документация


Модуль: src/api/routers/demo.py Конфигурация: src/config/demo.yaml, config.yamlapi.demo Последнее обновление: март 2026