LEX — AI Legal Platform for Law Firms

AI-powered legal analysis platform for law firms and corporate counsel.

Features

Resources

Blog Articles

Technology

Built on AWS (EC2, Bedrock Claude AI, ALB, WAF, S3, ACM, KMS). PostgreSQL, Redis, Qdrant vector database. TypeScript, React, Node.js.

Start free — 50 credits on registration. Sign up

TECH 6 хв

Server-side evidence extraction: як ми винесли аналіз доказів на бекенд

Фронтенд парсив докази з тексту відповіді regex-ами — мобільний Safari зависав на секунду. Ми перенесли витяг доказів на бекенд, додали SSE-подію evidence, і тепер клієнт просто рендерить готові об\

Server-side evidence extraction: як ми винесли аналіз доказів на бекенд

Коли парсинг на клієнті перестав справлятися — ми перенесли розбір доказів туди, де йому місце.


Проблема

LEX AI повертає користувачу не просто текст. Кожна відповідь містить докази: фрагменти судових рішень, статті законодавства, витяги з документів. Раніше весь цей потік приходив як єдиний текстовий блок, і фронтенд мусив самостійно розбирати його на структуровані картки.

На десктопі це працювало прийнятно. На мобільних пристроях — ні.

Симптоми, які ми бачили:

Проблема Причина
UI freezes на 300-800 мс Парсинг великих відповідей блокував main thread
Неправильне виділення доказів Regex-евристики не покривали всі формати
Дублювання логіки Кожен клієнт (веб, мобайл, MCP) писав свій парсер
Погіршення при масштабуванні Чим більше доказів — тим повільніше рендер

Коли відповідь містила 15-20 доказів (типова ситуація для аналізу судової практики), мобільний Safari просто зависав на секунду. Користувачі це помічали.

Архітектурне рішення

Замість того, щоб оптимізувати клієнтський парсер, ми поставили питання інакше: навіщо взагалі парсити на клієнті те, що бекенд вже знає?

Коли ChatService викликає інструменти (search_court_decisions, get_legislation_section, vault_search), він отримує структуровані дані. Потім LLM генерує текстову відповідь, а клієнт намагається із тексту витягнути назад ту саму структуру. Це зайвий цикл.

Рішення: бекенд витягує докази під час генерації відповіді та надсилає їх окремими SSE-подіями.

Потік даних: до і після

Раніше:

Backend: LLM генерує текст з доказами вперемішку
   -> SSE: answer (один великий блок)
   -> Frontend: regex-парсинг, побудова карток
   -> Рендер

Тепер:

Backend: LLM генерує текст
   -> EvidenceExtractor класифікує tool_result
   -> SSE: evidence { type, title, source, content, relevance_score }
   -> SSE: answer (чистий текст без вбудованих доказів)
   -> Frontend: рендер готових об'єктів

SSE-протокол

Ми розширили існуючий SSE-потік новою подією evidence. Повний набір подій тепер виглядає так:

Подія Призначення Payload
thinking Індикатор обробки { stage: string }
tool_result Результат виклику інструменту { tool, result, cost }
evidence Структурований доказ { type, title, source, content, relevance_score }
answer Текстовий фрагмент відповіді { delta: string }
complete Завершення потоку { total_cost, evidence_count }

Об'єкт evidence має чітку типізацію:

interface EvidenceBlock {
  type: 'court_decision' | 'legislation' | 'document' | 'legal_position';
  title: string;
  source: string;
  content: string;
  relevance_score: number;
}

Поле relevance_score (0-1) дозволяє фронтенду сортувати докази за релевантністю та згортати менш важливі за замовчуванням.

Витяг доказів на бекенді

EvidenceExtractor працює на етапі обробки tool_result. Коли ChatService отримує результат від інструменту, він передає його в екстрактор до того, як LLM почне генерувати фінальну відповідь.

Для класифікації (court_decision vs legislation vs document) ми використовуємо LLM на рівні quick-моделі (gpt-4o-mini). Це додає 50-100 мс на доказ, але економить значно більше на клієнті та гарантує коректну класифікацію.

Критичний момент: екстракція відбувається паралельно з генерацією відповіді. Поки LLM пише текст, докази вже летять до клієнта. Користувач бачить картки в EvidencePanel ще до завершення текстової відповіді.

Fallback-механізм

Ми не видалили клієнтський парсер. Він залишився як fallback:

if (receivedEvidenceEvents.length > 0) {
  // Використовуємо серверні докази
  renderStructuredEvidence(receivedEvidenceEvents);
} else {
  // Fallback: парсимо з тексту відповіді
  const extracted = parseEvidenceFromText(fullAnswer);
  renderStructuredEvidence(extracted);
}

Це захищає від трьох сценаріїв: бекенд ще не оновлений (поступовий деплой), екстрактор впав з помилкою, з'єднання розірвалось посеред потоку і evidence-події загубились.

Результати

Метрика До Після
Час до першого доказу в UI 2.1 сек 0.8 сек
Main thread blocking (мобайл) 300-800 мс < 50 мс
Коректність класифікації ~82% ~96%
Розмір клієнтського бандлу baseline -4 KB (видалені regex-патерни)

Найбільший виграш — на мобільних. UI jank практично зник, бо фронтенд більше не займається важким парсингом. EvidencePanel просто рендерить готові об'єкти.

Висновки

Ця міграція підтвердила принцип, який ми дотримуємось у LEX AI: дані повинні структуруватись якомога ближче до джерела. Бекенд знає, що він повернув з інструменту. Змушувати клієнт здогадуватись про це з тексту — це архітектурний борг, який ми нарешті закрили.

Fallback-шар робить міграцію безпечною: навіть якщо серверна екстракція тимчасово недоступна, користувач побачить докази. Просто трохи повільніше.