Skip to content
Closed
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
4 changes: 2 additions & 2 deletions tests/test_tui_gateway_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3497,11 +3497,11 @@ def fake_switch_model(**kwargs):
"Model: anthropic/claude-sonnet-4.6\nProvider: anthropic"
)
assert agent._cached_system_prompt == db.system_prompt
assert session["history"][-1]["role"] == "system"
assert session["history"][-1]["role"] == "user"
assert "changed to anthropic/claude-sonnet-4.6" in session["history"][-1]["content"]
assert db.messages[-1] == {
"session_id": "session-key",
"role": "system",
"role": "user",
"content": session["history"][-1]["content"],
}
# ...and the shared process env was NOT touched.
Expand Down
40 changes: 40 additions & 0 deletions tests/tui_gateway/test_model_switch_marker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Regression tests for gateway model-switch history markers."""

from types import SimpleNamespace
from unittest.mock import MagicMock

from tui_gateway.server import _append_model_switch_marker


def test_model_switch_marker_is_user_message_not_mid_history_system():
"""Model switches must not inject a system role after conversation turns.

Strict OpenAI-compatible providers such as vLLM reject message arrays that
contain system messages after the beginning of the conversation.
"""
db = MagicMock()
session = {
"session_key": "sess-1",
"history": [
{"role": "user", "content": "hello"},
{"role": "assistant", "content": "hi"},
],
"history_version": 7,
"agent": SimpleNamespace(_session_db=db),
}

_append_model_switch_marker(
session,
model="qwen3.6-35b",
provider="api.lucasnicolas.dev",
)

marker = session["history"][-1]
assert marker["role"] == "user"
assert "active model for this chat has changed to qwen3.6-35b" in marker["content"]
assert session["history_version"] == 8
db.append_message.assert_called_once_with(
session_id="sess-1",
role="user",
content=marker["content"],
)
10 changes: 7 additions & 3 deletions tui_gateway/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1721,7 +1721,11 @@ def _append_model_switch_marker(session: dict | None, *, model: str, provider: s
f"{model}{provider_part}. From this point forward, use this runtime "
"metadata when answering questions about what model/provider is active.]"
)
entry = {"role": "system", "content": marker}
# Persist as a user message, not a system message. The gateway appends
# this marker after prior conversation turns, and strict OpenAI-compatible
# providers such as vLLM reject system messages that are not at the
# beginning of the API message list.
entry = {"role": "user", "content": marker}

lock = session.get("history_lock")
if lock is not None:
Expand All @@ -1736,14 +1740,14 @@ def _append_model_switch_marker(session: dict | None, *, model: str, provider: s
agent = session.get("agent")
db = getattr(agent, "_session_db", None) if agent is not None else None
if db is not None:
db.append_message(session_id=session_key, role="system", content=marker)
db.append_message(session_id=session_key, role="user", content=marker)
return

_ensure_session_db_row(session)
with _session_db(session) as scoped_db:
if scoped_db is not None:
scoped_db.append_message(
session_id=session_key, role="system", content=marker
session_id=session_key, role="user", content=marker
)
except Exception:
logger.debug("failed to persist model switch marker", exc_info=True)
Expand Down