Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1539,7 +1539,21 @@ def switch_llm(self, llm: LLM) -> None:

Args:
llm: LLM to install on the agent.

Raises:
ValueError: If the conversation's agent is an :class:`ACPAgent`.
Swapping OpenHands' own LLM object has no effect on an ACP
subprocess, which owns its own model. Persisting the swap would
leave base_state.json and the live session disagreeing (#4158);
use :meth:`switch_acp_model` for ACP conversations instead.
"""
if isinstance(self.agent, ACPAgent):
raise ValueError(
"switch_llm/switch_profile is not supported for ACP conversations. "
"The ACP server owns its own model, so switching the OpenHands LLM "
"would not take effect on the live session. Use switch_acp_model to "
"change the model of an ACP conversation."
)
try:
new_llm = self.llm_registry.get(llm.usage_id)
except KeyError:
Expand Down
28 changes: 28 additions & 0 deletions tests/sdk/conversation/test_switch_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,34 @@ def test_switch_acp_model_disarms_discarded_agent_finalizer(tmp_path):
assert switched._atexit_callback is None


def test_switch_llm_rejects_acp_agent(tmp_path):
"""Regression for #4158: switch_llm must reject ACP conversations.

Swapping the OpenHands LLM has no effect on the ACP subprocess (which owns
its own model), so persisting the swap would leave base_state.json and the
live session disagreeing. It must fail loudly instead of half-applying.
"""
conv, agent = _make_acp_conversation(tmp_path)
with pytest.raises(ValueError, match="not supported for ACP"):
conv.switch_llm(_make_llm("kimi", "kimi"))
# The agent is untouched — still the ACP agent, and no LLM swap occurred.
assert conv.agent is agent
assert isinstance(conv.state.agent, ACPAgent)


def test_switch_profile_rejects_acp_agent(tmp_path, profile_store):
"""Regression for #4158: switch_profile must reject ACP conversations.

The persisted state must not be rewritten to the profile's OpenHands LLM
while the live ACP session keeps the old agent.
"""
conv, agent = _make_acp_conversation(tmp_path)
with pytest.raises(ValueError, match="not supported for ACP"):
conv.switch_profile("fast")
assert conv.agent is agent
assert isinstance(conv.state.agent, ACPAgent)


def test_switch_profile(profile_store):
"""switch_profile switches the agent's LLM."""
conv = _make_conversation()
Expand Down
Loading