Skip to content

V2 chat: selecting an agent leaves the model and reasoning pickers active, and the model still wins #1392

Description

@paullizer

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

  1. Open the V2 chat page (/v2/chat).
  2. Pick a model from the Model picker.
  3. Pick an agent from the Agent picker.
  4. Observe that the model still shows as selected and the Reasoning picker is still offered.
  5. 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") in semantic_kernel_loader.py.
  • reasoning_effort only ever reaches the direct-model call parameters, via _resolve_reasoning_effort_for_model into api_params / stream_params in route_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 Model placeholder 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 updateReasoningButtonVisibility in the classic client.
  • With an agent selected, the request carries agent_info and 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:

  1. Image generation hides the model picker but leaves the agent picker visible, and the classic client forces image_generation = false when an agent is explicitly tagged.
  2. retryMessage is invoked with no options from MessageActions.tsx, so a retry uses server defaults rather than the composer's current selection.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions