karaoke-gen 0.96.0__py3-none-any.whl → 0.101.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. backend/api/routes/admin.py +696 -92
  2. backend/api/routes/audio_search.py +29 -8
  3. backend/api/routes/file_upload.py +99 -22
  4. backend/api/routes/health.py +65 -0
  5. backend/api/routes/internal.py +6 -0
  6. backend/api/routes/jobs.py +28 -1
  7. backend/api/routes/review.py +13 -6
  8. backend/api/routes/tenant.py +120 -0
  9. backend/api/routes/users.py +472 -51
  10. backend/main.py +31 -2
  11. backend/middleware/__init__.py +7 -1
  12. backend/middleware/tenant.py +192 -0
  13. backend/models/job.py +19 -3
  14. backend/models/tenant.py +208 -0
  15. backend/models/user.py +18 -0
  16. backend/services/email_service.py +253 -6
  17. backend/services/encoding_service.py +128 -31
  18. backend/services/firestore_service.py +6 -0
  19. backend/services/job_manager.py +44 -2
  20. backend/services/langfuse_preloader.py +98 -0
  21. backend/services/nltk_preloader.py +122 -0
  22. backend/services/spacy_preloader.py +65 -0
  23. backend/services/stripe_service.py +133 -11
  24. backend/services/tenant_service.py +285 -0
  25. backend/services/user_service.py +85 -7
  26. backend/tests/emulator/conftest.py +22 -1
  27. backend/tests/emulator/test_made_for_you_integration.py +167 -0
  28. backend/tests/test_admin_job_files.py +337 -0
  29. backend/tests/test_admin_job_reset.py +384 -0
  30. backend/tests/test_admin_job_update.py +326 -0
  31. backend/tests/test_email_service.py +233 -0
  32. backend/tests/test_impersonation.py +223 -0
  33. backend/tests/test_job_creation_regression.py +4 -0
  34. backend/tests/test_job_manager.py +171 -9
  35. backend/tests/test_jobs_api.py +11 -1
  36. backend/tests/test_made_for_you.py +2086 -0
  37. backend/tests/test_models.py +139 -0
  38. backend/tests/test_spacy_preloader.py +119 -0
  39. backend/tests/test_tenant_api.py +350 -0
  40. backend/tests/test_tenant_middleware.py +345 -0
  41. backend/tests/test_tenant_models.py +406 -0
  42. backend/tests/test_tenant_service.py +418 -0
  43. backend/utils/test_data.py +27 -0
  44. backend/workers/screens_worker.py +16 -6
  45. backend/workers/video_worker.py +8 -3
  46. {karaoke_gen-0.96.0.dist-info → karaoke_gen-0.101.0.dist-info}/METADATA +1 -1
  47. {karaoke_gen-0.96.0.dist-info → karaoke_gen-0.101.0.dist-info}/RECORD +58 -39
  48. lyrics_transcriber/correction/agentic/agent.py +17 -6
  49. lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +96 -43
  50. lyrics_transcriber/correction/agentic/providers/model_factory.py +27 -6
  51. lyrics_transcriber/correction/anchor_sequence.py +151 -37
  52. lyrics_transcriber/correction/handlers/syllables_match.py +44 -2
  53. lyrics_transcriber/correction/phrase_analyzer.py +18 -0
  54. lyrics_transcriber/frontend/src/api.ts +13 -5
  55. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +90 -57
  56. {karaoke_gen-0.96.0.dist-info → karaoke_gen-0.101.0.dist-info}/WHEEL +0 -0
  57. {karaoke_gen-0.96.0.dist-info → karaoke_gen-0.101.0.dist-info}/entry_points.txt +0 -0
  58. {karaoke_gen-0.96.0.dist-info → karaoke_gen-0.101.0.dist-info}/licenses/LICENSE +0 -0
@@ -7,25 +7,28 @@ backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  backend/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  backend/api/dependencies.py,sha256=-61nHBhiihUDSVMQd3VuHLP7uvKrUbm1y-j9RmV6_zc,16871
9
9
  backend/api/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- backend/api/routes/admin.py,sha256=QJ2B-90HySVd8la1DN2IGeKPq6Qzj4TVFZhgZOOoKBo,26060
11
- backend/api/routes/audio_search.py,sha256=wJ_5j_q3yaHs5W03M2B5FdTYUrAVqHV5tW0cBdRrzxI,39153
10
+ backend/api/routes/admin.py,sha256=GI4ANzshBUfAzMdRH3M8cUBnzsCbrL1owRXmIY5HK8A,46506
11
+ backend/api/routes/audio_search.py,sha256=CK2lVaM8RDP7i61XEtLeS0AJDfVJSy0ctnduK-ew-hQ,40129
12
12
  backend/api/routes/auth.py,sha256=G1U2KwS3uqBJpgvg2PIe_mZOWCJKPFhhewrmKbW3Z_s,11531
13
- backend/api/routes/file_upload.py,sha256=Skh4ewXo5I8XReXhXlJZO7tUbvdwz1YdNxFmJqYIFnM,92290
14
- backend/api/routes/health.py,sha256=6HSLJVtNaYHA-xaXgHsXMZUJayYYXXNdX-oThDLDCcw,11609
15
- backend/api/routes/internal.py,sha256=N_Fv0hYQ9gleYNehjHaZb1OKzM78PXDbcqTmHasll2w,15276
16
- backend/api/routes/jobs.py,sha256=xAIbcNrYLHlWYHvsv1C5BakdvgXdN5mpa_RRzJ3QSeI,59905
17
- backend/api/routes/review.py,sha256=hEvLUcb07DWcBc6E4fTa91zam3Z_a-x2vIvLP_OKR9o,28256
13
+ backend/api/routes/file_upload.py,sha256=nEl06-ZqkKZNzyNZh0RRtuOBox54XvdEOFkYtnSqpv8,95828
14
+ backend/api/routes/health.py,sha256=iZlhJpmz4vminUy_qrzkyk_1tgG1gmITz4jEWaCWJnM,14010
15
+ backend/api/routes/internal.py,sha256=yqzd5V8xGJQ_vTZL85vKIMwmBXf1MIp1nhder-kQEuA,15683
16
+ backend/api/routes/jobs.py,sha256=7i3uAOL9p4cNk1vJHJIIYCBkcJFaJFl6o3iq92Z9KFo,61218
17
+ backend/api/routes/review.py,sha256=BiaXZs-NXl7AVzGiwBzIhzm60fUslcQagSw4XuUByhU,28842
18
+ backend/api/routes/tenant.py,sha256=sM0WVXWGKJeyBNMbPDnAKOD9-SkJBmH4RkMXVWz4dlM,4038
18
19
  backend/api/routes/themes.py,sha256=_fPZg9N2KN-yyr1YR2vAy8FIm8PqVowboQ4WEpFTYiQ,4721
