Chat UI model dropdown is ignored by the server #5
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_agent#5
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
The model selector dropdown in the chat UI (
#chatModel) has no effect on which LLM is used. The selection is sent to the server but silently discarded.Repro
Chattab).google/gemini-3-flash).default_modelregardless of selection.Root cause
The frontend correctly sends
modelin the request body:The server deserializes it into
ChatRequest.model:…but then never reads
request.model. Thechathandler only forwardsuser_ai_config(BYOK triple) intoagent.handle_message:So when no BYOK config is supplied, the agent falls through to
LlmClient::default_model(set fromaibroker_models[0]→openrouter_models[0]→groq_models[0]incrates/hero_agent/src/llm_client.rs:156-162), and the dropdown choice is dropped on the floor.Suggested fix
Thread
request.modelintohandle_message(or theLlmOptionsit builds) so that when no BYOK config is active, the selected model overridesdefault_model. BYOK should still take precedence when present.Affected files
crates/hero_agent_server/src/routes.rs(chat handler)crates/hero_agent/src/agent.rs(handle_messagesignature +LlmOptions::model)Implementation Spec: Wire
ChatRequest.modelthrough to the agentObjective
Make the Chat UI's model dropdown (
#chatModel) actually select the LLM used for a request. ThreadChatRequest.modelfrom the HTTP handler throughAgent::handle_messagedown to theLlmOptions::modelused by both the quick-response and tool-loop code paths, while preserving BYOK precedence and all existing behavior when no model is sent.Requirements
ChatRequest.model, when non-empty and non-"auto", MUST override the server-sideLlmClient::default_modelfor the Chat UI code path (POST /api/chat).user_ai_config) MUST continue to take precedence over a UI-selected model. If both are present, BYOK wins.ChatRequest.modelisNone, empty string, or the literal"auto"sentinel (the dropdown shipsautoas its default option), behavior MUST be identical to today (complexity-basedselect_modelin the tool path,Lightweightin the quick-response path).handle_message: thequick_responsepath (triage != Tools) AND theagent_looppath (triage == Tools).Agent::handle_messagegets a newselected_model: Option<&str>parameter. All 5 non-chat call sites (whatsapp.rs,telegram.rs,cli.rs,routes.rs:279rpc_chat,routes.rs:984voice) MUST be updated to passNone— they have no UI dropdown and MUST keep today's behavior.modelfield onChatRequestMUST remain optional and backward-compatible (clients that omit it still work).routes.rsor anyllm_client.rstests.Files to modify
crates/hero_agent/src/agent.rs— addselected_model: Option<&str>param tohandle_message; thread intoquick_responseand into themodelselection block beforeagent_loop; updatequick_responsesignature to accept it. BYOK still wins.crates/hero_agent_server/src/routes.rs— inchat(), normalizerequest.model(trim, drop empty and"auto") and pass it intohandle_message. Update the 2 other call sites in this file (rpc_chat~L279, voice handler ~L984) to passNone.crates/hero_agent/src/channels/whatsapp.rs— add trailingNonearg.crates/hero_agent/src/channels/telegram.rs— add trailingNonearg.crates/hero_agent/src/channels/cli.rs— add trailingNonearg.No files to create. No schema/DB changes. No frontend changes (the JS already sends
model).Step-by-step implementation plan
Step 1 — Extend
Agent::handle_messagesignature and thread model through (crates/hero_agent/src/agent.rs)Dependencies: none.
crates/hero_agent/src/agent.rsline 121, add a new parameter at the end ofhandle_message:quick_responsecall to also passselected_model:modelselection block at lines 273–281 with BYOK-first, then UI-selected, then complexity-based: The existing call toagent_loopat line 284 already passes&modeland does not need a signature change —agent_loopusesmodelas-is inLlmOptions { model: Some(model.to_string()), ... }at lines 555–560.Step 2 — Extend
quick_responsesignature and thread model through (crates/hero_agent/src/agent.rs)Dependencies: Step 1.
selected_model: Option<&str>as the last parameter ofquick_response.quick_response(buildingLlmOptions { model: Some(model_name.clone()), ... }) stays unchanged.Step 3 — Update chat handler to pass the UI model (crates/hero_agent_server/src/routes.rs)
Dependencies: Step 1.
chat()function, after line 637 (after theuser_ai_configmatch) and before thetokio::spawnat line 643, add:handle_messagecall at lines 649–651 to passselected_model.as_deref()as the new trailing argument.Step 4 — Update the other call sites in routes.rs to pass
None(crates/hero_agent_server/src/routes.rs)Dependencies: Step 1.
rpc_chat): change toStep 5 — Update channel call sites to pass
None(crates/hero_agent/src/channels/*.rs)Dependencies: Step 1.
crates/hero_agent/src/channels/whatsapp.rsline 267: add a trailingNone.crates/hero_agent/src/channels/telegram.rsline 204: add a trailingNone.crates/hero_agent/src/channels/cli.rsline 63: add a trailingNone.Step 6 — Build and verify
Dependencies: Steps 1–5.
/home/rawan/codescalers/hero_agent, runcargo build -p hero_agent -p hero_agent_serverto confirm all 6 call sites compile.cargo test -p hero_agent_serverto confirm the 7 existing unit tests still pass.[MODEL] Using UI-selected model: .... Confirm pickingauto(or a config where models list is empty) falls back to the existing complexity-based path with[MODEL] No BYOK or UI selection, selecting model by complexity.Acceptance criteria
Agent::handle_messagehas a newselected_model: Option<&str>parameter at the end of its signature.Agent::quick_responsehas a newselected_model: Option<&str>parameter at the end of its signature.POST /api/chat) passes the request'smodel(trimmed, with empty and"auto"treated asNone) intohandle_message.user_ai_config.modeltakes precedence overselected_modelin bothhandle_message(tool path) andquick_response(chitchat path).Lightweight(quick-response path).rpc_chat, voice handler, whatsapp, telegram, cli) passNoneforselected_modeland their observable behavior is unchanged.cargo build -p hero_agent -p hero_agent_serversucceeds.cargo test -p hero_agent_serverpasses (7/7 existing tests).dashboard.jsstill sends{ message, model, stream }as it does today.Notes / Edge cases
autowhen the models list is empty (seedashboard.js:521—models = [cfg.defaultModel || 'auto']). That string is NOT a real model name on any provider, so step 3's normalization MUST strip it. Failing to strip"auto"would result in all providers rejecting the request.ChatRequest.modelalready has#[serde(default)]on line 579–582 of routes.rs — no schema change is needed, clients omittingmodelcontinue to work.agent_loopalready acceptsmodel: &strand buildsLlmOptions { model: Some(model.to_string()), ... }— no change needed there. The one-lineoptions.modellookup insideLlmClient::chat_completion*already uses the passed model and falls back todefault_modelonly whenoptions.modelisNone.LlmClient::default_modelis derived in constructor (llm_client.rs:156–162) asaibroker_models[0] → openrouter_models[0] → groq_models[0] → "claude-sonnet-4.5". This continues to be the ultimate fallback; we only affect it when a UI selection is actively provided.[MODEL]/[QUICK_RESPONSE_MODEL]tracing prefixes already exist in today's code — keep the same style for the new branch so existing log-watching dashboards keep working.user_ai_provider+user_ai_api_key+user_ai_model, theuser_ai_configis built (routes.rs:622–637) and wins over anyselected_model. This matches the issue's requirement: "BYOK should still take precedence when present."Test Results
cargo build -p hero_agent -p hero_agent_serversucceeded (no errors, only pre-existing warnings).cargo test -p hero_agent_serverexecuted the unit test suite:All pre-existing tests continue to pass. The change is a pure parameter addition and does not alter any of the behaviors exercised by the current test suite.
Implementation Summary
The Chat UI
#chatModeldropdown is now honored server-side.ChatRequest.modelis threaded from the/api/chathandler throughAgent::handle_messageinto both the tool-loop (agent_loop) and chitchat (quick_response) model-selection paths. BYOK (user_ai_config) still takes precedence; when no BYOK is active and a non-empty, non-automodel is provided by the client, that model overridesLlmClient::default_model.Files modified
crates/hero_agent/src/agent.rs— addedselected_model: Option<&str>parameter toAgent::handle_messageandAgent::quick_response; inserted a UI-selected branch between the BYOK and complexity/lightweight fallbacks in both.crates/hero_agent_server/src/routes.rs—chat()normalizesrequest.model(trims whitespace, treats empty and theautosentinel asNone) and forwards it intohandle_message.rpc_chatand the voice handler were updated to passNone— their observable behavior is unchanged.crates/hero_agent/src/channels/whatsapp.rs— passNonefor the new parameter.crates/hero_agent/src/channels/telegram.rs— passNonefor the new parameter.crates/hero_agent/src/channels/cli.rs— passNonefor the new parameter.No frontend changes were needed (
dashboard.jswas already sendingmodel). No tests were added; the existing unit-test surface does not coverhandle_messageor thechatroute. A follow-up PR could add a small unit test for themodelnormalization helper ("" | "auto" | " Auto " -> None).Behavior matrix
request.modeluser_ai_config.model(BYOK, unchanged)autoauto, or omittedLightweight(chitchat path) — unchanged from todayVerification
cargo build -p hero_agent -p hero_agent_server— success.cargo test -p hero_agent_server— 8 passed, 0 failed.Diff size: 5 files changed, 23 insertions(+), 9 deletions(-).
Notes
LlmClientalready handles unknown model names by failing over, so validating in the handler would couple the HTTP layer to LLM configuration for no additional safety.[MODEL]/[QUICK_RESPONSE_MODEL]prefixes so existing log dashboards continue to work.