fix(dash): prefer a local chat server over the cloud default at startup#100
Conversation
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 #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>
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>
…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>
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>
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>
rominf
left a comment
There was a problem hiding this comment.
LGTM. Startup now reuses the full detect_local_chat path (registry + parallel well-known-port probe) so a local server correctly wins over the cloud default:
- when detection finds a server,
detected.is_some()short-circuitsresolve_llm_config→ local wins; - when none exists,
detection_ran && detected.is_none()setsprobe_ok = falseand falls through to OAuth without a redundant re-probe (VLLM_ENDPOINTandDEFAULT_CHAT_BASE_URLresolve to the same loopback:8000 target); - the api-key gate in
should_detect_local_chatcorrectly prevents detection's keyless config from dropping a configured credential.
The parallel probe preserves priority order via the order-preserving handles Vec + find_map, panicked probe threads are swallowed as false, and there are no hot-path unwraps. SPDX headers present; removed detect_managed_chat import is safe (now called transitively).
Approving.
|
🔴 Automated review · pr-review-watcher · f64bb40 SummaryAt TUI startup, this swaps the old 🚫 Blocking (must fix before merge)None. Non-blocking
|
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>
Summary
DEFAULT_CHAT_BASE_URL(:8000) and then fell back to the ChatGPT cloud OAuth flow, even though the manual'd'detect action 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.:8000(e.g. arocm servedefault install) was invisible at startup, so the user landed in the cloud login flow instead of talking to their already-running local server.Root cause
event_loop()'s startup block (crates/rocm-dash-tui/src/app/mod.rs) calleddetect_managed_chat()(registry-only) and then a singleprobe_endpoint()against one URL, instead of reusingchat::detect_local_chat()— the same registry-first + 3-port-probe + model-fetch detection the in-app'd'action already used.Fix
detect_local_chat()in the same branch that previously calleddetect_managed_chat()(gated on "no explicitchat_url/env URL configured" — CLI/config precedence is untouched).llm::detect_local_endpoint()'s port probe withstd::thread::scopeso a cold start with nothing listening still costs onePROBE_TIMEOUT(~300ms) instead of three sequential probes (~900ms worst case), keeping startup latency bounded.Coordination with #97
Unmerged PR #97 (volen) also touches this startup branch, adding model discovery inside
resolve_llm_config's own fallback path whenchat_modelisNone. This change is conflict-minimal: it only swapsdetect_managed_chatfordetect_local_chatand leaves theresolve_llm_config(...)call itself untouched, so a rebase either direction should be a clean textual merge.Relates to EAI-7347.
Test plan
cargo build -p rocm-dash-tuicargo clippy -p rocm-dash-tui --all-targets --all-features(clean under-D warnings)cargo test -p rocm-dash-tui -- --test-threads=1(all green, incl. 3 new tests)llm::tests::detect_local_endpoint_prefers_lemonade_when_multiple_reachable— priority order preserved under parallel probingllm::tests::detect_local_endpoint_bounded_latency_when_nothing_listening— latency regression guardapp::chat::tests::detect_local_chat_prefers_local_server_over_no_endpoint— startup-preference behavior (a listener onrocm serve's default port is detected with no executor/registry available)