Repository navigation
fix: repair LLM test-connection and make silent model/template fallbacks visible - #281
guofeng201507 wants to merge 3 commits into
Conversation
POST /settings/test-connection calls LLMService.test_connection(), which has never existed, so the endpoint always raised AttributeError and the UI reported a generic failure regardless of whether the key was valid. Add the missing method. It probes the provider's models endpoint without spending completion tokens and without falling back to other providers, so a failure identifies this provider/base URL/key combination specifically. Credentials are sent only in headers, never in the URL. OpenRouter's /models is public and therefore cannot validate a key, so use /auth/key there and flag provisioning/management keys, which pass /auth/key but are rejected by chat completions. Also surface the template fallback from indicator aiGenerate through debug.generation_error: the built-in template passes validation, so previously a failed model call was indistinguishable from a successful response and the client reported success. Refs OpenByteInc#280
Cases: unconfigured key, OpenRouter authenticated endpoint selection, the provisioning/management key trap, HTTP failure surfacing, and that the Google key is sent as a header rather than in the URL.
A model-level fallback (requested model -> configured default -> static fallback) was only observable in the provider dashboard, so a misconfigured or unavailable model id silently produced answers from a different model while the client reported success. LLMService now records last_model_used / last_provider_used / model_fallback_used, the indicator generation path carries them in its edit plan, and aiGenerate returns them as generation_model / generation_provider / generation_model_fallback in the streamed debug frame.
|
Thanks — the endpoint really was dead before this ( 1. The probe targets whichever provider is active, not OpenRouter
if service == 'openrouter':
llm = LLMService()
ok, detail = llm.test_connection()
ok, detail = llm.test_connection(LLMProvider.OPENROUTER)2. Nothing covers the endpoint itselfThe new tests exercise Also worth noting: the new Google branch sends the credential in What I did with itApplied on current |
Summary
Two related problems, both of which made an LLM misconfiguration invisible to the client.
1. The admin "Test OpenRouter connection" button could never succeed.
POST /settings/test-connectioncallsLLMService.test_connection(), a method that does not exist, so the handler raisedAttributeErrorand always returned a generic failure:Not a regression —
git log -S'def test_connection' -- backend_api_python/app/services/llm.pyreturns no commits.2. Model-level fallback was silent.
call_llm_apitries[requested model] + [configured default] + [static fallback_model], so a bad model id quietly produced answers from a different model. The first symptom is only visible in the provider dashboard — e.g. an invaliddeepseek/…id still "worked" becauseopenai/gpt-4o-minianswered instead.Refs #280.
Changes
app/services/llm.pyLLMService.test_connection(provider=None) -> tuple[bool, str]: probes the provider's models endpoint with a 15s timeout. Spends no completion tokens and never falls back to another provider, so a failure identifies this provider / base URL / key combination.GET /auth/keybecause its/modelsis public (returns200without a key). Provisioning/management keys — which pass/auth/keybut are rejected by chat completions with401 "User not found"— are reported explicitly.x-goog-api-keyheader, matching_call_google_gemini.last_model_used/last_provider_used/model_fallback_usedon the instance when a call succeeds.app/routes/settings.py— return the probe's detail instead of a generic message.app/services/indicator_ai_generation.py+app/routes/indicator.py— carry the model usage and the template-fallback reason into the streameddebugframe (generation_error,generation_model,generation_provider,generation_model_fallback). The built-in template passes validation, so previously a failed model call was indistinguishable from a successful response.Verification
python -m pytest tests/test_llm_litellm_provider.py -q→ 41 passed (34 existing + 7 new: probe cases and model-fallback recording).test_connection()returns(True, 'openrouter reachable · model=…'); with a provisioning key it returns an actionable message. A deliberately invalid model id now reportslast_model_used = deepseek/deepseek-v4.1-flash,model_fallback_used = True.