19
- backend/api/routes/users.py,sha256=1EaBzR4qEzUklC3P3Yz-15Usm9tys3VPee0azmKzLj8,34355
20
+ backend/api/routes/users.py,sha256=nKJKuGedzSMuJVLMU4-CXNc7GsM5c82JzjFYeZtzgsg,52771
20
21
  backend/config.py,sha256=-HsMaacaKuRftYXUXZJr8GBHPdmacFbrNnFDMk7DHNI,8187
21
- backend/main.py,sha256=nmm-0GWYoqKaWXRQSrRxbyZ5kUnv2BGpAtTvCyhVV_Q,4675
22
- backend/middleware/__init__.py,sha256=lUSMQqQc4UxPazrE1XmyNNEvTKRk7tddAdFmxbTzz7M,157
22
+ backend/main.py,sha256=yRIdnkv_iIyOiLc2btqomxlflym13A-b3ei3X8gI8Ac,5944
23
+ backend/middleware/__init__.py,sha256=usnVRHqfGW56bWJFqaIXz49pSW91uyciBfwO7ANCpIk,369
23
24
  backend/middleware/audit_logging.py,sha256=oGdgbfH_M_3hIMAGrS5HpRvri2jjLxGFnFuA6IiU9BU,4170
25
+ backend/middleware/tenant.py,sha256=a-HEAhyvMUC4uFF6VptaNOO9O6YP64-ZoUitppl79PQ,6373
24
26
  backend/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- backend/models/job.py,sha256=Aw5pl6uwJpvnqr61HIKmxMB4h7moX4DqcyRoR46zRzw,24683
27
+ backend/models/job.py,sha256=B1Q12F6448okCOkmLmpBnlwGIKi16TqBjVkRttTmqi0,25663
26
28
  backend/models/requests.py,sha256=tCX7ss4to3rKAR_Wu92CZ8uPqNb2nnmCp1XepD4VR5s,4152
29
+ backend/models/tenant.py,sha256=rhj6n7bsX8ClstoHQKCVcLWALI31l5Tepcf_N8IXDdM,7403
27
30
  backend/models/theme.py,sha256=cfNSHIigoAV4P41Jku6Sa1okHSRhUHjFpl1XBVF3kCY,4628
28
- backend/models/user.py,sha256=5kUqfAMQU26DjCvCgjyoP0f07QbOypAnSgpuGhGcMg0,7377
31
+ backend/models/user.py,sha256=2Dl81Sa8_c0cuPJ3uddsLSOAN40mtv-5aUETtFZZK8c,8022
29
32
  backend/models/worker_log.py,sha256=QDgpdRarm00N1TdFoGhKJxZjP9GmiMjrTJb6msw7pHA,5547
30
33
  backend/pyproject.toml,sha256=LAvEXxQSRI94XQcwpBmuSoEu36341HOu53iigsN6dVs,577
31
34
  backend/quick-check.sh,sha256=PUvH0wXaGzWrgWwwJG5M4Bxxt3vVA4mmdEdL6VepVHY,2382
@@ -39,10 +42,10 @@ backend/services/auth_service.py,sha256=rEpb4O9YSTOQaEoP2XXKXRpppWI5sG10AsueQSVc
39
42
  backend/services/credential_manager.py,sha256=VA--PwvL3gboJ21orQb7MIUiovX8n66FohD6w84exX8,31682
40
43
  backend/services/discord_service.py,sha256=LnEasOgaSRlmebDKsVT2yKCdWNDmskUToP3o2wPmdus,5082
41
44
  backend/services/dropbox_service.py,sha256=R47MUKCqK2BEXXmQxMLG6zkPWw02htFoES9q9xiaYAw,10717
42
- backend/services/email_service.py,sha256=fxwcL-n6ZRGLQ7SHXhxUQK1MkprtgE3GcuL5ulUR-90,44699
45
+ backend/services/email_service.py,sha256=gpufwXhNWmdElpWCmKrTLtm2LB8S-0Y5wcyJ3n1i6Jg,52868
43
46
  backend/services/encoding_interface.py,sha256=Y8hPskDyX9DOyAD557F3-SdHjymaBkGXcoC9E_OW7SE,15825
44
- backend/services/encoding_service.py,sha256=d_1M9DucihOHNqXI1pR4QLj16FhmtMoONRu2YGPvhGY,14570
45
- backend/services/firestore_service.py,sha256=XOuXUNd91eVeyEx82uh-A-WMwcyUa2HVxqTfYnGPw-Q,18512
47
+ backend/services/encoding_service.py,sha256=XAX4hyL2wIHdpBCTc8yglcjVJboZxRVAjc07lUTxgFg,18025
48
+ backend/services/firestore_service.py,sha256=--Va-2lawugskepxo2p9w3ON2KsMGqacSOutCmU_CDQ,18801
46
49
  backend/services/flacfetch_client.py,sha256=zj4VToKamR8bShXZifLvjDmUVa1HHvDhZuaw_b7g4lU,19901
47
50
  backend/services/gce_encoding/README.md,sha256=frTFrp7-ugFAvUd_K9EgE4ud8Xu4Qf1V7BNirQn6qJk,2515
48
51
  backend/services/gce_encoding/__init__.py,sha256=d2H6zM207NSc5afnfgkd3kwOXUu2gFBYjxBSS7tvX_s,571
@@ -50,36 +53,44 @@ backend/services/gce_encoding/main.py,sha256=2A0Bd3zYzbV66ikShB3avpqfNoDFBxr8Ja6
50
53
  backend/services/gce_encoding/requirements.txt,sha256=-hW5ot-PnVoKkxrpY3yV6l68wx7AvzgnLYAWEgDGWNc,408
51
54
  backend/services/gdrive_service.py,sha256=3JTXmPBuxpKopAbWDrh6YhqR5L9BLop1R7caXBnIk2A,13266
52
55
  backend/services/job_logging.py,sha256=theCkAlwMbqyqAbu7vCCRpVRscaIpkam3UrCs_4Yi1g,8052
53
- backend/services/job_manager.py,sha256=YPXFJD9oIMSZvVl2Ed4czo-4M-HqMxJrYCfZXV_MkhE,31256
56
+ backend/services/job_manager.py,sha256=2vBp-a1r3kwOg-CvXRjjlgkBh9U5AlI6yLeCBAjKLMY,33214
54
57
  backend/services/job_notification_service.py,sha256=ywGJUMUDqV23OSDsugOUufpXO6N_24bM9YVaPp8bOk4,9557
58
+ backend/services/langfuse_preloader.py,sha256=t-Rt_ThjZm4K8W3jlyLn2H3cEy7KNUlhrvOPmdg-gYM,3100
55
59
  backend/services/local_encoding_service.py,sha256=Q6yK2-QL9tHM_pUBoxdyfT7ln2L5xMx82JB2nC5OfNQ,20680
56
60
  backend/services/local_preview_encoding_service.py,sha256=WLuJuIx-kH6u35gcYnvogf4Gt7iEokL2IdfxlrkY5po,14394
