microsoft / microsoft/simplechat
V2 chat: selecting an agent leaves the model and reasoning pickers active, and the model still wins
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 152
- Forks
- 116
- Avg merge
- 7h 7m
- Merged PRs (30d)
- 122
Description
Summary
In the V2 chat composer, the Model, Agent and Reasoning pickers are all independently live. Selecting an agent leaves a model showing as selected and a reasoning level selectable, even though an agent can act on neither.
The visible inconsistency is the smaller half. The request also sends all three fields together, and the server reads a model identity sent alongside agent_info as a deliberate override — so an agent could answer through a different model than the one it is configured with, silently.
Steps to reproduce
- Open the V2 chat page (
/v2/chat). - Pick a model from the Model picker.
- Pick an agent from the Agent picker.
- Observe that the model still shows as selected and the Reasoning picker is still offered.
- Send a message.
Expected: the agent answers using its own configured deployment, and the composer does not offer choices that cannot apply.
Actual: the composer presents a model and a reasoning level as if they were in force, and the request carries them, which suppresses the agent's own model resolution.
Root cause
Two independent halves.
The controls were never told about each other
Composer.tsx rendered the model picker on gating.showModelPicker (false only during image generation) and the reasoning picker on model support alone. Neither consulted the agent selection.
They are not independent:
- An agent answers with its own deployment —
deployment = agent.get("azure_openai_gpt_deployment")insemantic_kernel_loader.py. reasoning_effortonly ever reaches the direct-model call parameters, via_resolve_reasoning_effort_for_modelintoapi_params/stream_paramsinroute_backend_chats.py.
The request sent both halves, which the server reads as an override
chatStore.sendMessage assigned the model identity unconditionally, then appended agent_info and reasoning_effort. The route only lets an agent request choose its own model when no model identity was sent:
# route_backend_chats.py
should_use_default_model = (
_has_chat_agent_selection(request_agent_info)
and settings.get('enable_multi_model_endpoints', False)
and not data.get('model_id')
and not data.get('model_endpoint_id')
)
Because V2 always sent model_id and model_endpoint_id, that branch never fired. The route has agent-without-a-model handling for every configuration — the multi-endpoint default, the first APIM deployment, and the configured default model — and V2 reached none of it.
Impact
- Correctness: an agent could answer through the wrong model, with no error and nothing in the UI to indicate it.
- Usability: two controls appeared to offer choices that had no effect.
Affects the V2 interface only. The classic interface hides the model picker and the reasoning button when agents are enabled, so the confusing presentation is V2-specific.
Note on the classic client
V1 has a related asymmetry worth recording: getCurrentAgentSelection checks that agent mode is active, but getCurrentModelSelection reads the model select without checking that agent mode has hidden it — so V1 posts a model alongside an agent too. Its UI hides the picker, so it is not user-visible there, but the request shape has the same defect.
Resolution
Fixed in v0.261.034 by #1391.
- Selecting an agent renders the model picker as its plain
Modelplaceholder in muted styling, with a tooltip naming the agent. The picker stays clickable, and its menu still marks the retained model. - Choosing a model clears the agent; choosing an agent retains the model rather than clearing it.
- The reasoning picker is hidden while an agent is selected, and also during image generation, matching
updateReasoningButtonVisibilityin the classic client. - With an agent selected, the request carries
agent_infoand nothing else.
The rule lives in one module, buildSelectionFields, read by both the toolbar and the request builder — the original defect arose because each decided separately.
Documented in docs/explanation/fixes/V2_AGENT_MODEL_EXCLUSIVITY_FIX.md. Covered by functional_tests/test_v2_agent_model_exclusivity.py and functional_tests/test_v2_agent_model_exclusivity_logic.ts.
Known gaps left open
Two related pre-existing inconsistencies were found while tracing this and deliberately left out of the fix:
- Image generation hides the model picker but leaves the agent picker visible, and the classic client forces
image_generation = falsewhen an agent is explicitly tagged. retryMessageis invoked with no options fromMessageActions.tsx, so a retry uses server defaults rather than the composer's current selection.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
The issue is already resolved by #1391. For verification, inspect Composer.tsx, chatStore.sendMessage, route_backend_chats.py, and semantic_kernel_loader.py, then run functional_tests/test_v2_agent_model_exclusivity.py and functional_tests/test_v2_agent_model_exclusivity_logic.ts; done means the documented V2 exclusivity behavior is covered and passing.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, typescript
- Domain
- backend-api-design, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 25/100