Skip to content

Repository files navigation

Business Intake & Triage Assistant

FastAPI-сервис для первичной обработки и маршрутизации неструктурированных бизнес-запросов: структурирование через LLM, валидация, определение маршрута или необходимости уточнения, формирование ответа заказчику и аудит в SQLite.

Live demo: https://intake.elivcloud.org — access-protected demo; credentials available on request.

Что демонстрирует проект

  • Бизнес-проблема: неструктурированный business intake нужно быстро привести к форме, пригодной для первичной маршрутизации, не теряя запросы с недостаточными данными или неподдерживаемым доменом.
  • Решение: LLM выполняет structured analysis, после чего детерминированный Python-слой принимает финальное routing/action решение; при недостатке данных или уверенности используются контролируемые clarification/human-review пути.
  • Безопасность архитектуры: customer-facing результат отделен от внутреннего аудита, а ошибки LLM и конфигурации переходят в безопасный processing_error вместо падения процесса. Это не позволяет ответу модели напрямую определять итоговый маршрут или раскрывать служебные данные заказчику.

Статус проекта

Текущий статус: MVP завершен, функциональность заморожена; post-MVP portfolio/demo deployment развернут.

  • проведена ручная приемочная проверка через реальный фронтенд, бэкенд, OpenAI и SQLite-аудит;
  • 10 из 10 канонических E2E-сценариев прошли успешно — результаты приемочной проверки;
  • проведён независимый финальный аудит проекта;
  • проект опубликован на GitHub;
  • закрытый live demo работает через HTTPS; доступ защищен, credentials available on request;
  • дальнейшие идеи развития вынесены в план развития — новые продуктовые функции в текущей версии не добавляются.

Демо

Happy path: результат обработки запроса

Happy path: структурирование бизнес-запроса и подтвержденная маршрутизация к профильным специалистам.

Журнал с двумя контролируемыми ветками решения

Журнал: автоматическая маршрутизация и передача координатору на human review с сохранением обоих результатов.

Portfolio/demo deployment

Контейнерный demo deployment использует статический Vite build, который обслуживает Nginx, и FastAPI backend в приватной контейнерной сети. Nginx проксирует API-запросы к backend; backend напрямую наружу не экспонируется. Внешний reverse proxy завершает HTTPS и защищает весь hostname через Basic Auth perimeter.

SQLite хранится в отдельном persistent named volume. Demo database используется только для synthetic portfolio data. OPENAI_API_KEY хранится только в server runtime environment и не включен в репозиторий или frontend build.

Packaging остается компактным и разделен по назначению:

  • Dockerfile собирает FastAPI backend;
  • frontend/Dockerfile создает Vite build и переносит его в Nginx image;
  • frontend/nginx.conf обслуживает SPA и проксирует API к приватному backend;
  • compose.demo.yml связывает контейнеры и persistent SQLite volume для demo deployment.

Как это работает

Неструктурированный бизнес-запрос
              │
              ▼
    FastAPI + валидация (IngestRequest)
              │
              ▼
    OpenAI Structured Outputs
              │
              ▼
         LLMAnalysis
              │
              ▼
Детерминированный слой принятия решения
              │
              ▼
┌───────────────────────┬───────────────────────┬───────────────────────┐
│     Маршрутизация     │    Нужны уточнения    │ Передача координатору │
│   ready_for_routing   │  needs_clarification  │      human_review     │
└───────────────────────┴───────────────────────┴───────────────────────┘
              │
              ▼
     Ответ заказчику + SQLite-аудит

Аварийный путь — при ошибке LLM или конфигурации (например, не задан OPENAI_API_KEY) — обрабатывается отдельно и тоже безопасно, без падения приложения:

Ошибка LLM или конфигурации
              │
              ▼
        processing_error
              │
              ▼
Безопасная передача на ручную обработку + SQLite-аудит