57
61
  backend/services/lyrics_cache_service.py,sha256=53EIZuDs5jIKr6QiOhIKJjE2mx1Jd1_x15_uAeLnc4A,7598
58
62
  backend/services/metrics.py,sha256=hlEeLG4jOR1i_QbWaNQthoko9CSpe-Zn_LHJ2huEFeY,12728
63
+ backend/services/nltk_preloader.py,sha256=TZFghQrpDTi6COcW_rkjL15SlzdRNvPmrdy690fq0hw,3579
59
64
  backend/services/packaging_service.py,sha256=MUuBk0jtG3vGQhuOWbDv9u1uIMwbBC4NknLpJYFcWhs,10259
60
65
  backend/services/rclone_service.py,sha256=Q6wRAjlBiMMh9FBbbFRp4SHK1ljOQBed9zUJaJgAuhU,3646
66
+ backend/services/spacy_preloader.py,sha256=rpsMZB0CQWcoXjq8nyZCwJBkyjGheKaCFrfhxCOln5w,1836
61
67
  backend/services/storage_service.py,sha256=gA7Snz1pihzYpRKZjEZx3eDwyMUBezl3ccLuBdpTwE4,8573
62
- backend/services/stripe_service.py,sha256=gmw1mQr_TezXFHnjN77df6KOUTpEuuzYewNMmbozaGg,9379
68
+ backend/services/stripe_service.py,sha256=YPBVE2_ZeBYWklcFCTGoRWOuO1UTI16_Un2NKAE-sqE,14825
63
69
  backend/services/structured_logging.py,sha256=daGgZUdc1xIiOdxL9-YtWHDy51hwZqd8ELpomkh2QH4,9063
64
70
  backend/services/template_service.py,sha256=5CdSxBGhdBtxIUz24AhvltUfSWcFqZzBIxb9MjlN4YU,11212
71
+ backend/services/tenant_service.py,sha256=AlG5acJww-rpxGEQbw7eOWe1KaHLOcCn3fM95qDZMGw,9709
65
72
  backend/services/theme_service.py,sha256=6lJqMpFjmRY09CMJcW9IJ6DVPffLmwE0DnpXDUKaJ0U,17374
66
73
  backend/services/tracing.py,sha256=MNpJNMmSOw-9TRlb3dJk7pA8JTmSjAFHj9bSuyM5cOY,17210
67
- backend/services/user_service.py,sha256=TSVuIfM2rF4Acw1ic5TQ-QIjgeKU-Q8r93-PQdd4cwA,26215
74
+ backend/services/user_service.py,sha256=0_ztVqXKNH1jqKiZwqgStEfW95YowJbvYOL-eaBw2oY,29339
68
75
  backend/services/worker_service.py,sha256=WRgh3kconWYmvraEIsx6eUto2frKacauKApFU1wIEiQ,21093
69
76
  backend/services/youtube_service.py,sha256=L2jqcWkw6qIaKh4wEfX894mHaTU0m5qtgIX6TPi6uiQ,3828
70
77
  backend/services/youtube_upload_service.py,sha256=fvo4WxLQaKY_I75ViBMLk6vm87ym2ABHK0Fn_StYN6g,16694
71
78
  backend/tests/__init__.py,sha256=jTjELMqIzpgHG9dcXwwkszTAAC4Wf-c93C6D1k-toZg,44
72
79
  backend/tests/conftest.py,sha256=c4_jH0FIHsiKeUfo3ECPN78MlbW_IARTWwKr9RKLOSM,7843
73
80
  backend/tests/emulator/__init__.py,sha256=yOGPU5e0JPNYyUpy-u13jW4gEfWQs3dCJxfincptaCY,156
74
- backend/tests/emulator/conftest.py,sha256=gBZe23yPM4N2enFVO81ow67GcfGou8HZi7qWK5SI8GU,2858
81
+ backend/tests/emulator/conftest.py,sha256=VXhoJ3NZg3uiLzSAsushQtozFS7Lt6lmjk7ym-k6V8o,3844
75
82
  backend/tests/emulator/test_e2e_cli_backend.py,sha256=BTrWDv4sRTeOlheZ-d4R37tZsvJ2MhdFLbDb9tjbF08,41018
76
83
  backend/tests/emulator/test_emulator_integration.py,sha256=pm_JzgA2bmjcnHmGA-mZfJhUAYwGx10jv48A2deujRs,11822
84
+ backend/tests/emulator/test_made_for_you_integration.py,sha256=EYWZrYrQdoYk4Eb8G4M7Cyzq5ncAWqeYLuG8a31EAqc,5470
77
85
  backend/tests/emulator/test_style_loading_direct.py,sha256=e-FzlCVW_-ealM9hTGa2qgpwoVRiYKrcXDWB1FjkwXc,16842
78
86
  backend/tests/emulator/test_worker_logs_direct.py,sha256=96EeaSxeNQBtDiZ9ky1-CGE6Lv6ZV3dKuoF3inHxO8A,8158
79
87
  backend/tests/emulator/test_worker_logs_subcollection.py,sha256=whwUXW_-fF-nQp2Z791PNxi0nnvlRwLo87QacF-yMUI,16834
80
88
  backend/tests/requirements-test.txt,sha256=1ixGqdsXFG0hbqUPWUjNGPlKdQgYxo1GajXOiLhs66M,193
81
89
  backend/tests/requirements.txt,sha256=1526vDy39B1ZjJ5URZtoiDGnjvwuxiD9dNrjOLd6-gA,95
82
90
  backend/tests/test_admin_email_endpoints.py,sha256=C1mOX3K4P-_QqSWa8ZSH4gtMnUJ7hqq5rOi00zSKR8g,16592
91
+ backend/tests/test_admin_job_files.py,sha256=LXeV08a4XuaipM-QFFVJKE02e83Muj-JrjtCPlgExuM,13348
92
+ backend/tests/test_admin_job_reset.py,sha256=PsM6cBPeN5d6HO4pejaOQNyTh7OHTOFNPMTPuulcZCI,14292
93
+ backend/tests/test_admin_job_update.py,sha256=19NFazBk-fU8vrKKOoJV9c7pAvskNcAhihpmdQFZNIk,12157
83
94
  backend/tests/test_api_integration.py,sha256=ps4dFoW69NCKJAWvPSr410fEILPxfQNME_YmLPdNqxo,14886
84
95
  backend/tests/test_api_routes.py,sha256=j6alBwXgtKspXxbOiDuNCCVMENk6yxdFJkakbife-rM,3193
85
96
  backend/tests/test_audio_analysis_service.py,sha256=bKIAgyxfdbUOr7atHH-WCbZcZFkWGmGOOn7IfvRF2ms,11373
@@ -93,32 +104,39 @@ backend/tests/test_dependencies.py,sha256=kWiOQEMQWFPIbAM8FEMYI921n3dLOQFJ7HXyRH
93
104
  backend/tests/test_discord_service.py,sha256=OU5mVzjukFtJd_H5e_HxJqSsf78B0TrNfjxhwypbV34,8902
