Skip to content
Open
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
17 changes: 12 additions & 5 deletions coworker/server/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5211,13 +5211,20 @@ def _extra_roots_of(
]

# -- LLM auto-titles (FB-010) -------------------------------------------------
# The title must speak the user's language: with no language pinned, an English
# instruction ("4-5 word title") yields English titles for Chinese/Japanese/…
# sessions. Same-language also relaxes the word count to a length that is natural
# for the opener's script (CJK titles are a few characters, not 4-5 "words").
_AUTOTITLE_PROMPT = (
"You title chat sessions. Given the user's opening message(s) — and, when "
"present, the assistant's first reply for context — reply with ONLY a 4-5 word "
"title for the session, named after what the session is actually about — no "
"quotes or punctuation wrapping it. If there is no topic at all ("
'"hey", "how are you", "hi there" and a generic reply), reply with exactly: '
"small-talk"
"present, the assistant's first reply for context — reply with ONLY a short "
"title for the session, named after what the session is actually about. "
"Write the title in the same language as the user's opening message "
"(4-5 words for English; the natural equivalent brevity for other "
"languages) — no quotes or punctuation wrapping it. If there is no topic "
"at all ("
'"hey", "how are you", "hi there" and a generic reply), reply with '
"exactly: small-talk"
)

def _maybe_autotitle(self, session_id: str) -> None:
Expand Down
35 changes: 35 additions & 0 deletions tests/test_autotitle.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,38 @@ async def go():
assert mgr._autotitle_attempts[sid] == 1

asyncio.run(go())


async def test_title_call_pins_the_opener_language(tmp_path):
# With no language pinned in the titling instruction, an English system prompt
# ("4-5 word title") biases the model toward English titles even for non-English
# sessions. The prompt must tell the model to match the opener's language.
mgr, provider = _mgr(tmp_path, [_text("好的")], ["日本行程规划"])
await _turn(mgr, "lang1", "帮我规划一下日本的旅行")

system = provider.title_calls[0][0]["content"]
assert "same language as the user's opening message" in system


async def test_non_english_opener_keeps_non_english_title(tmp_path):
# End to end: a CJK title survives the sanitizer (whitespace collapse and quote
# strip don't touch CJK) and the 80-char absurdity cap, and is stored verbatim.
mgr, _ = _mgr(tmp_path, [_text("好的,我来帮你安排")], ["日本行程规划"])
await _turn(mgr, "lang2", "帮我规划一下日本的旅行")
assert mgr.session_store.load("lang2").title == "日本行程规划"


async def test_non_english_small_talk_still_hits_the_sentinel(tmp_path):
# The sentinel is a fixed English token regardless of the session's language;
# normalization must keep catching it after the prompt rewording.
mgr, provider = _mgr(
tmp_path,
[_text("你好呀"), _text("在的")],
["small-talk", "季度汇报整理"],
)
await _turn(mgr, "lang3", "你好")
assert mgr.session_store.title_state("lang3")["auto_title"] is None

await _turn(mgr, "lang3", "帮我整理季度汇报")
assert len(provider.title_calls) == 2
assert mgr.session_store.load("lang3").title == "季度汇报整理"
Loading