fix(dash): discover served model for configured chat endpoint#97
fix(dash): discover served model for configured chat endpoint#97volen-silo wants to merge 2 commits into
Conversation
A configured chat URL (OPENAI_BASE_URL / --chat-url) with no explicit model resolved to the "local-model" placeholder, which 404s on servers that register the model under its real id. Query /v1/models on startup to adopt the served model, mirroring the managed-service path. Gated on a reachable probe so an unreachable endpoint adds no fetch timeout to launch, and an explicit --chat-model/config value still wins. Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
034e4e1 to
f8c2da1
Compare
|
🔴 Automated review · pr-review-watcher · f8c2da1 SummaryFixes the broken 🚫 Blocking (must fix before merge)None. Non-blocking
|
…up (ROCm#100) * fix(dash): prefer a local chat server over the cloud default at startup Startup only single-probed DEFAULT_CHAT_BASE_URL (:8000) before falling back to the ChatGPT cloud OAuth flow, even though the manual 'd' detect path already knew how to find a local engine on any of its well-known ports (Lemonade :13305, vLLM :8000, rocm serve :11435) plus the managed-services registry. A server running on any port other than :8000 was invisible at startup and users landed in the cloud login flow instead. Reuse detect_local_chat() (registry-first, then the 3-port probe, then a best-effort model fetch) in the startup branch that already gated on "no explicit chat_url/env_url configured" — the same condition that previously ran only detect_managed_chat(). Parallelize llm::detect_local_endpoint()'s port probe (std::thread::scope) so a cold start with nothing listening still costs one PROBE_TIMEOUT (~300ms) instead of three, keeping startup latency bounded. Unmerged PR ROCm#97 also touches this startup branch (model discovery inside resolve_llm_config's own fallback when chat_model is None); this change is conflict-minimal — it swaps detect_managed_chat for detect_local_chat without touching the resolve_llm_config call itself. Relates to EAI-7347. Signed-off-by: Michael Roy <michael.roy@amd.com> * fix(chat): do not run local-detection swap when an api key is configured Startup local-engine detection returns a keyless detected_llm_config (api_key/auth_header forced to None). Firing it whenever no explicit URL was set — even when the user configured OPENAI_API_KEY / ROCMDASH_CHAT_API_KEY — silently dropped that credential and produced a 401 at request time. Gate detection on chat_api_key.is_none() as well (extracted into the pure should_detect_local_chat helper for unit testing): a configured key means "use my configured backend", so skip the swap and let resolve_llm_config carry the key through its normal precedence. Also skip the redundant fallback probe: when detection ran and found nothing it already probed the well-known vLLM :8000 port (== DEFAULT_CHAT_BASE_URL), so re-probing that same target just burned another PROBE_TIMEOUT on a no-server cold start; treat it as unreachable directly. Relates to EAI-7347. Signed-off-by: Michael Roy <michael.roy@amd.com> * fix(dash): guard well-known ports free in local-detect port-priority tests detect_local_chat_prefers_local_server_over_no_endpoint (and the sibling detect_local_endpoint_probes_rocm_serve_default_port) only bound the rocm-serve port and assumed the higher-priority Lemonade/vLLM ports were unreachable. On the Windows CI runner that isn't guaranteed, so an ambient listener on :13305 or :8000 made detection return that endpoint instead of rocm-serve's, failing the assertion. Both tests now bind-and-release the higher-priority ports first to confirm they're free before asserting on the rocm-serve fallback. Signed-off-by: Michael Roy <michael.roy@amd.com> * fix(dash): make local-detect priority tests port-independent The previous fix (bind-then-release the higher-priority ports before asserting) still flaked on the Windows CI runner: something answered on the Lemonade port during the probe despite the guard, most likely an ambient listener the test doesn't control. TCP-connect-based detection means any listener on a well-known port — even a bare guard listener held open, as the very first attempt at this fix did — registers as "reachable", so real ports can't be made deterministic from a test. Split the port-priority logic in llm.rs into probe_first_reachable, tested directly against OS-assigned ephemeral ports instead of the real well-known ports. Gave detect_local_chat an injectable probe seam (detect_local_chat_with_probe) so its own test can verify the no-managed-executor wiring without depending on any real network state. Signed-off-by: Michael Roy <michael.roy@amd.com> * fix(dash): widen latency margin in detect_local_endpoint timing test detect_local_endpoint_bounded_latency_when_nothing_listening's 2x PROBE_TIMEOUT margin was still too tight for the Windows CI runner (observed 624ms against a 600ms threshold). A true sequential-probing regression takes ~3x PROBE_TIMEOUT, so widen the margin to 4x to absorb scheduling jitter on slower CI machines while still catching that regression. Signed-off-by: Michael Roy <michael.roy@amd.com> * test(dash): strengthen local chat startup coverage Signed-off-by: Michael Roy <michael.roy@amd.com> --------- Signed-off-by: Michael Roy <michael.roy@amd.com>
Reconcile PR #97 (configured-endpoint served-model discovery) with main's PR #100 startup local-first detection (EAI-7347). Conflicts in crates/rocm-dash-tui/src/app/{chat.rs,mod.rs} resolved as a union that keeps both behaviors: - Preserve main's local-engine detection: should_detect_local_chat, StartupChatOutcome/startup_chat_outcome, detect_local_chat_with_probe, and the #89 Press-only overlay key gate (is_actionable_key). - Port PR #97's /v1/models discovery for a *configured* chat endpoint into a new helper, chat::discover_configured_chat_model, wired only on the StartupChatOutcome::Configured path. Startup matrix (verified by unit tests in app::chat::tests): - Configured URL + no explicit model + reachable -> adopt served /v1/models id. - Configured URL + explicit model -> config precedence, never overridden. - Configured URL + unreachable -> never probed, never replaced (no fetch timeout; an unreachable explicit CLI/env URL is left untouched). - Local detected / OAuth paths unchanged from main. RED before the port: configured_endpoint_adopts_served_model_when_no_model_set failed against the merged-main stub (got "local-model", expected served id); GREEN after wiring the helper. Full workspace tests, clippy -D warnings, and scripts/smoke_local.py all pass. Signed-off-by: Michael Roy <michael.roy@amd.com>
…ale chat_url recovery) Layer PR #105's stale persisted-URL recovery onto PR #97's configured-endpoint model discovery (which already includes current main). Manually resolve the chat.rs/mod.rs conflicts, preserving both behaviors: - PR #97: discover_configured_chat_model() adopts a served /v1/models id for a reachable configured endpoint that has no explicit model (probe_ok path). - PR #105 (EAI-7360): stale_chat_url_replacement() swaps a dead persisted tui.chat_url for a genuinely-different live local engine on the Configured + !probe_ok path, emits a "server changed" notice pointing at `/detect save`, and normalize_base_url() treats trailing slash and /v1 formatting as the same endpoint. Gated on chat_url.is_some() + no api_key/auth_header so env URLs and authenticated remote gateways are never silently replaced. The two paths are mutually exclusive on the Configured branch's probe_ok, so they compose without interference; resolve_llm_config's CLI>config>env>probe precedence is untouched. Matrix verified: no URL/model -> local detection; reachable configured -> /v1/models discovery; stale persisted -> live replacement + notice; unreachable env/explicit URL -> fail honestly; explicit auth -> no silent fallback. Signed-off-by: Michael Roy <michael.roy@amd.com>
Summary
/v1/modelsat startup to discover the served model name when a chat endpoint is configured viaOPENAI_BASE_URL/--chat-urlbut no model is set.rocm serve … && OPENAI_BASE_URL=… rocmflow was broken — sending a message returned404 Not Found: The model 'local-model' does not exist, making chat unusable out of the box.resolve_llm_config'sDEFAULT_CHAT_MODEL = "local-model"placeholder and built the agent with it. Accepting the consent prompt doesn't rebuild, so the placeholder reached the first request./v1/modelsand adopt the served id (via the existingfetch_first_model/pick_first_modelhelpers), mirroring the managed-service path.Non-obvious decisions
probe_ok) so an unreachable configured endpoint never adds the 3s fetch timeout to startup.--chat-model/ config value always wins — discovery only fills the placeholder (enforced by the purewith_discovered_modelhelper).local-modelfallback when/v1/modelscan't be read — some endpoints ignore the model field or don't expose the listing.Test plan
cargo test -p rocm-dash-tui --lib— 549 passed (3 new unit tests forwith_discovered_model: placeholder replaced, explicit model preserved, failed discovery keeps fallback).cargo clippy -p rocm-dash-tui --all-targets— clean.rocm serve Qwen/Qwen2.5-1.5B-Instruct --engine vllm, thenOPENAI_BASE_URL=http://127.0.0.1:11435/v1 rocm, accept the endpoint in the Chat tab, send a message → expect a normal reply and nolocal-model404.Risk
Low — additive best-effort discovery on a single startup path; existing precedence and the neutral fallback are preserved.