94
105
  backend/tests/test_distribution_services.py,sha256=8oB2vMq5T_4rvOs6BURoLLTQiDf0kUJ-jF8dzJzDYRk,33699
95
106
  backend/tests/test_dropbox_service.py,sha256=nDN1NjiCMS_f1bICWfe3maqbKT5FbhNuKCb1zjyjKq0,19198
96
- backend/tests/test_email_service.py,sha256=jj4EQwbJ2iOiL_-jFR-2t9YJv7KnGP99HcFjZHAq2-I,16754
107
+ backend/tests/test_email_service.py,sha256=u1VSTBCZl7m3GYR-vKV84tu3q7zhMuIhNA5a4OiCDlE,25290
97
108
  backend/tests/test_emulator_integration.py,sha256=m4teGnKLBKQNe9UdFi0-0iR_unnotWs07VWgSd_atuw,10387
98
109
  backend/tests/test_encoding_interface.py,sha256=7q9mKX4QG8-bfkxQgZkmeirK_ynfL88UTbgWS0f097U,14609
99
110
  backend/tests/test_file_upload.py,sha256=mWEIVj6cK6ZzKf4X0D0JhSk6pmJeCO0dOawV2GED7tc,68038
100
111
  backend/tests/test_flacfetch_client.py,sha256=ZquMDLFVgORr9GoNfybOV9Z3eruborRgCvBRY6l-V8A,22614
101
112
  backend/tests/test_gdrive_service.py,sha256=fh02KPaFqLEWfPaDQvSehn6r6RLq0bgt6MdXenF1kxA,19762
113
+ backend/tests/test_impersonation.py,sha256=vuCJaYmPOObLvFWuzxRvqadeRtFXgsbQxyOKd4TaxHg,9087
102
114
  backend/tests/test_instrumental_api.py,sha256=2eWaoEAYVY02HZgY4esn5echuWGv_sznoO6FYWIgTbQ,17210
103
115
  backend/tests/test_internal_api.py,sha256=qMGSDCkYd5OY_Z85vQu10xjqJn1JtaQcaKSnSBpVijw,12980
104
- backend/tests/test_job_creation_regression.py,sha256=C1KgyNhpJzchl10QK8trfmACs2BrEwYd-WK1YMMS-7I,22602
105
- backend/tests/test_job_manager.py,sha256=Y1MqanjqARh7HUSntzu2LkG3TFt0UYdIQHZisY7DP2A,12452
116
+ backend/tests/test_job_creation_regression.py,sha256=ydd97391bMq40MSTlTlvMn3_zjtFDWzSQLjYczoqR_U,22826
117
+ backend/tests/test_job_manager.py,sha256=9SvwVkgN5VoyaJH-vzVN3VRoXpMJf4YAluLmspZrhzs,18862
106
118
  backend/tests/test_job_manager_notifications.py,sha256=WlzXXxTCmHHCrD4XWu2j2fDYODlHGKThOQpZQeyhPXM,12996
107
119
  backend/tests/test_job_notification_service.py,sha256=YbXqCWbsK8O5WICItb3VCrEY8qKqoJ3NBEZtwCs7AYM,17892
108
- backend/tests/test_jobs_api.py,sha256=HOdP-ULWQFgrL7JDRaaE14HF9xiuHHRt4-5UrgBQGoI,10713
120
+ backend/tests/test_jobs_api.py,sha256=z3pGTawTpt75a1bQ5x4ynw3pTlNihLA6vqktTc6mBAU,11092
109
121
  backend/tests/test_local_encoding_service.py,sha256=TbBaFOB-8Re09G-qXIQCl8mS9WlQMFNQF01V64IekyI,14638
110
122
  backend/tests/test_local_preview_encoding_service.py,sha256=VwxpcAjDdYaVauSsrJ609U575VWRZbLsXew8BqBXpJY,20228
123
+ backend/tests/test_made_for_you.py,sha256=WNiJhmv_zse96Yg4scEXnpu66MMc0Hi4pNyiadfdlQ8,87628
111
124
  backend/tests/test_main.py,sha256=BvzcadLUV0SEUO1ViKm0djgy_TEKXm8PPvgfcCCEJAY,3348
112
- backend/tests/test_models.py,sha256=3qmuI-GmOXzhPTSLO2jlAl9E1hqtT057SkH4t4Dbchk,34435
125
+ backend/tests/test_models.py,sha256=buUjtm3TaiIff58SCpG0Du1Bnm-S9X8LyaW64zEhb4U,39606
113
126
  backend/tests/test_packaging_service.py,sha256=v4Bj8R61Fx0Kng6RoTj3SRN9jmxow5qCVtTJstTWNfE,13916
114
127
  backend/tests/test_requests.py,sha256=DI2IG67hHk2rhuzUfz_QNJCLRTy1NZPLsnSdZbIfPA8,6817
115
128
  backend/tests/test_routes_jobs.py,sha256=iqOZ93ACKpizSzLEJUx5hIo0vkKe2oIT7s7iZqQUJaE,11258
116
129
  backend/tests/test_routes_review.py,sha256=ENo6_wFUDcyXZhRy_NBqajan9dvqV_7f1rIQ7LLLS-U,14334
117
130
  backend/tests/test_services.py,sha256=JSsZZSpa0HjNZBSIrXXzBxZfJewfdR9RC3P8tHV8M8E,24288
118
131
  backend/tests/test_services_extended.py,sha256=cBcaEicz4WdBo5WZB77AoGZTwWAOC6W4OslpT6VmFI8,4124
132
+ backend/tests/test_spacy_preloader.py,sha256=uWpkrOPUOOcW1-fWClKMKVhXHGk30FXq537kQH5jpd0,4437
119
133
  backend/tests/test_storage_service.py,sha256=gorn3aFwKSr8lihxcG7kza7fYvDsTvxJhOxzuTtPK_o,19069
120
134
  backend/tests/test_style_upload.py,sha256=8G2dGgvyMGXsh1jk8PcGpTlSdBQAlHn13kpZcKn6rTY,10491
121
135
  backend/tests/test_template_service.py,sha256=BKURQ1M-hXXgdBl9LqhPQKfpItWOYDRBSt1y5FXQzaM,10431
136
+ backend/tests/test_tenant_api.py,sha256=P4395WYGldJIX7hYcbk96bIDW0Hogcj2McUG6iApNmc,13441
137
+ backend/tests/test_tenant_middleware.py,sha256=0WflieACxT1c_k-C2-yQ5sap_xm6z6w3p3ys09B8iMA,12577
138
+ backend/tests/test_tenant_models.py,sha256=NwNz197f5IPuhNU8f0d2OGBTeE7y7K2oulHfOJRo1EQ,15228
139
+ backend/tests/test_tenant_service.py,sha256=39bL1B0poa1LyxHDwFdQgSVu-QJ96rpe7Occ_ItC1Zc,16494
122
140
  backend/tests/test_theme_service.py,sha256=I_BrXjDAwmqGhxClJA8kE9FDYdQmtdYQINjIwyI3NTs,20454