Ключевые возможности

  • Structured Outputs (client.responses.parse(..., text_format=LLMAnalysis)) — строгая схема LLMAnalysis вместо произвольного текста от LLM (docs/LLM_INTEGRATION.md);
  • окончательное решение принимает детерминированный Python-слой (app/services/decision_service.py) — LLM только предлагает, но не решает (docs/DECISION_TABLE.md);
  • блокирующее уточнение (needs_clarification, маршрут ещё не назначен) и неблокирующее уточнение (ready_for_routing с уточняющими вопросами при уже подтверждённом маршруте) — два разных случая, не перепутанных между собой (docs/DECISION_TABLE.md);
  • маршрутизация сразу к нескольким специалистам, если запрос требует нескольких профильных компетенций;
  • неподдерживаемый бизнес-домен направляется на human_review, а не подставляется под похожий по звучанию существующий маршрут;
  • защита от инъекций инструкций (prompt injection) на уровне системного промпта — пользовательский текст явно помечен как недоверенные данные (docs/LLM_INTEGRATION.md);
  • безопасный processing_error вместо падения приложения при сбое LLM или отсутствующей конфигурации;
  • SQLite-аудит каждого обработанного запроса, включая ошибки, без утечки ключей и сырых ответов провайдера (docs/AUDIT_STORAGE.md);
  • тонкий фронтенд на TypeScript/Vite поверх того же API и внутренний журнал аудита (docs/FRONTEND.md).

В самом приложении эндпоинты аудита (GET /records, GET /records/{request_id}) и раздел фронтенда «Журнал» не имеют application-level авторизации. В live demo весь hostname закрыт внешним Basic Auth perimeter; без такого защитного периметра эти данные нельзя публиковать в открытом доступе.

Ограничения MVP

  • нет application-level аутентификации и авторизации ни у API бэкенда, ни у фронтенда — закрытый live demo компенсирует это внешним Basic Auth perimeter, но открытая публикация без защитного периметра недопустима;
  • нет гарантии экспертного качества решения: LLM классифицирует и предлагает, а не выполняет юридическую, комплаенс- или финансовую экспертизу (docs/PROCESS_FLOW.md) — итоговая маршрутизация подлежит проверке профильным специалистом или координатором;
  • ответ заказчику (ProcessingResult) и внутренний аудит (AuditRecordRead) — сознательно разные проекции одних и тех же данных: заказчик не видит LLMAnalysis, final_routes, reason_codes и детали ошибок (docs/PROCESSING_PIPELINE.md, docs/API_INGEST.md);
  • один LLM-провайдер (OpenAI), одна SQLite-база без миграций, без ролей и пользователей, без последующей переписки с заказчиком после получения ответа.

Полный план развития (v2/v3/later) — docs/ROADMAP.md. Фактически проведённая ручная приемочная проверка — docs/ACCEPTANCE_SCENARIOS.md.

Основные модели

  • app/domain/enums.pyRequestCategory, RequestTopic, SpecialistRoute, Priority, Confidence, RecommendedAction, ProcessingStatus, DecisionReason.
  • app/schemas/ingest.pyIngestRequest: валидация входящего неструктурированного запроса.
  • app/schemas/analysis.pyLLMAnalysis: строгая схема результата LLM-анализа с межполевыми бизнес-правилами (пример — docs/example_analysis.json).
  • app/schemas/decision.pyCustomerResponse, ProcessingDecision: итоговое решение и ответ заказчику.
  • app/services/decision_service.py — превращает LLMAnalysis в ProcessingDecision; LLM ничего не решает окончательно, только предлагает.
  • app/services/customer_response_service.py — формирует безопасные тексты для заказчика по контролируемым шаблонам, без утечки внутренних enum и служебных полей.
  • app/services/llm_service.pyRequestAnalyzer (протокол) и OpenAIRequestAnalyzer: получает валидированный LLMAnalysis от OpenAI через Structured Outputs.
  • app/services/llm_errors.py — собственная иерархия исключений LLM-слоя (LLMServiceError и подклассы), независимая от классов исключений OpenAI SDK.
  • app/core/prompts.py — системный промпт (SYSTEM_PROMPT), фиксированная константа без плейсхолдеров.
  • app/db/ProcessingRecord (SQLAlchemy 2.x модель) и ProcessingRecordRepository: SQLite-аудит (подробности — docs/AUDIT_STORAGE.md).
  • app/schemas/audit.pyAuditRecordCreate/AuditRecordRead/AuditRecordSummary: контракт репозитория аудита и его компактная проекция для списка.
  • app/services/processing_service.pyProcessingService: связывает RequestAnalyzer, детерминированный модуль принятия решения и аудит-репозиторий в единый конвейер обработки (подробности — docs/PROCESSING_PIPELINE.md).
  • app/schemas/processing.pyProcessingResult: безопасная внешняя проекция результата обработки.
  • app/core/lifespan.pycreate_lifespan(): при запуске приложения создает engine, инициализирует таблицы, создает фабрику сессий и создает или регистрирует RequestAnalyzer в app.state; при остановке освобождает engine. Создание OpenAIRequestAnalyzer в lifespan не создает реальный клиент OpenAI: он создается лениво при первом analyze() через _build_client().
  • app/main.pycreate_app(): фабрика приложения с внедряемыми Settings/RequestAnalyzer/Engine. На уровне импорта модуль создает settings = Settings() (app/core/config.py) и вызывает app = create_app() для uvicorn app.main:app, но сама фабрика не создает engine БД, таблицы или реальный клиент OpenAI. Engine, таблицы и RequestAnalyzer инициализируются в lifespan при реальном запуске, а клиент OpenAI — лениво при первом analyze().
  • app/api/dependencies.py — зависимости FastAPI в рамках одного запроса: Session, ProcessingRecordRepository, RequestAnalyzer, ProcessingService (ничего не кешируется глобально).
  • app/api/ingest.pyPOST /ingest: тонкий HTTP-эндпоинт над ProcessingService (подробности и примеры — docs/API_INGEST.md).
  • app/api/records.pyGET /records, GET /records/{request_id}: API аудита только для чтения над ProcessingRecordRepository (подробности и примеры — docs/API_RECORDS.md).

