perf(translate): fix slow translation startup — CJK estimation, output budget, provider token config

Root cause: batch sizing underestimated CJK token density (1.5→1.0 chars/token)
and ignored output budget as primary constraint, causing cascading finish_reason=length.

Changes:
- _token_budget.py: CJK_RATIO 1.5→1.0, OTHER_RATIO 2.2→1.8, safety factors 0.75/0.70
- _token_budget.py: new _compute_max_rows_by_output() — output budget is PRIMARY constraint
- _batch_sizer.py: resolve_provider_config() with DB-level context_window/max_output_tokens
- _batch_sizer.py: INPUT_SAFETY_FACTOR applied, max_rows_by_output used as row cap
- _llm_http.py: log actual usage.prompt_tokens/.completion_tokens from provider
- _llm_call.py: retry only missing rows after finish_reason=length (save partial result)
- models/llm.py + schema: provider-level context_window / max_output_tokens (nullable)
- services/llm_provider.py: get_provider_token_config() helper
- Alembic migration: add columns to llm_providers
- Svelte ProviderConfig: collapsible Advanced: Token Limits section
- 12 new tests (token budget, batch sizer, provider config)
- All 492 tests pass
This commit is contained in:
2026-06-03 23:25:08 +03:00
parent a819e1ec4d
commit 814f2da139
18 changed files with 1303 additions and 109 deletions

View File

@@ -85,6 +85,8 @@ async def get_providers(
is_active=p.is_active,
is_multimodal=bool(p.is_multimodal) if p.is_multimodal is not None else False,
max_images=p.max_images,
context_window=p.context_window,
max_output_tokens=p.max_output_tokens,
)
for p in providers
]
@@ -272,6 +274,8 @@ async def create_provider(
is_active=provider.is_active,
is_multimodal=bool(provider.is_multimodal) if provider.is_multimodal is not None else False,
max_images=provider.max_images,
context_window=provider.context_window,
max_output_tokens=provider.max_output_tokens,
)
@@ -309,6 +313,8 @@ async def update_provider(
is_active=provider.is_active,
is_multimodal=bool(provider.is_multimodal) if provider.is_multimodal is not None else False,
max_images=provider.max_images,
context_window=provider.context_window,
max_output_tokens=provider.max_output_tokens,
)

View File

@@ -456,6 +456,8 @@ async def get_consolidated_settings(
"default_model": p.default_model,
"is_active": p.is_active,
"is_multimodal": bool(p.is_multimodal) if p.is_multimodal is not None else False,
"context_window": p.context_window,
"max_output_tokens": p.max_output_tokens,
}
for p in providers
]