123
141
  backend/tests/test_unicode_sanitization.py,sha256=M4QfJmHGZ8WrhTfg7QPljPj2kUkN5QUNThtofzZhQiE,22930
124
142
  backend/tests/test_upload_api.py,sha256=oWXPunON3pSTEnE3oE9vIQk63GC6lKM5RgTmaKKQths,10127
@@ -130,6 +148,7 @@ backend/tests/test_workers.py,sha256=BW-MDQx5HrgICKOcd1KkvzyBO4sOfJXeLVlMiJDfmBw
130
148
  backend/tests/test_workers_extended.py,sha256=uFznO4Q2v_H7xjfje5eYXXjS72EnhwXNRcT9YshuB8M,6763
131
149
  backend/tests/test_youtube_service.py,sha256=HPm3HUJcu9rKcyRpBfufU6tzMP1lwZ9ZSe0vOoKNypI,9134
132
150
  backend/tests/test_youtube_upload_service.py,sha256=o58DXYWLOtp8nmaowO_UHdOErVCDl8YxHJUgI-xvyMs,21428
151
+ backend/utils/test_data.py,sha256=0H0GmV0UnLXe-l0NKAug7COORyQI3PQtbC_qQHllIpI,713
133
152
  backend/validate.py,sha256=u8lXKnC1ocXKAaYxNoCE9dRrhvoYI11XrTtaWIp-HLw,5040
134
153
  backend/version.py,sha256=Ai-O0n-W-iBIYY_c71-QZikxAsmTpcel4X3T3p8AqVI,677
135
154
  backend/workers/README.md,sha256=JYfLyngGkzxl1mRpkppIZB3ou9GR3sRVmZAxIAdURPY,15935
@@ -137,9 +156,9 @@ backend/workers/__init__.py,sha256=Eswp1Jvb6-97r7zUe7FUXSexsCZvghR08qwx1L_4298,3
137
156
  backend/workers/audio_worker.py,sha256=9RWIzuhU24fpRMj9JW8cpWODeYsZrbaXhtxddrMFKIo,29088
138
157
  backend/workers/lyrics_worker.py,sha256=nsy90pb0BPAl_lbZ0E7d7jIclcXK7W8d9RxxEdJaHVg,35149
139
158
  backend/workers/render_video_worker.py,sha256=L6-VCxSBY6MbXBBm0-wu_DXxcIWKfW6ynTv9K03WCjY,25163
140
- backend/workers/screens_worker.py,sha256=HvSrgpY4X0imu81HygAG-qS024puBWUONx-0fJ4sOe8,20998
159
+ backend/workers/screens_worker.py,sha256=vCS7j4dQRQ_tsWNhz-XH5nau7kGyUSzSp-dzyZpeJEo,21402
141
160
  backend/workers/style_helper.py,sha256=7-PM79dAUAZHTj-bnATy43ImqNdl_00E9pm94CYg1BM,6906
142
- backend/workers/video_worker.py,sha256=49Q7D3bNg38iJk26q2k8y3v-oUscUtE50qaUymlFQbk,55292
161
+ backend/workers/video_worker.py,sha256=5r3PcYdoca16fjZtFzCwfKm-G7QjL98ZLMhw6qGVDNE,55670
143
162
  backend/workers/video_worker_orchestrator.py,sha256=NInZjFwztqcYneuxrkDqEFWV7z5BzGlaaoJxj812KA4,27510
144
163
  backend/workers/worker_logging.py,sha256=nlbGsjDjdkv28Fm-95vKBumy6P47dGiNN5Zf3r0A480,9928
145
164
  karaoke_gen/__init__.py,sha256=wHpDbURJxmJAMNZ0uQjISv5MIT7KD9RWYi15xlYgEhU,1351
@@ -193,7 +212,7 @@ lyrics_transcriber/core/controller.py,sha256=zRjdxOrJEaa2depvzZvwVQiEFmf8Ew3Aek8
193
212
  lyrics_transcriber/correction/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
194
213
  lyrics_transcriber/correction/agentic/__init__.py,sha256=p7PHiebuvRs8RDlPDs-9gLZKzXG5KfWg3fFCdDhY6pE,222
195
214
  lyrics_transcriber/correction/agentic/adapter.py,sha256=Z0JBTAA7xlSdctCHqO9nBMl78C4XmqsLKKtS6BvNZNI,2912
196
- lyrics_transcriber/correction/agentic/agent.py,sha256=GV6TkrIQBhibJllXDnp9zBBmaf_vnoSVuJZmm6WVhS0,12722
215
+ lyrics_transcriber/correction/agentic/agent.py,sha256=73E2ouAiE0RUiIvxIb_lVaiIo_DJr_YaH3CAaqdjh08,13274
197
216
  lyrics_transcriber/correction/agentic/feedback/aggregator.py,sha256=323t8LDbE26ni83woyN7uVMSuSQhnqTgwJc-d-KuDbs,273
198
217
  lyrics_transcriber/correction/agentic/feedback/collector.py,sha256=HT-2cAP_bx7Iv-0-tpZv534do111g0FlTUt2XaKoUtA,415
199
218
  lyrics_transcriber/correction/agentic/feedback/retention.py,sha256=dUCUsKPCzHVQxiLLBXcdfAZ5NqiG25go0Z6GFXeK0vY,881
@@ -231,8 +250,8 @@ lyrics_transcriber/correction/agentic/providers/circuit_breaker.py,sha256=D3Jg4Y
231
250
  lyrics_transcriber/correction/agentic/providers/config.py,sha256=7m4I3imOZyv-6eR8mxuK9vVdqPEDcGuVAkEgdGWu10w,4299
232
251
  lyrics_transcriber/correction/agentic/providers/constants.py,sha256=cXLzKTyFVt9q6wQd_gWcv3EZ5Sm27AOAz6NyPapcess,695
233
252
  lyrics_transcriber/correction/agentic/providers/health.py,sha256=F8pHY5BQYvylGRDGXUHplcAJooAyiqVLRhBl4kHC1H8,710
234
- lyrics_transcriber/correction/agentic/providers/langchain_bridge.py,sha256=KrvjM0jY-7xrP65mW5TfsXnqN2BHcUgaDzcmWv1Rh_U,10423
235
- lyrics_transcriber/correction/agentic/providers/model_factory.py,sha256=90EjVwoKTWo8jXTrroI7GXM9AU-_ACx9g_fHB4vnR2w,9919
253
+ lyrics_transcriber/correction/agentic/providers/langchain_bridge.py,sha256=fgWZjzWiClBZ1NA6E8pf0kp23j6p56Htry_h7vtJ3ZM,12607
254
+ lyrics_transcriber/correction/agentic/providers/model_factory.py,sha256=ZsPdjSwnN1hFZxPwJkH-2rtgv59f9BmNRJmsLYtK67Q,10805
236
255
  lyrics_transcriber/correction/agentic/providers/response_cache.py,sha256=Byr7fQJsgUMFlsvHeVCxTiFjjnbsg3KIlEmEEtAo-Gw,7047