Опциональная ручная проверка LLM

Требует реального OPENAI_API_KEY (не хранится в репозитории) и не запускается автоматически:

$env:OPENAI_API_KEY = "..."
.venv\Scripts\python.exe scripts\manual_llm_check.py

Требования

  • Python 3.12
  • pip
  • Node.js и npm — для запуска и тестирования фронтенда

Создание виртуального окружения

PowerShell (Windows):

python -m venv .venv
.venv\Scripts\Activate.ps1

Bash (Linux/macOS):

python3 -m venv .venv
source .venv/bin/activate

Установка проекта с зависимостями для разработки

pip install -e ".[dev]"

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

Локальный .env создаётся из .env.example:

PowerShell (Windows):

Copy-Item .env.example .env

Bash (Linux/macOS):

cp .env.example .env

Для реального LLM-анализа нужно указать в .env:

OPENAI_API_KEY=...

.env не хранится в Git.

Без OPENAI_API_KEY приложение всё равно запускается: GET /health отвечает штатно, а POST /ingest штатно возвращает результат со статусом processing_error и передачей запроса на ручную обработку — вместо падения с ошибкой.

Запуск FastAPI

PowerShell (Windows):

.venv\Scripts\python.exe -m uvicorn app.main:app --reload

Bash (Linux/macOS):

uvicorn app.main:app --reload

Приложение будет доступно на http://127.0.0.1:8000, проверка состояния — GET /health.

Запуск тестов

pytest

Запуск Ruff

ruff check .
ruff format --check .

Фронтенд

Тонкий продуктовый фронтенд (frontend/) — чистый TypeScript + Vite, без UI-фреймворков. Подробности архитектуры и ограничений — docs/FRONTEND.md.

cd frontend
npm install
npm run dev

Сервер разработки Vite поднимается на http://localhost:5173 и проксирует запросы /health, /ingest, /records на локальный FastAPI (http://127.0.0.1:8000) — бэкенд должен быть запущен отдельно (см. выше). CORS в бэкенде ради этого не добавлялся: в режиме разработки браузер обращается к тому же источнику (origin) (localhost:5173), а Vite сам перенаправляет запрос на бэкенд.

Два раздела интерфейса:

  • Новый запрос — форма отправки неструктурированного запроса (POST /ingest) и карточка результата с понятным статусом, ответом заказчику и уточняющими вопросами (если требуются);
  • Журнал — просмотр только для чтения накопленного журнала аудита (GET /records, GET /records/{request_id}): список записей и полная карточка одной записи с анализом LLM и итоговым решением.

Фронтенд не добавляет аутентификацию или авторизацию. Раздел «Журнал» — это прямой просмотр внутреннего API аудита, который сам по себе не защищен (см. предупреждение выше и docs/API_RECORDS.md). В live demo весь интерфейс закрыт внешним Basic Auth perimeter; открытая публикация без такого защитного слоя недопустима.

Тесты фронтенда (Vitest + jsdom):

cd frontend
npm run test
npm run typecheck
npm run build

Подход к разработке

  • автор репозитория — владелец продукта, требований, архитектурных решений и приемки;
  • AI-инструменты использовались как помощники при реализации и review;
  • рабочий цикл: specification → implementation → independent review → human decision/acceptance;
  • критические бизнес-правила и финальные решения не делегируются LLM автоматически.

About

AI-powered business intake and triage MVP with FastAPI, OpenAI Structured Outputs, deterministic routing, SQLite audit, and a TypeScript frontend.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages