diff --git a/src/fabledassistant/services/generation_task.py b/src/fabledassistant/services/generation_task.py index 527143a..e7fa185 100644 --- a/src/fabledassistant/services/generation_task.py +++ b/src/fabledassistant/services/generation_task.py @@ -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,