237
256
  lyrics_transcriber/correction/agentic/providers/response_parser.py,sha256=c2KypM-yHbIXXakHV5s-qh8fl8FhssLPVo3pJbyAiG4,4301
238
257
  lyrics_transcriber/correction/agentic/providers/retry_executor.py,sha256=hX21Zwy2cSECAw7k13ndEinWRqwjo4xYoSCQ2B2CUf0,3912
@@ -241,7 +260,7 @@ lyrics_transcriber/correction/agentic/workflows/__init__.py,sha256=OsBExAbIIKxJg
241
260
  lyrics_transcriber/correction/agentic/workflows/consensus_workflow.py,sha256=gMuLTUxkgYaciMsI4yrZSC3wi--7V_PgaDNE-Vd6FE8,575
242
261
  lyrics_transcriber/correction/agentic/workflows/correction_graph.py,sha256=kgZKnz0h9cG1EfhW7BSSl-kSpQtJrRM_S86kAniXfE4,1815
243
262
  lyrics_transcriber/correction/agentic/workflows/feedback_workflow.py,sha256=KsKLD3AP66YYmXfUn-mVZjERYLtU1Zs4a-7CB2zDfas,596
244
- lyrics_transcriber/correction/anchor_sequence.py,sha256=5tl4Cjiw5UlLbEb1Oy-g3ebKCinXSwohdaCB9-rTMtI,43798
263
+ lyrics_transcriber/correction/anchor_sequence.py,sha256=-FT4NoNjwGnoxgm4m117Z3DPlJAJAlFYcTe4BeUj9pQ,48974
245
264
  lyrics_transcriber/correction/corrector.py,sha256=zqmpwt_LMG1VdijXUIMGr42ny2qIiBqwEIDCslqi2dE,42622
246
265
  lyrics_transcriber/correction/feedback/__init__.py,sha256=i1gd0Vb4qvlzZQ3lqA3fJjt288YP7f-MBPwOzZ7Rjh4,68
247
266
  lyrics_transcriber/correction/feedback/schemas.py,sha256=OiF_WUqcqiEKIoburYM8kWAIundy82PQE7ImsdP8UCk,4416
@@ -254,11 +273,11 @@ lyrics_transcriber/correction/handlers/no_space_punct_match.py,sha256=jY2fa547Qc
254
273
  lyrics_transcriber/correction/handlers/relaxed_word_count_match.py,sha256=x4k__6gav4-STk_TycLcg5Sw4x2vUFAj5fWmOv7Yd_w,3911
255
274
  lyrics_transcriber/correction/handlers/repeat.py,sha256=1PJADW44egYh7N9D2fN-gDIusWVglFjGHrCZuTQYNpA,4313
256
275
  lyrics_transcriber/correction/handlers/sound_alike.py,sha256=75IvDSfoGUG2xVbYp-xsYuQXf7Jo-0ymsTzdBSOrwwQ,11935
257
- lyrics_transcriber/correction/handlers/syllables_match.py,sha256=c9_hrJb_xkkqd2SuDjrsSmUF7OMYV65LRzBfAhCHxEY,11217
276
+ lyrics_transcriber/correction/handlers/syllables_match.py,sha256=CV_sYISqPhA2VFbaU2XuX4SbjQcpeuMH7LTw91pgH4I,13103
258
277
  lyrics_transcriber/correction/handlers/word_count_match.py,sha256=OltTEs6eYnslxdvak97M5gXDiqXJxMHKk__Q9F_akXc,3595
259
278
  lyrics_transcriber/correction/handlers/word_operations.py,sha256=410xhyO9tiqezV5yd5JKwKbxSGwXK9LWHJ7-zNIuOWA,7423
260
279
  lyrics_transcriber/correction/operations.py,sha256=rmSxDdlu5H2drbVvR1A9KuaZT60vbHeZKKaB7olD4ns,14659
261
- lyrics_transcriber/correction/phrase_analyzer.py,sha256=dtO_2LjxnPdHJM7De40mYIdHCkozwhizVVQp5XGO7x0,16962
280
+ lyrics_transcriber/correction/phrase_analyzer.py,sha256=bEGz3KlH8iFAudMK7L0eC-CyVSwZkIZtXr8qh_k4_u8,17576
262
281
  lyrics_transcriber/correction/text_utils.py,sha256=7QHK6-PY7Rx1G1E31sWiLBw00mHorRDo-M44KMHFaZs,833
263
282
  lyrics_transcriber/frontend/.gitignore,sha256=cR2ofyyWArkna_jByfaWi8gTeMhsKTSoK128PmIw218,262
264
283
  lyrics_transcriber/frontend/.yarn/releases/yarn-4.7.0.cjs,sha256=KTYy2KCV2OpHhussV5jIPDdUSr7RftMRhqPsRUmgfAY,2765465
@@ -282,7 +301,7 @@ lyrics_transcriber/frontend/public/favicon.ico,sha256=ZK7QvdBuZp0QxPkluCW4IKxfle
282
301
  lyrics_transcriber/frontend/public/nomad-karaoke-logo.png,sha256=jTTBFXV6hGJGolZYQ-dIjgQQbMsehk5XGtsllhLrdzg,212641
283
302
  lyrics_transcriber/frontend/public/nomad-karaoke-logo.svg,sha256=0LOH346_a-1JeYquMWd1v2A3XMN8zLDBeujfWAOeNYk,9185
284
303
  lyrics_transcriber/frontend/src/App.tsx,sha256=STVmqN3xtXambV_5X4M0MNuwYjARHBQfn1cuCqxNGkw,7979
285
- lyrics_transcriber/frontend/src/api.ts,sha256=GcjbOrlU7EdUpZ7MUPFqE1rtH-ckdw8wHtgyQxWateY,8648
304
+ lyrics_transcriber/frontend/src/api.ts,sha256=MDTKq1rZUXKdRHb1YFDKaGuFOK-LO2wC3yvladlWjeY,9085
286
305
  lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx,sha256=YvJlBP-3udqrOmvwKuMR7FxfIozZq5pVfTYvmhzbnHo,5602
287
306
  lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx,sha256=ubJwQewryjUrXwpBkITQNu4POhoUtDbNA93cqa-yJKY,3416
288
307
  lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx,sha256=Yg6FG0LtrneRfAYeBu3crt_RdN-_o7FojtYhDMDKi0o,8595
@@ -309,7 +328,7 @@ lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx,sha256=33XpyHj0s
309
328
  lyrics_transcriber/frontend/src/components/ModeSelectionModal.tsx,sha256=eihGI49r9tKq-AaEtnmVrbiBOoJApWvabaZW4ydmg-4,5302
310
329
  lyrics_transcriber/frontend/src/components/ModeSelector.tsx,sha256=HnBAK_gFgNBJLtMC_ESMVdUapDjmqmoLX8pQeyHfpOw,2651
