fix(titles): reject trivial-echo new session titles (gate-rebase of #6529)

Co-authored-by: Hermes Agent <hermes@nousresearch.com>
This commit is contained in:
nesquena-hermesandHermes Agent authored and nesquena-hermes committed 2026-07-26 20:22:25 +00:00
1 parent 8e3bb02e0e
commit b145f59312
2 files changed
+86 -5

No files matched your search

+35 -5
View File
@@ -2839,13 +2839,19 @@ def _sanitize_generated_title(text: str) -> str:
s = re.sub(r'^\s*title\s*:\s*', '', s, flags=re.IGNORECASE) s = re.sub(r'^\s*title\s*:\s*', '', s, flags=re.IGNORECASE)
s = s.strip(" \t\r\n\"'`*_~") s = s.strip(" \t\r\n\"'`*_~")
s = re.sub(r'\s+', ' ', s).strip() s = re.sub(r'\s+', ' ', s).strip()
# Guard against chain-of-thought leakage and meta-reasoning patterns. # Guard against chain-of-thought leakage, meta-reasoning, and trivial echo.
if _looks_invalid_generated_title(s): if _is_bad_new_title(s):
return '' return ''
return s[:80] return s[:80]
def _looks_invalid_generated_title(text: str) -> bool: def _looks_invalid_generated_title(text: str) -> bool:
"""True when an existing/persisted title is structurally invalid (CoT leak).
Intentionally does NOT reject short conversational words like "Done" or
"Cool" — those can be legitimate stored titles. Use ``_is_bad_new_title``
when validating a freshly generated candidate.
"""
s = str(text or '') s = str(text or '')
if not s.strip(): if not s.strip():
return True return True
@@ -2857,7 +2863,31 @@ def _looks_invalid_generated_title(text: str) -> bool:
or re.search(r'^\s*(i|we)\s+(should|need to|will|can)\b', s, flags=re.IGNORECASE) or re.search(r'^\s*(i|we)\s+(should|need to|will|can)\b', s, flags=re.IGNORECASE)
or re.search(r'^\s*let me\b', s, flags=re.IGNORECASE) or re.search(r'^\s*let me\b', s, flags=re.IGNORECASE)
or re.search(r"^\s*here(?:'s| is) (?:a |my )?(?:thinking|thought)", s, flags=re.IGNORECASE) or re.search(r"^\s*here(?:'s| is) (?:a |my )?(?:thinking|thought)", s, flags=re.IGNORECASE)
or re.search(r'^\s*(ok|okay|done|all set|complete|completed|finished)\b[\s.!?]*$', s, flags=re.IGNORECASE) )
def _is_bad_new_title(text: str) -> bool:
"""True when a freshly generated title candidate should be discarded.
Includes structural CoT rejection plus trivial single-token echo replies
(pong, yes, done, cool, …) that are useless as first-generation titles.
"""
if _looks_invalid_generated_title(text):
return True
s = str(text or '').strip()
_token = re.sub(r'[\s.!?]+$', '', s, flags=re.IGNORECASE)
if re.fullmatch(
r'(?:pong|ping|yes|no|yep|nope|hi|hello|hey|thanks|thank you|sure|k|kk|cool|nice|lol|ok|okay|done)',
_token,
flags=re.IGNORECASE,
):
return True
return bool(
re.search(
r'^\s*(ok|okay|done|all set|complete|completed|finished)\b[\s.!?]*$',
s,
flags=re.IGNORECASE,
)
) )
@@ -2992,7 +3022,7 @@ def _first_exchange_snippets(messages):
# is asking...", etc.). Assistant rows that carry tool_calls but # is asking...", etc.). Assistant rows that carry tool_calls but
# also contain a substantive answer text are kept — those are # also contain a substantive answer text are kept — those are
# agentic first-turn plans that are legitimate title candidates. # agentic first-turn plans that are legitimate title candidates.
if m.get('tool_calls') and (not candidate or _looks_invalid_generated_title(candidate)): if m.get('tool_calls') and (not candidate or _is_bad_new_title(candidate)):
continue continue
if candidate: if candidate:
asst_text = candidate asst_text = candidate
@@ -3016,7 +3046,7 @@ def _latest_exchange_snippets(messages):
if role == 'assistant' and not asst_text: if role == 'assistant' and not asst_text:
candidate = _message_text(m.get('content')) candidate = _message_text(m.get('content'))
# Skip tool-call-only preambles # Skip tool-call-only preambles
if m.get('tool_calls') and (not candidate or _looks_invalid_generated_title(candidate)): if m.get('tool_calls') and (not candidate or _is_bad_new_title(candidate)):
continue continue
if candidate: if candidate:
asst_text = candidate asst_text = candidate
+51
View File
@@ -98,3 +98,54 @@ class TestThinkingPreambleStripping:
assert not _looks_invalid_generated_title("Python import debugging"), ( assert not _looks_invalid_generated_title("Python import debugging"), (
"Real titles must still pass the invalid-title guard" "Real titles must still pass the invalid-title guard"
) )
def test_looks_invalid_generated_title_rejects_trivial_echo(self):
"""Trivial echo rejection applies to NEW titles only, not existing titles."""
from api.streaming import _is_bad_new_title, _looks_invalid_generated_title
assert _is_bad_new_title("pong")
assert _is_bad_new_title("Yes!")
assert _is_bad_new_title("Done")
assert _is_bad_new_title("Cool")
# Existing-title validity must accept short conversational titles so
# every subsequent turn does not re-fire the title LLM.
assert not _looks_invalid_generated_title("pong")
assert not _looks_invalid_generated_title("Done")
assert not _looks_invalid_generated_title("Cool")
assert not _looks_invalid_generated_title("Repo Metrics Overview")
def test_snippet_extraction_skips_tool_preamble_echo(self):
"""First/latest exchange helpers skip tool rows whose content is echo/CoT."""
from api.streaming import _first_exchange_snippets, _latest_exchange_snippets
messages = [
{"role": "user", "content": "ping"},
{
"role": "assistant",
"content": "Let me check my memory first.",
"tool_calls": [{"id": "1", "function": {"name": "memory"}}],
},
{
"role": "assistant",
"content": "pong",
"tool_calls": [{"id": "2", "function": {"name": "echo"}}],
},
{"role": "assistant", "content": "Repo metrics look healthy overall."},
]
u, a = _first_exchange_snippets(messages)
assert u == "ping"
assert "healthy" in a.lower()
assert "pong" not in a.lower()
assert "let me" not in a.lower()
latest = [
{"role": "user", "content": "status?"},
{
"role": "assistant",
"content": "Done",
"tool_calls": [{"id": "9", "function": {"name": "status"}}],
},
{"role": "assistant", "content": "All services green."},
]
u2, a2 = _latest_exchange_snippets(latest)
assert u2 == "status?"
assert "green" in a2.lower()