Π’ΡΠ΅Ρ ΠΌΠΎΠ΄ΡΠ»ΡΠ½Π°Ρ ΡΠΈΡΡΠ΅ΠΌΠ° Π΄Π»Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ Π°ΡΠ΄ΠΈΠΎ, ΡΡΠ°Π½ΡΠΊΡΠΈΠΏΡΠΈΠΈ, ΠΈΠ·Π²Π»Π΅ΡΠ΅Π½ΠΈΡ ΡΡΡΡΠΊΡΡΡΠΈΡΠΎΠ²Π°Π½Π½ΠΎΠΉ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΠΈ Ρ ΠΏΠΎΠΌΠΎΡΡΡ NLP ΠΈ ΡΠΎΠΏΠΎΡΡΠ°Π²Π»Π΅Π½ΠΈΡ Ρ Π²ΠΈΠ·ΡΠ°Π»ΡΠ½ΡΠΌΠΈ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°ΠΌΠΈ.
βββββββββββββββββββ HTTP/WebSocket βββββββββββββββββββ βββββββββββββββββββ
β Mod1_v2 β βββββββββββββββββββΊ β Mod2-v1 β β Mod3-v1 β
β (ASR + Chunk) β β (NLP + Layout)β β (Visual Map) β
β Port: 8080 β β Port: 8001 β β Port: 9001 β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Web Application β
β (Frontend) β
β Port: 3000 β
βββββββββββββββββββββββ
β
βΌ HTTP API
βββββββββββββββββββ
β Mod3-v1 β
β (Visual Map) β
β Port: 9001 β
βββββββββββββββββββ
ΠΠ°Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅: Π Π°ΡΠΏΠΎΠ·Π½Π°Π²Π°Π½ΠΈΠ΅ ΡΠ΅ΡΠΈ, ΡΠ°Π·Π±ΠΈΠ΅Π½ΠΈΠ΅ Π½Π° ΡΠ°Π½ΠΊΠΈ ΠΈ Π΄ΠΎΡΡΠ°Π²ΠΊΠ° Π΄Π°Π½Π½ΡΡ
- π€ Π Π°ΡΠΏΠΎΠ·Π½Π°Π²Π°Π½ΠΈΠ΅ ΡΠ΅ΡΠΈ (Whisper)
- βοΈ Π§Π°Π½ΠΊΠΈΠ½Π³ ΡΠ΅ΠΊΡΡΠ° ΠΏΠΎ ΠΏΡΠ΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΡΠΌ
- π‘ WebSocket ΡΡΡΠΈΠΌΠΈΠ½Π³ Π² ΡΠ΅Π°Π»ΡΠ½ΠΎΠΌ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ
- π ΠΠΎΡΡΠ°Π²ΠΊΠ° Π΄Π°Π½Π½ΡΡ Π² Mod2 ΡΠ΅ΡΠ΅Π· webhooks
POST /v1/transcribe
- ΠΠ°ΠΊΠ΅ΡΠ½Π°Ρ ΡΡΠ°Π½ΡΠΊΡΠΈΠΏΡΠΈΡ ΡΠ°ΠΉΠ»Π°GET /v1/session/{sid}/text
- ΠΠΎΠ»ΡΡΠΈΡΡ ΠΏΠΎΠ»Π½ΡΠΉ ΡΠ΅ΠΊΡΡ ΡΠ΅ΡΡΠΈΠΈGET /v1/session/{sid}/chunks
- ΠΠΎΠ»ΡΡΠΈΡΡ ΡΠ°Π½ΠΊΠΈ ΡΠ΅ΡΡΠΈΠΈPOST /v1/hooks/register
- Π Π΅Π³ΠΈΡΡΡΠ°ΡΠΈΡ webhook'ΠΎΠ²GET /healthz
- ΠΡΠΎΠ²Π΅ΡΠΊΠ° Π·Π΄ΠΎΡΠΎΠ²ΡΡ
WS /v1/stream
- Π‘ΡΡΠΈΠΌΠΈΠ½Π³ Π°ΡΠ΄ΠΈΠΎ Π² ΡΠ΅Π°Π»ΡΠ½ΠΎΠΌ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ
- ΠΡΠ΄ΠΈΠΎ ΡΠ°ΠΉΠ»Ρ (WAV, MP3, etc.)
- WebSocket Π°ΡΠ΄ΠΈΠΎ ΡΡΡΠΈΠΌ
- ΠΠ°ΡΠ°ΠΌΠ΅ΡΡΡ:
session_id
,lang
(ru-RU),emit_partial
- Π§Π°Π½ΠΊΠΈ (JSON) Ρ ΠΏΠΎΠ»ΡΠΌΠΈ:
{ "session_id": "string", "chunk_id": "string", "seq": 1, "text": "string", "overlap_prefix": "string|null", "lang": "ru-RU" }
- Π€ΠΈΠ½Π°Π»ΡΠ½ΡΠΉ ΡΠ΅Π·ΡΠ»ΡΡΠ°Ρ (JSON):
{ "session_id": "string", "text_full": "string", "lang": "ru-RU", "duration_sec": 120.5, "total_chunks": 15 }
# config.yaml
app:
host: 0.0.0.0
port: 8080
log_level: info
emit_partial: true
whisper:
model: small
device: cpu
language: ru
vad_filter: true
chunking:
sent_min: 3
sent_max: 5
char_limit: 1200
overlap_sent: 1
ΠΠ°Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅: ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΡΠ΅ΠΊΡΡΠ°, ΠΈΠ·Π²Π»Π΅ΡΠ΅Π½ΠΈΠ΅ ΡΡΡΠ½ΠΎΡΡΠ΅ΠΉ ΠΈ Π³Π΅Π½Π΅ΡΠ°ΡΠΈΡ layout'ΠΎΠ²
- π§ NLP ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ° (Stanza Π΄Π»Ρ ΡΡΡΡΠΊΠΎΠ³ΠΎ ΡΠ·ΡΠΊΠ°)
- π ΠΠ·Π²Π»Π΅ΡΠ΅Π½ΠΈΠ΅ ΠΊΠ»ΡΡΠ΅Π²ΡΡ ΡΡΠ°Π· (NOUN, ADJ+NOUN, NOUN+NOUN)
- π― ΠΠ°ΠΏΠΏΠΈΠ½Π³ Π½Π° ΠΎΠ½ΡΠΎΠ»ΠΎΠ³ΠΈΡ (ΡΠΎΡΠ½ΡΠΉ + fuzzy ΠΏΠΎΠΈΡΠΊ)
- π¨ ΠΠ΅Π½Π΅ΡΠ°ΡΠΈΡ layout'ΠΎΠ² ΠΏΠΎ ΡΠ°Π±Π»ΠΎΠ½Ρ hero-main-footer
POST /v2/ingest/chunk
- ΠΡΠΈΠ΅ΠΌ ΡΠ°Π½ΠΊΠΎΠ² ΠΎΡ Mod1POST /v2/ingest/full
- ΠΡΠΈΠ΅ΠΌ ΡΠΈΠ½Π°Π»ΡΠ½ΠΎΠ³ΠΎ ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠ°POST /v2/webhooks/register
- Π Π΅Π³ΠΈΡΡΡΠ°ΡΠΈΡ webhook'ΠΎΠ²
GET /v2/session/{session_id}/layout
- ΠΠΎΠ»ΡΡΠΈΡΡ layout ΡΠ΅ΡΡΠΈΠΈGET /v2/session/{session_id}/entities
- ΠΠΎΠ»ΡΡΠΈΡΡ ΠΈΠ·Π²Π»Π΅ΡΠ΅Π½Π½ΡΠ΅ entities ΠΈ keyphrases
GET /v2/vocab
- ΠΠΎΠ»ΡΡΠΈΡΡ ΡΠ»ΠΎΠ²Π°ΡΡ ΡΠ΅ΡΠΌΠΈΠ½ΠΎΠ²POST /v2/vocab/sync
- Π‘ΠΈΠ½Ρ ΡΠΎΠ½ΠΈΠ·ΠΈΡΠΎΠ²Π°ΡΡ ΡΠ»ΠΎΠ²Π°ΡΡ
POST /v2/debug/parse
- ΠΡΠ»Π°Π΄ΠΊΠ° ΠΏΠ°ΡΡΠΈΠ½Π³Π° ΡΠ΅ΠΊΡΡΠ°
GET /healthz
- ΠΡΠΎΠ²Π΅ΡΠΊΠ° Π·Π΄ΠΎΡΠΎΠ²ΡΡ
- Π§Π°Π½ΠΊΠΈ ΠΎΡ Mod1 (Ρ ΠΏΠΎΠ΄ΠΏΠΈΡΡΡ HMAC-SHA256)
- Π€ΠΈΠ½Π°Π»ΡΠ½ΡΠ΅ ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΡ ΠΎΡ Mod1
- Π’Π΅ΠΊΡΡ Π΄Π»Ρ ΠΎΡΠ»Π°Π΄ΠΊΠΈ
- Layout (JSON):
{ "status": "ok", "session_id": "string", "layout": { "template": "hero-main-footer", "sections": { "hero": [{"component": "Hero"}], "main": [{"component": "ui.button"}, {"component": "ui.form"}], "footer": [{"component": "ContactForm"}] }, "count": 3 } }
# config/app.yaml
STANZA_LANG: ru
FUZZY_THRESHOLD: 0.80
STREAM_PREVIEW: true
PAGE_TEMPLATE: hero-main-footer
MAX_COMPONENTS_PER_PAGE: 12
PLAN: EXTENDED
ΠΠ°Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅: Π‘ΠΎΠΏΠΎΡΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ NLP ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠΎΠ² Ρ Π²ΠΈΠ·ΡΠ°Π»ΡΠ½ΡΠΌΠΈ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°ΠΌΠΈ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡΠ°
- π― Π‘ΠΎΠΏΠΎΡΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΡΠ΅ΡΠΌΠΈΠ½ΠΎΠ² Ρ UI ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°ΠΌΠΈ
- π Fuzzy matching Ρ RapidFuzz (ΠΏΠΎΡΠΎΠ³ 0.8)
- π Π£ΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΡΠ»ΠΎΠ²Π°ΡΠ΅ΠΌ ΡΠ΅ΡΠΌΠΈΠ½ΠΎΠ² ΠΈ ΡΠΈΠ½ΠΎΠ½ΠΈΠΌΠΎΠ²
- π¨ ΠΠ΅Π½Π΅ΡΠ°ΡΠΈΡ layout'ΠΎΠ² ΠΏΠΎ ΡΠ°Π±Π»ΠΎΠ½Π°ΠΌ
- πΎ Π‘ΠΎΡ ΡΠ°Π½Π΅Π½ΠΈΠ΅ ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠΎΠ² Π² PostgreSQL
POST /v1/map
- Π‘ΠΎΠΏΠΎΡΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΡΡΡΠ½ΠΎΡΡΠ΅ΠΉ Ρ layout'ΠΎΠΌGET /v1/layout/{session_id}
- ΠΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΡΠΎΡ ΡΠ°Π½Π΅Π½Π½ΠΎΠ³ΠΎ layout'Π°
GET /v1/vocab
- ΠΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΡΠ»ΠΎΠ²Π°ΡΡ ΡΠ΅ΡΠΌΠΈΠ½ΠΎΠ²POST /v1/vocab/sync
- Π‘ΠΈΠ½Ρ ΡΠΎΠ½ΠΈΠ·Π°ΡΠΈΡ ΡΠ»ΠΎΠ²Π°ΡΡ
GET /healthz
- ΠΡΠΎΠ²Π΅ΡΠΊΠ° Π·Π΄ΠΎΡΠΎΠ²ΡΡ
- Π‘ΡΡΠ½ΠΎΡΡΠΈ (entities) ΠΎΡ Mod2-v1
- ΠΠ»ΡΡΠ΅Π²ΡΠ΅ ΡΡΠ°Π·Ρ (keyphrases) ΠΎΡ Mod2-v1
- Session ID Π΄Π»Ρ ΠΈΠ΄Π΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ
- Layout (JSON):
{ "status": "ok", "session_id": "string", "layout": { "template": "hero-main-footer", "sections": { "hero": [{"component": "Hero", "confidence": 1.0, "match_type": "default"}], "main": [ {"component": "ui.button", "confidence": 1.0, "match_type": "exact"}, {"component": "ui.form", "confidence": 1.0, "match_type": "exact"} ], "footer": [] }, "count": 3 }, "matches": [ { "term": "ΠΊΠ½ΠΎΠΏΠΊΠ°", "component": "ui.button", "component_type": "ui.button", "confidence": 1.0, "match_type": "exact" } ] }
- Π’ΠΎΡΠ½ΠΎΠ΅ ΡΠΎΠ²ΠΏΠ°Π΄Π΅Π½ΠΈΠ΅ - ΠΏΠΎΠΈΡΠΊ ΡΠΎΡΠ½ΠΎΠ³ΠΎ ΡΠΎΠ²ΠΏΠ°Π΄Π΅Π½ΠΈΡ ΡΠ΅ΡΠΌΠΈΠ½Π°
- ΠΠΎΠΈΡΠΊ ΠΏΠΎ ΡΠΈΠ½ΠΎΠ½ΠΈΠΌΠ°ΠΌ - ΠΏΠΎΠΈΡΠΊ ΡΡΠ΅Π΄ΠΈ ΡΠΈΠ½ΠΎΠ½ΠΈΠΌΠΎΠ² ΡΠ΅ΡΠΌΠΈΠ½ΠΎΠ²
- Fuzzy matching - ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ RapidFuzz Ρ ΠΏΠΎΡΠΎΠ³ΠΎΠΌ 0.8
- ΠΠ΅Π΄ΡΠΏΠ»ΠΈΠΊΠ°ΡΠΈΡ - ΡΠ΄Π°Π»Π΅Π½ΠΈΠ΅ Π΄ΡΠ±Π»ΠΈΠΊΠ°ΡΠΎΠ² ΠΏΠΎ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°ΠΌ
- Π‘ΠΎΡΡΠΈΡΠΎΠ²ΠΊΠ° - ΡΠΎΡΡΠΈΡΠΎΠ²ΠΊΠ° ΠΏΠΎ ΡΠ²Π΅ΡΠ΅Π½Π½ΠΎΡΡΠΈ (confidence)
- ΠΠ°Π·Π°: SQLite (
sqlite:///./mod3.db
) - terms - Π‘Π»ΠΎΠ²Π°ΡΡ ΡΠ΅ΡΠΌΠΈΠ½ΠΎΠ²
- synonyms - Π‘ΠΈΠ½ΠΎΠ½ΠΈΠΌΡ ΡΠ΅ΡΠΌΠΈΠ½ΠΎΠ²
- components - ΠΠΈΠ·ΡΠ°Π»ΡΠ½ΡΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ
- mappings - Π‘ΠΎΠΏΠΎΡΡΠ°Π²Π»Π΅Π½ΠΈΡ ΡΠ΅ΡΠΌΠΈΠ½ΠΎΠ² Ρ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°ΠΌΠΈ
- templates - Π¨Π°Π±Π»ΠΎΠ½Ρ layout'ΠΎΠ²
- layouts - Π‘ΠΎΡ ΡΠ°Π½Π΅Π½Π½ΡΠ΅ layout'Ρ
# config/settings.py
database_url: str = "sqlite:///./mod3.db"
fuzzy_threshold: float = 0.8
max_components_per_section: int = 5
default_template: str = "hero-main-footer"
cors_origins: list = ["http://localhost:3000", "http://localhost:8001", "http://localhost:8080"]
ΠΡΠ΄ΠΈΠΎ β Mod1 (ASR) β Π§Π°Π½ΠΊΠΈ β Mod2 (NLP) β Entities β Mod3 (Mapping) β Visual Layout
- Mod1 ΠΎΡΠΏΡΠ°Π²Π»ΡΠ΅Ρ ΡΠ°Π½ΠΊΠΈ Π² Mod2 ΡΠ΅ΡΠ΅Π· HTTP POST
- Mod2 ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°Π΅Ρ ΡΠ΅ΠΊΡΡ ΠΈ ΠΈΠ·Π²Π»Π΅ΠΊΠ°Π΅Ρ entities/keyphrases
- Web-ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΠΏΠΎΠ»ΡΡΠ°Π΅Ρ entities ΠΎΡ Mod2 ΡΠ΅ΡΠ΅Π·
/v2/session/{id}/entities
- Web-ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΠΎΡΠΏΡΠ°Π²Π»ΡΠ΅Ρ entities Π² Mod3 Π΄Π»Ρ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΡ layout
- Mod3 Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ Π²ΠΈΠ·ΡΠ°Π»ΡΠ½ΡΠΉ layout Π²Π΅Π±-ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ
POST /v2/ingest/chunk
Headers:
X-Signature: sha256=<hmac_hash>
Idempotency-Key: <session_id>:<chunk_id>
X-Request-Id: <session_id>:<seq>
Content-Type: application/json
GET /v2/session/{session_id}/entities
Headers:
Content-Type: application/json
POST /v1/map
Headers:
Content-Type: application/json
Body:
{
"session_id": "string",
"entities": ["ΠΊΠ½ΠΎΠΏΠΊΠ°", "ΡΠΎΡΠΌΠ°"],
"keyphrases": ["ΡΠΎΠ·Π΄Π°ΠΉΡΠ΅ ΠΊΠ½ΠΎΠΏΠΊΡ Π΄Π»Ρ ΠΎΡΠΏΡΠ°Π²ΠΊΠΈ ΡΠΎΡΠΌΡ"],
"template": "hero-main-footer"
}
cd Mod1_v2
docker compose up --build -d
# ΠΠΎΡΡΡΠΏΠ΅Π½ Π½Π° http://localhost:8080
cd Mod2-v1
docker compose up --build -d
# ΠΠΎΡΡΡΠΏΠ΅Π½ Π½Π° http://localhost:8001
cd Mod3-v1
docker compose up --build -d
# ΠΠΎΡΡΡΠΏΠ΅Π½ Π½Π° http://localhost:9001
# Mod1
curl http://localhost:8080/healthz
# Mod2
curl http://localhost:8001/healthz
# Mod3
curl http://localhost:9001/healthz
- FastAPI - Web framework
- Whisper - ASR engine
- SQLModel - ORM
- WebSocket - Real-time streaming
- httpx - HTTP client Π΄Π»Ρ Π΄ΠΎΡΡΠ°Π²ΠΊΠΈ
- FastAPI - Web framework
- Stanza - NLP pipeline
- RapidFuzz - Fuzzy matching
- SQLAlchemy - Async ORM
- Pydantic - Data validation
- jsonschema - Schema validation
- FastAPI - Web framework
- SQLAlchemy - ORM
- SQLite - ΠΠ°Π·Π° Π΄Π°Π½Π½ΡΡ
- RapidFuzz - Fuzzy matching
- Pydantic - Data validation
- Alembic - ΠΠΈΠ³ΡΠ°ΡΠΈΠΈ
Π ΠΈΡΠΎΠ³Π΅ ΡΠΈΡΡΠ΅ΠΌΠ° ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»ΡΠ΅Ρ:
- Π’ΡΠ°Π½ΡΠΊΡΠΈΠΏΡΠΈΡ Π°ΡΠ΄ΠΈΠΎ Π² ΡΠ΅ΠΊΡΡ
- Π‘ΡΡΡΠΊΡΡΡΠΈΡΠΎΠ²Π°Π½Π½ΡΠ΅ ΡΠ°Π½ΠΊΠΈ Π΄Π»Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ
- ΠΠ·Π²Π»Π΅ΡΠ΅Π½Π½ΡΠ΅ ΡΡΡΠ½ΠΎΡΡΠΈ ΠΈΠ· ΡΠ΅ΠΊΡΡΠ°
- Π‘ΠΎΠΏΠΎΡΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ Ρ Π²ΠΈΠ·ΡΠ°Π»ΡΠ½ΡΠΌΠΈ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°ΠΌΠΈ
- ΠΠΎΡΠΎΠ²ΡΠ΅ layout'Ρ Π΄Π»Ρ UI ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ²
- API Π΄Π»Ρ ΠΈΠ½ΡΠ΅Π³ΡΠ°ΡΠΈΠΈ Ρ Π²Π½Π΅ΡΠ½ΠΈΠΌΠΈ ΡΠΈΡΡΠ΅ΠΌΠ°ΠΌΠΈ
{
"session_id": "abc123",
"transcript": "Π‘ΠΎΠ·Π΄Π°ΠΉΡΠ΅ ΠΊΠ½ΠΎΠΏΠΊΡ Π΄Π»Ρ ΠΎΡΠΏΡΠ°Π²ΠΊΠΈ ΡΠΎΡΠΌΡ",
"layout": {
"template": "hero-main-footer",
"sections": {
"hero": [{"component": "Hero", "confidence": 1.0, "match_type": "default"}],
"main": [
{"component": "ui.button", "confidence": 1.0, "match_type": "exact"},
{"component": "ui.form", "confidence": 1.0, "match_type": "exact"}
],
"footer": []
},
"count": 3
},
"matches": [
{
"term": "ΠΊΠ½ΠΎΠΏΠΊΠ°",
"component": "ui.button",
"component_type": "ui.button",
"confidence": 1.0,
"match_type": "exact"
},
{
"term": "ΡΠΎΡΠΌΠ°",
"component": "ui.form",
"component_type": "ui.form",
"confidence": 1.0,
"match_type": "exact"
}
]
}