311
330
  lyrics_transcriber/frontend/src/components/ModelSelector.tsx,sha256=lfG_B5VAzSfrU0FqJl8XptN6DVt2kSljU96HMXo8mf4,559
312
- lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx,sha256=59ZhG5XsxUZ_dkK8BjTQhYmYP5Wv86uRR-xtuwFRK8c,5582
331
+ lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx,sha256=zKR_S1AyYAEmr1hnr2IN14mw1igpYbJB12HjaUrXbH4,6803
313
332
  lyrics_transcriber/frontend/src/components/ReferenceView.tsx,sha256=a3CFpbJ8M-kFV3K79xyuo-sfOoC9He_IuXwhIcAOImo,10510
314
333
  lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx,sha256=pVlqHrSloxXZV_Ib8cbk1invF7WA3uge5b7pnFPe9Pc,12290
315
334
  lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx,sha256=VQg_gBFViAxQu9Z75o6rOsvmH5DZBjKq9FkU8aB_7mI,13790
@@ -431,8 +450,8 @@ lyrics_transcriber/transcribers/whisper.py,sha256=YcCB1ic9H6zL1GS0jD0emu8-qlcH0Q
431
450
  lyrics_transcriber/types.py,sha256=UJjaxhVd2o14AG4G8ToU598p0JeYdiTFjpG38jGCoYQ,27917
432
451
  lyrics_transcriber/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
433
452
  lyrics_transcriber/utils/word_utils.py,sha256=-cMGpj9UV4F6IsoDKAV2i1aiqSO8eI91HMAm_igtVMk,958
434
- karaoke_gen-0.96.0.dist-info/METADATA,sha256=7yJr3KPMRKSUFYK-U05SfBDGHk9ZpYMGMDmeTPsAf1Q,23115
435
- karaoke_gen-0.96.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
436
- karaoke_gen-0.96.0.dist-info/entry_points.txt,sha256=xIyLe7K84ZyjO8L0_AmNectz93QjGSs5AkApMtlAd4g,160
437
- karaoke_gen-0.96.0.dist-info/licenses/LICENSE,sha256=81R_4XwMZDODHD7JcZeUR8IiCU8AD7Ajl6bmwR9tYDk,1074
438
- karaoke_gen-0.96.0.dist-info/RECORD,,
453
+ karaoke_gen-0.101.0.dist-info/METADATA,sha256=zkFeEtygnskVvZLwCarJ9blhCd4pA60LV75aSZm8RaI,23116
454
+ karaoke_gen-0.101.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
455
+ karaoke_gen-0.101.0.dist-info/entry_points.txt,sha256=xIyLe7K84ZyjO8L0_AmNectz93QjGSs5AkApMtlAd4g,160
456
+ karaoke_gen-0.101.0.dist-info/licenses/LICENSE,sha256=81R_4XwMZDODHD7JcZeUR8IiCU8AD7Ajl6bmwR9tYDk,1074
457
+ karaoke_gen-0.101.0.dist-info/RECORD,,
@@ -86,28 +86,39 @@ class AgenticCorrector:
86
86
 
87
87
  @classmethod
88
88
  def from_model(
89
- cls,
90
- model: str,
89
+ cls,
90
+ model: str,
91
91
  config: ProviderConfig | None = None,
92
92
  session_id: Optional[str] = None,
93
- cache_dir: Optional[str] = None
93
+ cache_dir: Optional[str] = None,
94
+ warmup: bool = True
94
95
  ) -> "AgenticCorrector":
95
96
  """Factory method to create corrector from model specification.
96
-
97
+
97
98
  This is a convenience method for the common case where you want
98
99
  to use LangChainBridge with a model spec string.
99
-
100
+
100
101
  Args:
101
102
  model: Model identifier in format "provider/model"
102
103
  config: Optional provider configuration
103
104
  session_id: Optional Langfuse session ID to group related traces
104
105
  cache_dir: Optional cache directory (uses default if not provided)
105
-
106
+ warmup: If True, eagerly initialize the model to avoid delays when
107
+ multiple threads call classify_gap() simultaneously (default: True)
108
+
106
109
  Returns:
107
110
  AgenticCorrector instance with LangChainBridge provider
108
111
  """
109
112
  config = config or ProviderConfig.from_env(cache_dir=cache_dir)
110
113
  provider = LangChainBridge(model=model, config=config)
114
+
115
+ # Eagerly initialize the model to avoid lazy initialization delays
116
+ # when multiple threads call classify_gap() simultaneously
117
+ if warmup:
118
+ logger.info(f"🤖 Warming up model {model} before parallel processing...")
119
+ if not provider.warmup():
120
+ logger.warning(f"🤖 Model warmup failed for {model}, will retry on first use")
121
+
111
122
  return cls(provider=provider, session_id=session_id)
112
123
 
113
124
  def classify_gap(
@@ -13,6 +13,7 @@ from __future__ import annotations
13
13
 
14
14
  import logging
15
15
  import os
16
+ import threading
16
17
  import time
17
18
  from concurrent.futures import ThreadPoolExecutor, TimeoutError as FuturesTimeoutError
18
19
  from typing import List, Dict, Any, Optional
@@ -94,10 +95,80 @@ class LangChainBridge(BaseAIProvider):
94
95
  cache_dir=self._config.cache_dir,
95
96
  enabled=cache_enabled
96
97
  )
97
-
98
- # Lazy-initialized chat model
98
+
99
+ # Lazy-initialized chat model with thread-safe initialization
100
+ # Lock prevents race condition where multiple threads try to initialize simultaneously
99
101
  self._chat_model: Optional[Any] = None
