fix(title): stop model-switch marker from becoming the session title
Switching models before sending the first real message titled the session "[System: The active model for this chat has…" instead of the user's actual question. `_append_model_switch_marker` persists its notice with `role="user"` because strict OpenAI-compatible providers reject a system message that is not first (#48338). Titling had no way to tell that apart from a genuine opening turn, which caused two distinct failures: 1. `_MACHINE_PREFIXES` did not cover the marker. Its `[System: ` prefix matches none of `[CONTEXT COMPACTION`, `[Runtime note:`, or `[SYSTEM]` (different case, no closing bracket), so `is_titleable_user_message()` returned True and the marker was formatted into the title. 2. `maybe_auto_title()` counted the marker as a user message. With the marker present, the first real question arrived at `user_msg_count == 2` and the `> 1` guard returned early, so the session was never titled at all and its `title` column stayed NULL. Fixing only (1) would therefore have traded a wrong title for a permanently missing one. Add the marker prefix to `_MACHINE_PREFIXES` (kept in sync with `tui_gateway.server._MODEL_SWITCH_MARKER_PREFIX`) and count only titleable user messages when detecting the opening turn. The guard stays narrow: ordinary user text that happens to start with "[System:" still titles normally. Adds 6 regression tests, verified to fail without the fix.
This commit is contained in:
@@ -132,6 +132,15 @@ _MACHINE_PREFIXES = (
|
||||
"[Runtime note:",
|
||||
"[System note:",
|
||||
"[SYSTEM]",
|
||||
# Model-switch marker from tui_gateway.server._append_model_switch_marker.
|
||||
# It is persisted with role="user" (strict OpenAI-compatible providers
|
||||
# reject a system message that is not first — #48338), so without this
|
||||
# entry it looks like a real opening turn: switching models before the
|
||||
# first real message titled the session
|
||||
# "[System: The active model for this chat has…" instead of the user's
|
||||
# actual question. Keep in sync with
|
||||
# tui_gateway.server._MODEL_SWITCH_MARKER_PREFIX.
|
||||
"[System: The active model for this chat has changed to ",
|
||||
)
|
||||
|
||||
|
||||
@@ -673,10 +682,19 @@ def maybe_auto_title(
|
||||
# turn prologue, and after it when called post-response, so accept both.
|
||||
# Entries are dicts; anything else means a caller passed the wrong
|
||||
# positional and titling must degrade quietly rather than raise.
|
||||
#
|
||||
# Machine-authored openers are excluded from the count. They are persisted
|
||||
# with role="user" (see _MACHINE_PREFIXES), so counting them would make a
|
||||
# session that opened with e.g. a model-switch marker look like it was
|
||||
# already past its opening turn — the real first question then arrives at
|
||||
# count 2 and never gets titled at all, leaving the session permanently
|
||||
# NULL-titled.
|
||||
user_msg_count = sum(
|
||||
1
|
||||
for m in (conversation_history or [])
|
||||
if isinstance(m, dict) and m.get("role") == "user"
|
||||
if isinstance(m, dict)
|
||||
and m.get("role") == "user"
|
||||
and is_titleable_user_message(m.get("content") or "")
|
||||
)
|
||||
if user_msg_count > 1 and not _session_is_untitled(session_db, session_id):
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user