feat(generation): add conditional thinking classifier
Routes simple/conversational messages to think=false automatically, even when the user has thinking enabled. Patterns checked: word count thresholds, complexity keywords, code blocks, skip patterns for greetings and simple CRUD. Workspace mode (think=true from frontend) still benefits from the classifier on short messages. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,65 @@ _TOOL_CALL_MARKER = re.compile(r"^\s*\[TOOL_CALLS\]\s*", re.IGNORECASE)
|
||||
|
||||
DB_FLUSH_INTERVAL = 5.0 # seconds between partial DB flushes
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Conditional thinking classifier
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Patterns that force think=True even on short messages
|
||||
_THINK_FORCE = re.compile(
|
||||
r"\b("
|
||||
r"analyz|compar|explain\s+why|help\s+me\s+(think|plan|understand|figure\s+out)|"
|
||||
r"step[- ]by[- ]step|debug|troubleshoot|diagnos|"
|
||||
r"pros\s+and\s+cons|trade[- ]?off|"
|
||||
r"architect|design\s+(a|the|my|this)|"
|
||||
r"write\s+a\s+(detailed|long|comprehensive|full)|"
|
||||
r"brainstorm|outline\s+(a|the|my)|"
|
||||
r"what\s+(are|is)\s+the\s+(best|difference|relationship|impact|implication)|"
|
||||
r"how\s+(do|does|should|would|can)\s+.{0,40}\s+work|"
|
||||
r"why\s+(is|are|does|do|did|would|should)\b"
|
||||
r")",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
# Patterns that force think=False regardless of message length
|
||||
_THINK_SKIP = re.compile(
|
||||
r"^(hi|hey|hello|thanks|thank\s+you|ok|okay|got\s+it|sounds\s+good|"
|
||||
r"great|perfect|sure|yes|no|yep|nope|nice|cool|awesome|"
|
||||
r"what('s| is) \d|what time|how many|remind me|add (a |an )?(task|note|reminder)|"
|
||||
r"create (a |an )?(task|note)|delete|update|mark .{0,30} (done|complete))\b",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
_WORD_COUNT_THRESHOLD = 60 # messages over this word count always use think=True
|
||||
_SHORT_MESSAGE_THRESHOLD = 12 # messages under this always use think=False
|
||||
|
||||
|
||||
def _should_think(user_content: str, think_requested: bool) -> bool:
|
||||
"""Return whether extended thinking should be used for this request.
|
||||
|
||||
If the caller didn't request thinking, we never enable it. If they did,
|
||||
we check whether the message is complex enough to warrant the overhead.
|
||||
"""
|
||||
if not think_requested:
|
||||
return False
|
||||
|
||||
text = user_content.strip()
|
||||
word_count = len(text.split())
|
||||
|
||||
if word_count <= _SHORT_MESSAGE_THRESHOLD:
|
||||
return False
|
||||
if _THINK_SKIP.match(text):
|
||||
return False
|
||||
if word_count >= _WORD_COUNT_THRESHOLD:
|
||||
return True
|
||||
if "```" in text:
|
||||
return True
|
||||
if _THINK_FORCE.search(text):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
# Human-readable labels for each tool, shown in the status indicator
|
||||
_TOOL_LABELS: dict[str, str] = {
|
||||
"create_task": "Creating task",
|
||||
@@ -202,6 +261,9 @@ async def run_generation(
|
||||
# Emit context event
|
||||
buf.append_event("context", {"context": context_meta})
|
||||
|
||||
# Apply thinking classifier — downgrade think=True for simple/conversational messages
|
||||
think = _should_think(user_content, think)
|
||||
|
||||
t_start = time.monotonic()
|
||||
timing: dict = {
|
||||
"think": think,
|
||||
|
||||
Reference in New Issue
Block a user