Текстовый файл из учебного комплекта, не свидетельство установленного runtime. В этом snapshot удалены credentials; исходная и очищенная SHA256 указаны в source manifest. Вернуться к учебнику · Исходный файл
1 Options name: 'web-rtc-komaroff' 2 3 NATS_USER = ENV.fetch('WEBRTC_NATS_USER') 4 NATS_PASS = ENV.fetch('WEBRTC_NATS_PASSWORD') 5 NATS_URL = "nats://[credentials-omitted]@web-rtc-komaroff_audio_nats:4222" 6 7 USER_ID = "user123" 8 NATS_EVENTS_SUBJECT = "nats.events." 9 NATS_AGENT_SUBJECT = "nats.agent." 10 NATS_AGENT_HISTORY_SUBJECT = "nats.agent.history." 11 NATS_LLM_SUBJECT = "nats.llm." 12 ASR_IN_PREFIX = "inference.whisper.stream." 13 ASR_OUT_PREFIX = "inference.whisper.text." 14 15 HOST = 'webrtc-komaroff.*' 16 VOICE_CHAT_HOST = 'voice-chat.*' 17 POSTGRES_HOST = 'web-rtc-komaroff_src_postgres' 18 POSTGRES_PORT = '5432' 19 POSTGRES_DB = 'mcp' 20 POSTGRES_USER = ENV.fetch('WEBRTC_POSTGRES_USER') 21 POSTGRES_PASSWORD = ENV.fetch('WEBRTC_POSTGRES_PASSWORD') 22 DATABASE_URL = "postgresql://#{POSTGRES_USER}:#{POSTGRES_PASSWORD}@#{POSTGRES_HOST}:#{POSTGRES_PORT}/#{POSTGRES_DB}?sslmode=disable" 23 NATS_TOOLS_PREFIX = 'nats.tools.' 24 NATS_REQUEST_TIMEOUT = '600' 25 AGENT_MAX_STEPS = '10' 26 MAX_STEPS = '10' 27 TIMEOUT_SECONDS = '600' 28 WORKFLOW_RUNTIME = ENV.fetch('WORKFLOW_RUNTIME', 'ruby_node') 29 NATS_WORKFLOW_RUN_SUBJECT = 30 case WORKFLOW_RUNTIME 31 when 'ruby' 32 'nats.workflow.run.ruby' 33 when 'ruby_node' 34 'nats.workflow.run.ruby.node' 35 else 36 'nats.workflow.run.python' 37 end 38 NATS_WORKFLOW_HEALTH_SUBJECT = 39 case WORKFLOW_RUNTIME 40 when 'ruby' 41 'nats.workflow.health.ruby' 42 when 'ruby_node' 43 'nats.workflow.health.ruby.node' 44 else 45 'nats.workflow.health.python' 46 end 47 webrtc_registry = ->(img){ "builder-registry.builder.giscloud.ru/gis/webrtc-komaroff/#{img}/main" } 48 registry =->(image) { "builder-registry.builder.giscloud.ru/#{image}" } 49 50 WHISPER_STACK_REVISION = '2026-04-22-whisper-sidecars-simplified-1' 51 WHISPER_FILE_JOB_STORAGE_DIR = 'локальный источник; 52 WHISPER_FILE_JOB_LOCK_URL = 'http://web-rtc-komaroff_whisper_file_job_lock:8091' 53 54 WHISPER_FILE_JOB_ENV = { 55 NATS_URL: NATS_URL, 56 NATS_EVENTS_SUBJECT: NATS_EVENTS_SUBJECT, 57 ASR_FILE_EVENT_PREFIX: 'inference.whisper.file.event.', 58 ASR_FILE_SESSION_EVENT_PREFIX: 'inference.whisper.file.session.', 59 LINTO_HTTP_URL: 'http://llm-models_linto_stt_whisper_http:80/transcribe', 60 LINTO_HTTP_PROXY_URL: 'http://web-rtc-komaroff_nats_proxy_linto:7000', 61 LINTO_HTTP_HOST: '', 62 LINTO_HTTP_LANGUAGE: 'ru', 63 FILE_JOB_STORAGE_DIR: WHISPER_FILE_JOB_STORAGE_DIR, 64 FILE_CHUNK_SECONDS: '300', 65 FILE_CHUNK_OVERLAP_SECONDS: '15', 66 FILE_CHUNK_RETRY_LIMIT: '2', 67 FILE_TEXT_SOFT_BREAK_S: '0.8', 68 FILE_TEXT_HARD_BREAK_S: '1.8', 69 FILE_JOB_LOCK_URL: WHISPER_FILE_JOB_LOCK_URL, 70 FILE_JOB_LOCK_TIMEOUT_S: '5', 71 FILE_JOB_LOCK_HEARTBEAT_S: '30', 72 WHISPER_STACK_REVISION: WHISPER_STACK_REVISION 73 } 74 75 WHISPER_WEBUI_ENV = { 76 WEBUI_ENABLED: '1', 77 WEBUI_FILE_TRANSCRIBE_ENABLED: '1', 78 WEBUI_FILE_MAX_UPLOAD_MB: '1024', 79 WEBUI_NATS_WS_URL: '/ws', 80 WEBUI_NATS_USER: NATS_USER, 81 WEBUI_NATS_PASS: NATS_PASS, 82 WEBUI_NATS_CLIENT_NAME: 'whisper-webui' 83 } 84 85 WHISPER_STREAM_ENV = { 86 ASR_IN_PREFIX: ASR_IN_PREFIX, 87 ASR_OUT_PREFIX: ASR_OUT_PREFIX, 88 LINTO_WS_URL: 'ws://192.168.28.134/streaming', 89 MIN_SPEECH_DURATION_MS: '250', 90 MIN_SILENCE_DURATION_MS: '500', 91 MAX_SPEECH_DURATION_S: '30.0', 92 SPEECH_PAD_MS: '30', 93 VAD_THRESHOLD: '0.8', 94 BUFFER_CHECK_INTERVAL_S: '1.0', 95 STREAM_BRIDGE_ENABLED: '1' 96 } 97 98 WHISPER_DIARIZATION_ENV = { 99 DIARIZATION_ENABLED: '1', 100 DIARIZATION_HTTP_URL: 'http://diarization-models_pyannote_diarization_shared:8080/diarize', 101 DIARIZATION_HTTP_PROXY_URL: 'http://web-rtc-komaroff_nats_proxy_pyannote_shared:7000', 102 DIARIZATION_HTTP_HOST: '', 103 DIARIZATION_HEALTH_URL: 'http://web-rtc-komaroff_nats_proxy_pyannote_shared:7000/healthz', 104 DIARIZATION_HEALTH_HOST: '', 105 DIARIZATION_HEALTH_TIMEOUT_S: '5', 106 DIARIZATION_TIMEOUT_S: '1800', 107 DIARIZATION_RETRY_LIMIT: '1', 108 DIARIZATION_AUDIO_FORMAT: 'auto', 109 DIARIZATION_WAV_MAX_UPLOAD_MB: '64', 110 DIARIZATION_AUDIO_MAX_UPLOAD_MB: '64', 111 DIARIZATION_NUM_SPEAKERS: '', 112 DIARIZATION_MIN_SPEAKERS: '2', 113 DIARIZATION_MAX_SPEAKERS: '8', 114 DIARIZATION_EXTRA_FIELDS_JSON: '{"runtime":"shared","segmentation_batch_size":"2","embedding_batch_size":"2"}', 115 DIARIZATION_TURN_BREAK_S: '1.0', 116 DIARIZATION_SYNTAX_POSTPROCESS_DEFAULT: '0', 117 DIARIZATION_SYNTAX_SENTENCE_GAP_S: '1.2', 118 DIARIZATION_SYNTAX_DOMINANCE_THRESHOLD: '0.65' 119 } 120 121 WHISPER_DIARIZATION_EVENT_ENV = { 122 ASR_FILE_DIAR_EVENT_PREFIX: 'inference.whisper.file.diar.event.', 123 ASR_FILE_DIAR_TEXT_EVENT_PREFIX: 'inference.whisper.file.diar_text.event.', 124 ASR_FILE_DIAR_TEXT_SESSION_EVENT_PREFIX: 'inference.whisper.file.diar_text.session.' 125 } 126 127 WHISPER_BENCH_CONFIGS_JSON = <<~JSON 128 [ 129 { 130 "id": "pyannote_selected_shared_b2_auto", 131 "case_id": "pyannote_diarization", 132 "label": "pyannote selected shared batch 2", 133 "engine": "pyannote", 134 "audio_format": "auto", 135 "min_speakers": 2, 136 "max_speakers": 8, 137 "diarization_http_url": "http://diarization-models_pyannote_diarization_shared:8080/diarize", 138 "diarization_http_proxy_url": "http://web-rtc-komaroff_nats_proxy_pyannote_shared:7000", 139 "diarization_http_host": "", 140 "diarization_health_url": "http://web-rtc-komaroff_nats_proxy_pyannote_shared:7000/healthz", 141 "diarization_health_host": "", 142 "runtime_label": "H100 MIG-750 shared with LinTO", 143 "segmentation_batch_size": 2, 144 "embedding_batch_size": 2, 145 "notes": "Default selected path: shared pyannote service, request-level batch 2, auto compact audio, Russian long-file speaker hints 2-8. Isolated uses its own configured NATS route." 146 }, 147 { 148 "id": "pyannote_selected_isolated_b2_auto", 149 "case_id": "pyannote_diarization", 150 "runtime": "isolated", 151 "label": "pyannote isolated batch 2", 152 "engine": "pyannote", 153 "audio_format": "auto", 154 "min_speakers": 2, 155 "max_speakers": 8, 156 "diarization_http_url": "http://diarization-models_pyannote_diarization_isolated:8080/diarize", 157 "diarization_http_proxy_url": "http://web-rtc-komaroff_nats_proxy_pyannote_isolated:7000", 158 "diarization_http_host": "", 159 "diarization_health_url": "http://web-rtc-komaroff_nats_proxy_pyannote_isolated:7000/healthz", 160 "diarization_health_host": "", 161 "runtime_label": "H100 isolated pyannote", 162 "segmentation_batch_size": 2, 163 "embedding_batch_size": 2 164 }, 165 { 166 "id": "sherpa_onnx_default_ru_check", 167 "case_id": "sherpa_onnx_diarization", 168 "label": "sherpa-onnx default Russian check", 169 "engine": "sherpa_onnx", 170 "audio_format": "auto", 171 "diarization_http_url": "http://diarization-models_sherpa_onnx_diarization:8080/diarize", 172 "diarization_http_proxy_url": "http://web-rtc-komaroff_nats_proxy_sherpa_onnx:7000", 173 "diarization_http_host": "", 174 "diarization_health_url": "http://web-rtc-komaroff_nats_proxy_sherpa_onnx:7000/healthz", 175 "diarization_health_host": "", 176 "runtime_label": "ONNX sidecar", 177 "cluster_threshold": "0.5", 178 "notes": "Real sherpa-onnx sidecar; used to validate CPU/ONNX throughput and speaker stability on Russian samples." 179 }, 180 { 181 "id": "sortformer_very_high_latency_b1_ru_check", 182 "case_id": "sortformer_diarization", 183 "label": "Sortformer very-high-latency batch 1 Russian check", 184 "engine": "nemo_sortformer", 185 "audio_format": "auto", 186 "diarization_http_url": "http://diarization-models_sortformer_diarization:8080/diarize", 187 "diarization_http_proxy_url": "http://web-rtc-komaroff_nats_proxy_sortformer:7000", 188 "diarization_http_host": "", 189 "diarization_health_url": "http://web-rtc-komaroff_nats_proxy_sortformer:7000/healthz", 190 "diarization_health_host": "", 191 "runtime_label": "H100 3g.47GB MIG shared with vLLM", 192 "sortformer_profile": "very_high_latency", 193 "batch_size": 1, 194 "num_workers": 0, 195 "chunk_len": 340, 196 "chunk_right_context": 40, 197 "fifo_len": 40, 198 "spkcache_update_period": 300, 199 "spkcache_len": 188, 200 "sortformer_window_s": 900, 201 "sortformer_overlap_s": 15, 202 "max_speakers": 4, 203 "notes": "Real NeMo Sortformer candidate for speed; very-high-latency profile, max four speakers, long files are chunked with overlap and Russian quality must be checked locally." 204 } 205 ] 206 JSON 207 208 LINTO_PROXY_SERVICE_ID = 'srv-gismaster-webrtc-linto-01' 209 PYANNOTE_SHARED_PROXY_SERVICE_ID = 'srv-gismaster-webrtc-pyannote-shared-01' 210 PYANNOTE_ISOLATED_PROXY_SERVICE_ID = 'srv-gismaster-webrtc-pyannote-isolated-01' 211 SHERPA_ONNX_PROXY_SERVICE_ID = 'srv-gismaster-webrtc-sherpa-onnx-01' 212 SORTFORMER_PROXY_SERVICE_ID = 'srv-gismaster-webrtc-sortformer-01' 213 214 # Internal HTTP requesters for H100 model backends. They have no ingress or 215 # published host ports; requests cross hosts through the existing inference 216 # NATS/leafnode path and terminate at matching H100-side receivers. 217 Service :nats_proxy_linto, image: 'dtorry/nats-proxy:latest', ports: 7000 do 218 env NATS_URL: NATS_URL 219 env NATS_STREAM: 'inference' 220 env NATS_RESPONSE_TIMEOUT: '1800' 221 env STREAM_RESPONSE_TIMEOUT: '1800' 222 env SERVICE_ID: LINTO_PROXY_SERVICE_ID 223 env SERVICE_ROLE: 'requester' 224 env NATS_REQUEST_SUBJECT_ROOT: 'to.inference.linto' 225 env NATS_RESPONSE_SUBJECT_ROOT: 'from.inference.linto' 226 env NATS_CONSUMER_NAME: "linto-gateway-#{LINTO_PROXY_SERVICE_ID}" 227 env NATS_JS_API_PREFIX: 'JS.INF.API' 228 # Cross-account stream imports do not return the JetStream publish ACK. 229 # The H100 receiver still consumes the request through JetStream. 230 env NATS_MODE: 'core' 231 env PROXY_AUTH_ENABLED: 'false' 232 end 233 234 Service :nats_proxy_pyannote_shared, image: 'dtorry/nats-proxy:latest', ports: 7000 do 235 env NATS_URL: NATS_URL 236 env NATS_STREAM: 'inference' 237 env NATS_RESPONSE_TIMEOUT: '1800' 238 env STREAM_RESPONSE_TIMEOUT: '1800' 239 env SERVICE_ID: PYANNOTE_SHARED_PROXY_SERVICE_ID 240 env SERVICE_ROLE: 'requester' 241 env NATS_REQUEST_SUBJECT_ROOT: 'to.inference.pyannote.shared' 242 env NATS_RESPONSE_SUBJECT_ROOT: 'from.inference.pyannote.shared' 243 env NATS_CONSUMER_NAME: "pyannote-shared-gateway-#{PYANNOTE_SHARED_PROXY_SERVICE_ID}" 244 env NATS_JS_API_PREFIX: 'JS.INF.API' 245 # The request is transient and the H100 receiver remains the durable 246 # JetStream consumer. Core mode avoids waiting for a cross-account publish 247 # ACK before the already-running receiver can answer. 248 env NATS_MODE: 'core' 249 env PROXY_AUTH_ENABLED: 'false' 250 end 251 252 Service :nats_proxy_pyannote_isolated, image: 'dtorry/nats-proxy:latest' do 253 env NATS_URL: NATS_URL 254 env NATS_STREAM: 'inference' 255 env NATS_RESPONSE_TIMEOUT: '1800' 256 env STREAM_RESPONSE_TIMEOUT: '1800' 257 env SERVICE_ID: PYANNOTE_ISOLATED_PROXY_SERVICE_ID 258 env SERVICE_ROLE: 'requester' 259 env NATS_REQUEST_SUBJECT_ROOT: 'to.inference.pyannote.isolated' 260 env NATS_RESPONSE_SUBJECT_ROOT: 'from.inference.pyannote.isolated' 261 env NATS_CONSUMER_NAME: "pyannote-isolated-gateway-#{PYANNOTE_ISOLATED_PROXY_SERVICE_ID}" 262 env NATS_JS_API_PREFIX: 'JS.INF.API' 263 env NATS_MODE: 'core' 264 env PROXY_AUTH_ENABLED: 'false' 265 end 266 267 Service :nats_proxy_sherpa_onnx, image: 'dtorry/nats-proxy:latest' do 268 env NATS_URL: NATS_URL 269 env NATS_STREAM: 'inference' 270 env NATS_RESPONSE_TIMEOUT: '1800' 271 env STREAM_RESPONSE_TIMEOUT: '1800' 272 env SERVICE_ID: SHERPA_ONNX_PROXY_SERVICE_ID 273 env SERVICE_ROLE: 'requester' 274 env NATS_REQUEST_SUBJECT_ROOT: 'to.inference.sherpa.onnx' 275 env NATS_RESPONSE_SUBJECT_ROOT: 'from.inference.sherpa.onnx' 276 env NATS_CONSUMER_NAME: "sherpa-onnx-gateway-#{SHERPA_ONNX_PROXY_SERVICE_ID}" 277 env NATS_JS_API_PREFIX: 'JS.INF.API' 278 env NATS_MODE: 'core' 279 env PROXY_AUTH_ENABLED: 'false' 280 end 281 282 Service :nats_proxy_sortformer, image: 'dtorry/nats-proxy:latest' do 283 env NATS_URL: NATS_URL 284 env NATS_STREAM: 'inference' 285 env NATS_RESPONSE_TIMEOUT: '1800' 286 env STREAM_RESPONSE_TIMEOUT: '1800' 287 env SERVICE_ID: SORTFORMER_PROXY_SERVICE_ID 288 env SERVICE_ROLE: 'requester' 289 env NATS_REQUEST_SUBJECT_ROOT: 'to.inference.sortformer' 290 env NATS_RESPONSE_SUBJECT_ROOT: 'from.inference.sortformer' 291 env NATS_CONSUMER_NAME: "sortformer-gateway-#{SORTFORMER_PROXY_SERVICE_ID}" 292 env NATS_JS_API_PREFIX: 'JS.INF.API' 293 env NATS_MODE: 'core' 294 env PROXY_AUTH_ENABLED: 'false' 295 end 296 297 PublishPorts core: [ 298 { target: 19303, published: 19303, protocol: 'udp', mode: 'host' } 299 ] 300 301 # Core backend for browser sessions: handles /core, WebRTC-facing orchestration, 302 # and coordinates agent / ASR requests through NATS and the API gateway. 303 Service :src_core, image: webrtc_registry['core']do 304 ingress host: HOST, path: '/core', port: 8000 305 306 env NATS_URL: NATS_URL, 307 NATS_EVENTS_SUBJECT: NATS_EVENTS_SUBJECT, 308 NATS_AGENT_SUBJECT: NATS_AGENT_SUBJECT, 309 ASR_IN_PREFIX: ASR_IN_PREFIX, 310 ASR_OUT_PREFIX: ASR_OUT_PREFIX, 311 API_URL: 'http://web-rtc-komaroff_src_api_gateway:8100/api', 312 NATS_REQUEST_TIMEOUT: NATS_REQUEST_TIMEOUT, 313 STACK_SERVICE_NAME: 'src_core', 314 USER_ID: USER_ID, 315 CORE_HOST: '0.0.0.0', 316 CORE_PORT: 8000 317 end 318 319 # Internal REST facade for domain data used by core/agent/runtime services. 320 Service :src_api_gateway, image: webrtc_registry['api-gateway']do 321 ingress host: HOST, path: '/api', port: 8100 322 323 env API_HOST: '0.0.0.0', 324 API_PORT: 8100, 325 NATS_URL: NATS_URL, 326 NATS_TOOLS_PREFIX: NATS_TOOLS_PREFIX 327 end 328 329 # Main agent runtime: consumes agent requests from NATS and calls tools/workflows/LLM. 330 Service :src_agent, image: webrtc_registry['agent'] do 331 env STACK_SERVICE_NAME: 'src_agent', 332 USER_ID: USER_ID, 333 NATS_URL: NATS_URL, 334 NATS_EVENTS_SUBJECT: NATS_EVENTS_SUBJECT, 335 NATS_AGENT_SUBJECT: NATS_AGENT_SUBJECT, 336 NATS_LLM_SUBJECT: NATS_LLM_SUBJECT, 337 NATS_WORKFLOW_RUN_SUBJECT: NATS_WORKFLOW_RUN_SUBJECT, 338 WORKFLOW_TIMEOUT_SECONDS: '270', # Two sequential LLM calls (120s each) plus tool/transport headroom. 339 AGENT_MAX_STEPS: AGENT_MAX_STEPS, 340 MAX_STEPS: MAX_STEPS, 341 TIMEOUT_SECONDS: TIMEOUT_SECONDS, 342 DATABASE_URL: DATABASE_URL, 343 API_URL: 'http://web-rtc-komaroff_src_api_gateway:8100/api', 344 AIRPORTS_API_URL: 'http://web-rtc-komaroff_src_api_gateway:8100/api/airports/search', 345 RUNWAYS_API_URL: 'http://web-rtc-komaroff_src_api_gateway:8100/api/airports', 346 PILOT_API_URL: 'http://web-rtc-komaroff_src_api_gateway:8100/api/pilot/location', 347 ROUTES_API_URL: 'http://web-rtc-komaroff_src_api_gateway:8100/api/routes/nearest', 348 WEATHER_API_URL: 'http://web-rtc-komaroff_src_api_gateway:8100/api/weather', 349 FLIGHTS_API_URL: 'http://web-rtc-komaroff_src_api_gateway:8100/api/flights/status' 350 end 351 352 # LLM adapter service for the agent layer. In current config it points at remote Ollama. 353 Service :src_llm, image: webrtc_registry['llm'] do 354 env STACK_SERVICE_NAME: 'src_llm', 355 USER_ID: USER_ID, 356 NATS_URL: NATS_URL, 357 NATS_EVENTS_SUBJECT: NATS_EVENTS_SUBJECT, 358 NATS_LLM_SUBJECT: NATS_LLM_SUBJECT, 359 OLLAMA_URL: 'https://nats-proxy-ollama.gis-master.ru', 360 LLM_MODE: 'remote', 361 LLM_LOCAL_MODEL: 'Qwen/Qwen3-1.7B-GGUF', 362 LLM_REMOTE_MODEL: 'qwen3:30b', 363 LLM_MODELS: '/app/models/llm', 364 OLLAMA_MODEL_FILE: '', 365 LLM_CONTEXT_SIZE: '8192' 366 volume name: 'LLM_MODELS', target: '/app/models/llm' 367 end 368 369 # Python workflow runner. Used only when WORKFLOW_RUNTIME selects the python subjects. 370 Service :src_langgraph, image: webrtc_registry['langgraph'] do 371 env STACK_SERVICE_NAME: 'src_langgraph', 372 USER_ID: USER_ID, 373 NATS_URL: NATS_URL, 374 NATS_LLM_SUBJECT: NATS_LLM_SUBJECT, 375 NATS_TOOLS_PREFIX: NATS_TOOLS_PREFIX, 376 NATS_WORKFLOW_RUN_SUBJECT: 'nats.workflow.run.python', 377 NATS_WORKFLOW_HEALTH_SUBJECT: 'nats.workflow.health.python', 378 NATS_REQUEST_TIMEOUT_SECONDS: '120', 379 MAX_CONCURRENCY: '8' 380 end 381 382 # Ruby workflow runner. Used only when WORKFLOW_RUNTIME selects the ruby subjects. 383 Service :src_langgraph_rb, image: webrtc_registry['langgraph-rb'] do 384 env STACK_SERVICE_NAME: 'src_langgraph_rb', 385 USER_ID: USER_ID, 386 NATS_URL: NATS_URL, 387 NATS_LLM_SUBJECT: NATS_LLM_SUBJECT, 388 NATS_TOOLS_PREFIX: NATS_TOOLS_PREFIX, 389 NATS_WORKFLOW_RUN_SUBJECT: 'nats.workflow.run.ruby', 390 NATS_WORKFLOW_HEALTH_SUBJECT: 'nats.workflow.health.ruby', 391 NATS_REQUEST_TIMEOUT_SECONDS: '120' 392 393 command ['bundle', 'exec', 'ruby', './lib/src_langgraph_rb/runtime/service.rb'] 394 end 395 396 # Ruby+Node workflow runner. This is the active path when WORKFLOW_RUNTIME=ruby_node. 397 Service :src_langgraph_rb_node, image: webrtc_registry['langgraph-rb-node'] do 398 env STACK_SERVICE_NAME: 'src_langgraph_rb_node', 399 USER_ID: USER_ID, 400 NATS_URL: NATS_URL, 401 NATS_LLM_SUBJECT: NATS_LLM_SUBJECT, 402 NATS_TOOLS_PREFIX: NATS_TOOLS_PREFIX, 403 NATS_WORKFLOW_RUN_SUBJECT: 'nats.workflow.run.ruby.node', 404 NATS_WORKFLOW_HEALTH_SUBJECT: 'nats.workflow.health.ruby.node', 405 NATS_REQUEST_TIMEOUT_SECONDS: '120' 406 407 command ['bundle', 'exec', 'ruby', './lib/src_langgraph_rb_node/runtime/service.rb'] 408 end 409 410 # Browser frontend for the voice chat app. Serves UI and talks to core/NATS. 411 Service :src_front, image: webrtc_registry['front'] do 412 ingress host: HOST, port: 8000 413 414 env FRONT_NATS_EVENTS_SUBJECT: "#{NATS_EVENTS_SUBJECT}#{USER_ID}", 415 FRONT_NATS_AGENT_SUBJECT: "#{NATS_AGENT_SUBJECT}#{USER_ID}", 416 FRONT_NATS_WS_UPSTREAM: 'web-rtc-komaroff_audio_nats', 417 FRONT_NATS_USER: NATS_USER, 418 FRONT_NATS_PASS: NATS_PASS, 419 FRONT_NATS_CLIENT_NAME: 'src_front', 420 FRONT_CORE_ORIGIN: 'http://web-rtc-komaroff_src_core:8000', 421 COM_GITLAB_CI_PIPELINE_IID: ENV['CI_PIPELINE_ID'].to_s, 422 FRONT_MESSAGE_REQUEST_TIMEOUT_MS: '300000' # Longer than the Agent workflow deadline. 423 end 424 425 # Persistent PostgreSQL for MCP / workflow / application state. 426 Service :src_postgres, image: webrtc_registry['postgres'] do 427 env POSTGRES_DB: POSTGRES_DB, 428 POSTGRES_USER: POSTGRES_USER, 429 POSTGRES_PASSWORD: POSTGRES_PASSWORD 430 431 volume target: '/var/lib/postgresql/data' 432 433 # оставляем твой параметр 434 command ['postgres', '--wal_level=logical'] 435 436 ingress host_sni: "db.#{HOST}", port: 5432, entrypoints: 'postgres' 437 end 438 439 440 441 # GisMaster client 442 # to.inference.ollama.requests.?? <- json (Ollama Rest API) reply: inference.ollama.responses.INBOX.12312312321 443 # from.inference.ollama.responses.<id> ?? -> json (Ollama Rest API) 444 445 # to.inference.whisper.stream.<sid> <- chunks 446 # from.inference.whisper.text.<sid> -> text 447 448 # Browser-facing NATS gateway: exposes /ws and bridges local websocket clients 449 # into the inference account through a leafnode. 450 Service :audio_nats, image: 'nats:2.11.6-scratch' do 451 ingress host: HOST, path: '/ws', path_sub: ['^/ws(.*)', '$1'], port: 9222 452 453 command ['-c', '/nats.conf'] 454 config target: '/nats.conf', file_content: <<~CONF 455 jetstream: false 456 websocket: { listen: 0.0.0.0:9222, no_tls: true, compression: true } 457 458 debug: true 459 trace: true 460 461 accounts: { 462 local: { 463 users: [ 464 { user: #{NATS_USER}, password: #{NATS_PASS}, allowed_connection_types: [ STANDARD, WEBSOCKET ] } 465 ] 466 exports: [ 467 { stream: "to.inference.>" } 468 ] 469 imports: [ 470 { stream: { account: inference, subject: "inference.>" }, to: "from.inference.>" } 471 { service: { account: inference, subject: "$JS.dinf.API.>" }, to: "JS.INF.API.>" } 472 ] 473 } 474 475 inference: { 476 exports: [ 477 { stream: "inference.>" } 478 { service: "$JS.dinf.API.>" } 479 ] 480 imports: [ 481 { stream: { account: local, subject: "to.inference.>" }, to: "inference.>" } 482 ] 483 jetstream: enabled 484 } 485 } 486 487 leafnodes: { 488 remotes: [ 489 # Inference 490 { 491 # url: nats://h100.local:37422, 492 url: nats://192.168.28.134:37422, 493 account: inference, 494 nkey: #{ENV.fetch('WEBRTC_NATS_LEAFNODE_NKEY')} 495 } 496 ] 497 } 498 CONF 499 end 500 501 502 # Shared guard used by all Whisper upload/transcription/diarization pages. The UI 503 # polls it, and each backend runner also enforces it before model work starts. 504 Service :whisper_file_job_lock, image: webrtc_registry['voice-chat/stt_whisper_to_nats'] do 505 env HOST_SERVING_HOST: '0.0.0.0', 506 HOST_SERVING_PORT: '8091', 507 FILE_JOB_LOCK_TTL_S: '7200', 508 STACK_SERVICE_NAME: 'whisper_file_job_lock' 509 510 entrypoint 'python -m src.webui.job_lock_server' 511 end 512 513 # Plain transcription service. Same binary as py_faster_whisper_diarize, but this 514 # deployment serves /whisper-trasncription, keeps the live stream bridge on, and disables diarization. 515 Service :py_faster_whisper, image: webrtc_registry['voice-chat/stt_whisper_to_nats'] do 516 ingress host: HOST, path: '/whisper-trasncription', port: 8090 517 518 env WHISPER_FILE_JOB_ENV.merge(WHISPER_WEBUI_ENV).merge(WHISPER_STREAM_ENV).merge( 519 DIARIZATION_ENABLED: '0', 520 WEBUI_BASE_PATH: '/whisper-trasncription', 521 WEBUI_PAGE_MODE: 'transcribe_only', 522 STACK_SERVICE_NAME: 'stt_whisper_to_nats' 523 ) 524 525 entrypoint 'python -m src.stt_whisper_to_nats' 526 end 527 528 # Combined file ASR + speaker separation service. Same binary as py_faster_whisper, 529 # but this deployment serves /whisper-diarization, runs file-mode only, and enables diarization. 530 Service :py_faster_whisper_diarize, image: webrtc_registry['voice-chat/stt_whisper_to_nats'] do 531 ingress host: HOST, path: '/whisper-diarization', port: 8090 532 533 env WHISPER_FILE_JOB_ENV.merge(WHISPER_WEBUI_ENV).merge(WHISPER_DIARIZATION_EVENT_ENV).merge(WHISPER_DIARIZATION_ENV).merge( 534 STREAM_BRIDGE_ENABLED: '0', 535 WEBUI_BASE_PATH: '/whisper-diarization', 536 WEBUI_PAGE_MODE: 'diarize', 537 WEBUI_HOME_LINK_PATH: '/whisper-trasncription', 538 STACK_SERVICE_NAME: 'stt_whisper_to_nats_diarize' 539 ) 540 541 entrypoint 'python -m src.stt_whisper_to_nats' 542 end 543 544 # Staged file ASR + speaker separation analysis service. Same binary as the other 545 # py_faster_whisper deployments, but this deployment serves /whisper-staged and 546 # exposes isolated Transcribe / Diarize / Combine steps on cached artifacts. 547 Service :py_faster_whisper_staged, image: webrtc_registry['voice-chat/stt_whisper_to_nats'] do 548 ingress host: HOST, path: '/whisper-staged', port: 8090 549 550 env WHISPER_FILE_JOB_ENV.merge(WHISPER_WEBUI_ENV).merge(WHISPER_DIARIZATION_EVENT_ENV).merge(WHISPER_DIARIZATION_ENV).merge( 551 STREAM_BRIDGE_ENABLED: '0', 552 WEBUI_BASE_PATH: '/whisper-staged', 553 WEBUI_PAGE_MODE: 'staged', 554 WEBUI_HOME_LINK_PATH: '/whisper-diarization', 555 STACK_SERVICE_NAME: 'stt_whisper_to_nats_staged' 556 ) 557 558 entrypoint 'python -m src.stt_whisper_to_nats' 559 end 560 561 # Benchmark UI for non-streaming transcription + diarization experiments. It 562 # keeps results separate from /whisper-staged while using the same deployed image. 563 Service :py_faster_whisper_bench, image: webrtc_registry['voice-chat/stt_whisper_to_nats'] do 564 ingress host: HOST, path: '/whisper-bench', port: 8090 565 566 env WHISPER_FILE_JOB_ENV.merge(WHISPER_WEBUI_ENV).merge(WHISPER_DIARIZATION_EVENT_ENV).merge(WHISPER_DIARIZATION_ENV).merge( 567 STREAM_BRIDGE_ENABLED: '0', 568 WEBUI_BASE_PATH: '/whisper-bench', 569 WEBUI_PAGE_MODE: 'bench', 570 WEBUI_HOME_LINK_PATH: '/whisper-staged', 571 FILE_JOB_STORAGE_DIR: 'локальный источник;, 572 BENCH_CONFIGS_JSON: WHISPER_BENCH_CONFIGS_JSON, 573 STACK_SERVICE_NAME: 'stt_whisper_to_nats_bench' 574 ) 575 576 volume name: 'whisper_bench_jobs', target: 'локальный источник; 577 entrypoint 'python -m src.stt_whisper_to_nats' 578 end 579 580 # Main page for all tried non-streaming transcription + diarization approaches. 581 # The case pages under /whisper-summary/cases/<case-id> reuse the same 582 # runner and result store as /whisper-bench, so measured stats stay comparable. 583 Service :py_faster_whisper_summary, image: webrtc_registry['voice-chat/stt_whisper_to_nats'] do 584 ingress host: HOST, path: '/whisper-summary', port: 8090 585 586 env WHISPER_FILE_JOB_ENV.merge(WHISPER_WEBUI_ENV).merge(WHISPER_DIARIZATION_EVENT_ENV).merge(WHISPER_DIARIZATION_ENV).merge( 587 STREAM_BRIDGE_ENABLED: '0', 588 WEBUI_BASE_PATH: '/whisper-summary', 589 WEBUI_PAGE_MODE: 'summary', 590 WEBUI_HOME_LINK_PATH: '/whisper-summary', 591 FILE_JOB_STORAGE_DIR: 'локальный источник;, 592 BENCH_CONFIGS_JSON: WHISPER_BENCH_CONFIGS_JSON, 593 STACK_SERVICE_NAME: 'stt_whisper_to_nats_summary' 594 ) 595 596 volume name: 'whisper_bench_jobs', target: 'локальный источник; 597 entrypoint 'python -m src.stt_whisper_to_nats' 598 end 599 600 # Fast browser page for checking that the voice-chat runtime and its rag-stack 601 # dependencies are alive enough for the real transcription/diarization flows. 602 Service :py_faster_whisper_status, image: webrtc_registry['voice-chat/stt_whisper_to_nats'] do 603 ingress host: HOST, path: '/status', port: 8090 604 ingress host: HOST, path: '/health', path_sub: ['^/health(.*)', '/status$1'], port: 8090 605 deploy_label 'traefik.http.routers.web-rtc-komaroff_py_faster_whisper_status-0.priority=1000' 606 deploy_label 'traefik.http.routers.web-rtc-komaroff_py_faster_whisper_status-1.priority=1000' 607 608 env WHISPER_FILE_JOB_ENV.merge(WHISPER_WEBUI_ENV).merge(WHISPER_DIARIZATION_ENV).merge( 609 STREAM_BRIDGE_ENABLED: '0', 610 WEBUI_FILE_TRANSCRIBE_ENABLED: '0', 611 WEBUI_BASE_PATH: '/status', 612 WEBUI_PAGE_MODE: 'diagnostics', 613 WEBUI_HOME_LINK_PATH: '/status', 614 WEBUI_NATS_CLIENT_NAME: 'voice-chat-diagnostics', 615 STACK_SERVICE_NAME: 'voice_chat_status', 616 DIAGNOSTICS_NATS_CLIENT_NAME: 'voice-chat-diagnostics', 617 NATS_AGENT_HISTORY_SUBJECT: NATS_AGENT_HISTORY_SUBJECT, 618 NATS_WORKFLOW_HEALTH_SUBJECT: NATS_WORKFLOW_HEALTH_SUBJECT, 619 NATS_LLM_SUBJECT: NATS_LLM_SUBJECT, 620 DIAGNOSTICS_TIMEOUT_S: '5', 621 DIAGNOSTICS_SMOKE_TIMEOUT_S: '20', 622 DIAGNOSTICS_FRONT_URL: 'http://web-rtc-komaroff_src_front:8000/', 623 DIAGNOSTICS_CORE_URL: 'http://web-rtc-komaroff_src_core:8000/core', 624 DIAGNOSTICS_API_GATEWAY_URL: 'http://web-rtc-komaroff_src_api_gateway:8100/api/pilot/location', 625 DIAGNOSTICS_FILE_JOB_LOCK_URL: 'http://web-rtc-komaroff_whisper_file_job_lock:8091/health', 626 DIAGNOSTICS_WHISPER_TRANSCRIBE_URL: 'http://web-rtc-komaroff_py_faster_whisper:8090/whisper-trasncription/health', 627 DIAGNOSTICS_WHISPER_DIARIZE_URL: 'http://web-rtc-komaroff_py_faster_whisper_diarize:8090/whisper-diarization/health', 628 DIAGNOSTICS_WHISPER_STAGED_URL: 'http://web-rtc-komaroff_py_faster_whisper_staged:8090/whisper-staged/health', 629 DIAGNOSTICS_WHISPER_BENCH_URL: 'http://web-rtc-komaroff_py_faster_whisper_bench:8090/whisper-bench/health', 630 DIAGNOSTICS_WHISPER_SUMMARY_URL: 'http://web-rtc-komaroff_py_faster_whisper_summary:8090/whisper-summary/health', 631 # Absolute-form HTTP proxy requests reach LinTO on H100; a direct 632 # /healthcheck on nats-proxy only checks the proxy's own Rack process. 633 DIAGNOSTICS_LINTO_HTTP_HEALTH_URL: 'http://llm-models_linto_stt_whisper_http:80/healthcheck', 634 DIAGNOSTICS_LINTO_HTTP_HEALTH_PROXY_URL: 'http://web-rtc-komaroff_nats_proxy_linto:7000', 635 DIAGNOSTICS_PYANNOTE_SHARED_HEALTH_URL: 'http://web-rtc-komaroff_nats_proxy_pyannote_shared:7000/healthz', 636 DIAGNOSTICS_PYANNOTE_SHARED_HEALTH_HOST: '', 637 DIAGNOSTICS_PYANNOTE_SHARED_DIARIZE_URL: 'http://diarization-models_pyannote_diarization_shared:8080/diarize', 638 DIAGNOSTICS_PYANNOTE_SHARED_DIARIZE_PROXY_URL: 'http://web-rtc-komaroff_nats_proxy_pyannote_shared:7000', 639 DIAGNOSTICS_PYANNOTE_SHARED_DIARIZE_HOST: '', 640 DIAGNOSTICS_PYANNOTE_ISOLATED_HEALTH_URL: 'http://diarization-models_pyannote_diarization_isolated:8080/healthz', 641 DIAGNOSTICS_PYANNOTE_ISOLATED_HEALTH_PROXY_URL: 'http://web-rtc-komaroff_nats_proxy_pyannote_isolated:7000', 642 DIAGNOSTICS_PYANNOTE_ISOLATED_DIARIZE_URL: 'http://diarization-models_pyannote_diarization_isolated:8080/diarize', 643 DIAGNOSTICS_PYANNOTE_ISOLATED_DIARIZE_PROXY_URL: 'http://web-rtc-komaroff_nats_proxy_pyannote_isolated:7000', 644 DIAGNOSTICS_SHERPA_ONNX_HEALTH_URL: 'http://diarization-models_sherpa_onnx_diarization:8080/healthz', 645 DIAGNOSTICS_SHERPA_ONNX_HEALTH_PROXY_URL: 'http://web-rtc-komaroff_nats_proxy_sherpa_onnx:7000', 646 DIAGNOSTICS_SHERPA_ONNX_DIARIZE_URL: 'http://diarization-models_sherpa_onnx_diarization:8080/diarize', 647 DIAGNOSTICS_SHERPA_ONNX_DIARIZE_PROXY_URL: 'http://web-rtc-komaroff_nats_proxy_sherpa_onnx:7000', 648 DIAGNOSTICS_SORTFORMER_HEALTH_URL: 'http://diarization-models_sortformer_diarization:8080/healthz', 649 DIAGNOSTICS_SORTFORMER_HEALTH_PROXY_URL: 'http://web-rtc-komaroff_nats_proxy_sortformer:7000', 650 DIAGNOSTICS_SORTFORMER_DIARIZE_URL: 'http://diarization-models_sortformer_diarization:8080/diarize', 651 DIAGNOSTICS_SORTFORMER_DIARIZE_PROXY_URL: 'http://web-rtc-komaroff_nats_proxy_sortformer:7000', 652 DIAGNOSTICS_H100_INGRESS_INFORMATIONAL: '1' 653 ) 654 655 entrypoint 'python -m src.stt_whisper_to_nats' 656 end 657 658 659 660 # Debug shell for manual NATS inspection inside the stack. 661 Service :nats_box, image: 'natsio/nats-box' do 662 env NATS_URL: NATS_URL 663 entrypoint 'sh -c "sleep infinity"' 664 end 665 666 # Admin UI for inspecting NATS subjects/connections/messages. 667 Service :nats_ui, image: registry['nats-ui/main:latest'], ports: 7000 do 668 ingress host: "ns.#{HOST}", basic_auth: ENV.fetch('WEBRTC_NATS_UI_BASIC_AUTH') 669 env NATS_URL: NATS_URL 670 end 671 672 # Admin UI for browsing the PostgreSQL database. 673 Service :pgweb_mcp, image: 'sosedoff/pgweb:0.17.0', env: { PGWEB_DATABASE_URL: DATABASE_URL }, 674 ingress: { host: "pgweb.#{HOST}", port: 8081, basic_auth: ENV.fetch('WEBRTC_PGWEB_BASIC_AUTH') }