100
-
102
+ self._model_init_lock = threading.Lock()
103
+
104
+ def warmup(self) -> bool:
105
+ """Eagerly initialize the chat model.
106
+
107
+ Call this after creating the bridge to avoid lazy initialization delays
108
+ when multiple threads call generate_correction_proposals() simultaneously.
109
+
110
+ Returns:
111
+ True if model was initialized successfully, False otherwise
112
+ """
113
+ if self._chat_model is not None:
114
+ logger.debug(f"🤖 Model {self._model} already initialized")
115
+ return True
116
+
117
+ logger.info(f"🤖 Warming up model {self._model}...")
118
+ # Trigger initialization by calling the initialization logic directly
119
+ try:
120
+ self._ensure_model_initialized()
121
+ return self._chat_model is not None
122
+ except Exception as e:
123
+ logger.error(f"🤖 Warmup failed for {self._model}: {e}")
124
+ return False
125
+
126
+ def _ensure_model_initialized(self) -> None:
127
+ """Ensure the chat model is initialized (thread-safe).
128
+
129
+ This method handles the lazy initialization with proper locking.
130
+ It's separated out so it can be called from both warmup() and
131
+ generate_correction_proposals().
132
+ """
133
+ if self._chat_model is not None:
134
+ return
135
+
136
+ with self._model_init_lock:
137
+ # Double-check after acquiring lock
138
+ if self._chat_model is not None:
139
+ return
140
+
141
+ timeout = self._config.initialization_timeout_seconds
142
+ logger.info(f"🤖 Initializing model {self._model} with {timeout}s timeout...")
143
+ init_start = time.time()
144
+
145
+ try:
146
+ # Use ThreadPoolExecutor for cross-platform timeout
147
+ with ThreadPoolExecutor(max_workers=1) as executor:
148
+ future = executor.submit(
149
+ self._factory.create_chat_model,
150
+ self._model,
151
+ self._config
152
+ )
153
+ try:
154
+ self._chat_model = future.result(timeout=timeout)
155
+ except FuturesTimeoutError:
156
+ raise InitializationTimeoutError(
157
+ f"Model initialization timed out after {timeout}s. "
158
+ f"This may indicate network issues or service unavailability."
159
+ ) from None
160
+
161
+ init_elapsed = time.time() - init_start
162
+ logger.info(f"🤖 Model initialized in {init_elapsed:.2f}s")
163
+
164
+ except InitializationTimeoutError:
165
+ self._circuit_breaker.record_failure(self._model)
166
+ raise
167
+ except Exception as e:
168
+ self._circuit_breaker.record_failure(self._model)
169
+ logger.error(f"🤖 Failed to initialize chat model: {e}")
170
+ raise
171
+
101
172
  def name(self) -> str:
102
173
  """Return provider name for logging."""
103
174
  return f"langchain:{self._model}"
@@ -140,46 +211,28 @@ class LangChainBridge(BaseAIProvider):
140
211
  "until": open_until
141
212
  }]
142
213
 
143
- # Step 2: Get or create chat model with initialization timeout
144
- if not self._chat_model:
145
- timeout = self._config.initialization_timeout_seconds
146
- logger.info(f"🤖 Initializing model {self._model} with {timeout}s timeout...")
147
- init_start = time.time()
148
-
149
- try:
150
- # Use ThreadPoolExecutor for cross-platform timeout
151
- with ThreadPoolExecutor(max_workers=1) as executor:
152
- future = executor.submit(
153
- self._factory.create_chat_model,
154
- self._model,
155
- self._config
156
- )
157
- try:
158
- self._chat_model = future.result(timeout=timeout)
159
- except FuturesTimeoutError:
160
- raise InitializationTimeoutError(
161
- f"Model initialization timed out after {timeout}s. "
162
- f"This may indicate network issues or service unavailability."
163
- ) from None
164
-
165
- init_elapsed = time.time() - init_start
166
- logger.info(f"🤖 Model initialized in {init_elapsed:.2f}s")
167
-
168
- except InitializationTimeoutError as e:
169
- self._circuit_breaker.record_failure(self._model)
170
- logger.exception("🤖 Model initialization timeout")
171
- return [{
172
- "error": INIT_TIMEOUT_ERROR,
173
- "message": str(e),
174
- "timeout_seconds": timeout
175
- }]
176
- except Exception as e:
177
- self._circuit_breaker.record_failure(self._model)
178
- logger.error(f"🤖 Failed to initialize chat model: {e}")
179
- return [{
180
- "error": MODEL_INIT_ERROR,
181
- "message": str(e)
182
- }]
214
+ # Step 2: Get or create chat model with thread-safe initialization
215
+ # Use double-checked locking to avoid race condition where multiple threads
216
+ # all try to initialize the model simultaneously (which caused job 2ccbdf6b
217
+ # to have 5 concurrent model initializations and 6+ minute delays)
218
+ #
219
+ # NOTE: For best performance, call warmup() after creating the bridge to
220
+ # eagerly initialize the model before parallel processing begins.
221
+ try:
222
+ self._ensure_model_initialized()
223
+ except InitializationTimeoutError as e:
224
+ logger.exception("🤖 Model initialization timeout")
225
+ return [{
226
+ "error": INIT_TIMEOUT_ERROR,
227
+ "message": str(e),
228
+ "timeout_seconds": self._config.initialization_timeout_seconds
229
+ }]
230
+ except Exception as e:
231
+ logger.error(f"🤖 Failed to initialize chat model: {e}")
232
+ return [{
233
+ "error": MODEL_INIT_ERROR,
234
+ "message": str(e)
235
+ }]
183
236
 
184
237
  # Step 3: Execute with retry logic
185
238
  logger.info(
@@ -10,6 +10,14 @@ from .config import ProviderConfig
10
10
 
11
11
  logger = logging.getLogger(__name__)
12
12
 
13
+ # Try to import Langfuse preloader (may not exist in standalone library usage)
14
+ try:
15
+ from backend.services.langfuse_preloader import get_preloaded_langfuse_handler
16
+
17
+ _HAS_LANGFUSE_PRELOADER = True
18
+ except ImportError:
19
+ _HAS_LANGFUSE_PRELOADER = False
20
+
13
21
  # Error message constant for TRY003 compliance
14
22
  GOOGLE_API_KEY_MISSING_ERROR = (
15
23
  "GOOGLE_API_KEY environment variable is required for Google/Gemini models. "
@@ -87,25 +95,38 @@ class ModelFactory:
87
95
 
88
96
  def _initialize_langfuse(self, model_spec: str) -> None:
89
97
  """Initialize Langfuse callback handler if keys are present.
90
-
98
+
99
+ First tries to use a preloaded handler (to avoid 200+ second init delay
100
+ on Cloud Run cold starts), then falls back to creating a new one.
101
+
91
102
  Langfuse reads credentials from environment variables automatically:
92
103
  - LANGFUSE_PUBLIC_KEY
93
- - LANGFUSE_SECRET_KEY
104
+ - LANGFUSE_SECRET_KEY
94
105
  - LANGFUSE_HOST (optional)
95
-
106
+
96
107
  Args:
97
108
  model_spec: Model specification for logging
98
-
109
+
99
110
  Raises:
100
111
  RuntimeError: If Langfuse keys are set but initialization fails
101
112
  """
102
113
  public_key = os.getenv("LANGFUSE_PUBLIC_KEY")
103
114
  secret_key = os.getenv("LANGFUSE_SECRET_KEY")
104
-
115
+
105
116
  if not (public_key and secret_key):
106
117
  logger.debug("🤖 Langfuse keys not found, tracing disabled")
107
118
  return
108
-
119
+
120
+ # Try to use preloaded handler first (avoids 200+ second delay on Cloud Run)
121
+ if _HAS_LANGFUSE_PRELOADER:
122
+ preloaded = get_preloaded_langfuse_handler()
123
+ if preloaded is not None:
124
+ logger.info(f"🤖 Using preloaded Langfuse handler for {model_spec}")
125
+ self._langfuse_handler = preloaded
126
+ return
127
+
128
+ # Fall back to creating new handler
129
+ logger.info(f"🤖 Initializing Langfuse handler (not preloaded) for {model_spec}...")
109
130
  try:
110
131
  from langfuse.langchain import CallbackHandler
111
132