tenseleyflow/loader / bb48c80

Browse files

Move task classification into runtime

Authored by espadonne
SHA
bb48c80b9ad7f3e5bfdbbf60db557097ebd8e6cf
Parents
e36e64f
Tree
6ad44c9

4 changed files

StatusFile+-
M src/loader/agent/reasoning.py 5 108
M src/loader/runtime/conversation.py 1 4
A src/loader/runtime/task_classification.py 193 0
A tests/test_task_classification.py 29 0
src/loader/agent/reasoning.pymodified
@@ -30,114 +30,11 @@ from ..runtime.reasoning_types import (
3030
     TaskCompletionCheck,
3131
     TaskDecomposition,
3232
 )
33
-
34
-
35
-# === Query Classification ===
36
-
37
-def is_conversational(message: str) -> bool:
38
-    """Detect if a message is conversational rather than a task.
39
-
40
-    Returns True for greetings, casual chat, simple questions about the agent.
41
-    These don't need tool calling - just a quick response.
42
-    """
43
-    msg = message.lower().strip()
44
-
45
-    # Very short messages are usually conversational
46
-    if len(msg) < 15:
47
-        # Greetings
48
-        greetings = [
49
-            "hi", "hello", "hey", "yo", "sup", "hiya", "howdy",
50
-            "ello", "hallo", "greetings", "good morning", "good afternoon",
51
-            "good evening", "morning", "evening", "afternoon",
52
-            "what's up", "whats up", "wassup", "how are you",
53
-            "how's it going", "hows it going",
54
-        ]
55
-        if any(msg.startswith(g) or msg == g for g in greetings):
56
-            return True
57
-
58
-    # Questions about the agent itself
59
-    agent_questions = [
60
-        "who are you", "what are you", "what can you do",
61
-        "how do you work", "what is loader", "what's loader",
62
-        "help", "what is this", "how does this work",
63
-    ]
64
-    if any(q in msg for q in agent_questions):
65
-        return True
66
-
67
-    # Casual/social messages
68
-    casual = [
69
-        "thanks", "thank you", "thx", "ty",
70
-        "cool", "nice", "great", "awesome", "ok", "okay",
71
-        "bye", "goodbye", "see you", "later", "cya",
72
-        "lol", "haha", "hehe", "lmao",
73
-        "please", "sorry", "oops",
74
-    ]
75
-    if msg in casual or any(msg == c for c in casual):
76
-        return True
77
-
78
-    # Messages that are clearly NOT conversational (tasks)
79
-    task_indicators = [
80
-        "create", "make", "build", "write", "edit", "delete", "remove",
81
-        "run", "execute", "install", "fix", "debug", "test", "check",
82
-        "find", "search", "show", "list", "read", "open", "close",
83
-        "add", "update", "change", "modify", "refactor", "implement",
84
-        "file", "folder", "directory", "code", "function", "class",
85
-        "git", "npm", "pip", "python", "node", "bash", "command",
86
-    ]
87
-    if any(ind in msg for ind in task_indicators):
88
-        return False
89
-
90
-    # Short messages without task indicators are likely conversational
91
-    if len(msg) < 30 and not any(c in msg for c in [".", "/", "\\", "`"]):
92
-        return True
93
-
94
-    return False
95
-
96
-
97
-def estimate_complexity(message: str) -> str:
98
-    """Estimate query complexity for token budgeting.
99
-
100
-    Returns: "trivial", "simple", "moderate", or "complex"
101
-    """
102
-    msg = message.lower()
103
-    word_count = len(message.split())
104
-
105
-    # Trivial: greetings, thanks, very short
106
-    if is_conversational(message) or word_count < 5:
107
-        return "trivial"
108
-
109
-    # Complex indicators
110
-    complex_indicators = [
111
-        "project", "application", "website", "api", "database",
112
-        "refactor", "migrate", "upgrade", "implement", "design",
113
-        "multiple", "several", "all", "entire", "whole",
114
-        "and then", "after that", "also", "as well",
115
-    ]
116
-    complex_count = sum(1 for ind in complex_indicators if ind in msg)
117
-
118
-    if complex_count >= 2 or word_count > 50:
119
-        return "complex"
120
-
121
-    # Simple indicators
122
-    simple_indicators = [
123
-        "what is", "how do", "show me", "list", "read",
124
-        "single", "one", "just", "only", "quick",
125
-    ]
126
-    if any(ind in msg for ind in simple_indicators) and word_count < 20:
127
-        return "simple"
128
-
129
-    return "moderate"
130
-
131
-
132
-def get_token_budget(complexity: str) -> tuple[int, int]:
133
-    """Get (max_tokens, context_tokens) for a complexity level."""
134
-    budgets = {
135
-        "trivial": (256, 2048),    # Quick response, minimal context
136
-        "simple": (512, 4096),     # Short response, some context
137
-        "moderate": (1024, 8192),  # Normal response
138
-        "complex": (2048, 16384),  # Full response, full context
139
-    }
140
-    return budgets.get(complexity, (1024, 8192))
33
+from ..runtime.task_classification import (
34
+    estimate_complexity,
35
+    get_token_budget,
36
+    is_conversational,
37
+)
14138
 
14239
 
14340
 # Prompts for reasoning stages
src/loader/runtime/conversation.pymodified
@@ -7,10 +7,6 @@ from collections.abc import Awaitable, Callable
77
 from pathlib import Path
88
 from typing import Any
99
 
10
-from ..agent.reasoning import (
11
-    estimate_complexity,
12
-    get_token_budget,
13
-)
1410
 from ..llm.base import Message, Role, ToolCall
1511
 from .assistant_turns import AssistantTurnRequester
1612
 from .completion_policy import CompletionPolicy
@@ -23,6 +19,7 @@ from .hooks import build_default_tool_hooks
2319
 from .phases import TurnPhase, TurnPhaseTracker
2420
 from .repair import ResponseRepairer
2521
 from .rollback import RollbackPlan
22
+from .task_classification import estimate_complexity, get_token_budget
2623
 from .tool_batches import ToolBatchRunner
2724
 from .tracing import RuntimeTracer
2825
 from .workflow import (
src/loader/runtime/task_classification.pyadded
@@ -0,0 +1,193 @@
1
+"""Runtime-owned task classification helpers."""
2
+
3
+from __future__ import annotations
4
+
5
+
6
+def is_conversational(message: str) -> bool:
7
+    """Detect if a message is conversational rather than a task."""
8
+
9
+    msg = message.lower().strip()
10
+
11
+    if len(msg) < 15:
12
+        greetings = [
13
+            "hi",
14
+            "hello",
15
+            "hey",
16
+            "yo",
17
+            "sup",
18
+            "hiya",
19
+            "howdy",
20
+            "ello",
21
+            "hallo",
22
+            "greetings",
23
+            "good morning",
24
+            "good afternoon",
25
+            "good evening",
26
+            "morning",
27
+            "evening",
28
+            "afternoon",
29
+            "what's up",
30
+            "whats up",
31
+            "wassup",
32
+            "how are you",
33
+            "how's it going",
34
+            "hows it going",
35
+        ]
36
+        if any(msg.startswith(greeting) or msg == greeting for greeting in greetings):
37
+            return True
38
+
39
+    agent_questions = [
40
+        "who are you",
41
+        "what are you",
42
+        "what can you do",
43
+        "how do you work",
44
+        "what is loader",
45
+        "what's loader",
46
+        "help",
47
+        "what is this",
48
+        "how does this work",
49
+    ]
50
+    if any(question in msg for question in agent_questions):
51
+        return True
52
+
53
+    casual = [
54
+        "thanks",
55
+        "thank you",
56
+        "thx",
57
+        "ty",
58
+        "cool",
59
+        "nice",
60
+        "great",
61
+        "awesome",
62
+        "ok",
63
+        "okay",
64
+        "bye",
65
+        "goodbye",
66
+        "see you",
67
+        "later",
68
+        "cya",
69
+        "lol",
70
+        "haha",
71
+        "hehe",
72
+        "lmao",
73
+        "please",
74
+        "sorry",
75
+        "oops",
76
+    ]
77
+    if msg in casual or any(msg == item for item in casual):
78
+        return True
79
+
80
+    task_indicators = [
81
+        "create",
82
+        "make",
83
+        "build",
84
+        "write",
85
+        "edit",
86
+        "delete",
87
+        "remove",
88
+        "run",
89
+        "execute",
90
+        "install",
91
+        "fix",
92
+        "debug",
93
+        "test",
94
+        "check",
95
+        "find",
96
+        "search",
97
+        "show",
98
+        "list",
99
+        "read",
100
+        "open",
101
+        "close",
102
+        "add",
103
+        "update",
104
+        "change",
105
+        "modify",
106
+        "refactor",
107
+        "implement",
108
+        "file",
109
+        "folder",
110
+        "directory",
111
+        "code",
112
+        "function",
113
+        "class",
114
+        "git",
115
+        "npm",
116
+        "pip",
117
+        "python",
118
+        "node",
119
+        "bash",
120
+        "command",
121
+    ]
122
+    if any(indicator in msg for indicator in task_indicators):
123
+        return False
124
+
125
+    if len(msg) < 30 and not any(char in msg for char in [".", "/", "\\", "`"]):
126
+        return True
127
+
128
+    return False
129
+
130
+
131
+def estimate_complexity(message: str) -> str:
132
+    """Estimate query complexity for token budgeting."""
133
+
134
+    msg = message.lower()
135
+    word_count = len(message.split())
136
+
137
+    if is_conversational(message) or word_count < 5:
138
+        return "trivial"
139
+
140
+    complex_indicators = [
141
+        "project",
142
+        "application",
143
+        "website",
144
+        "api",
145
+        "database",
146
+        "refactor",
147
+        "migrate",
148
+        "upgrade",
149
+        "implement",
150
+        "design",
151
+        "multiple",
152
+        "several",
153
+        "all",
154
+        "entire",
155
+        "whole",
156
+        "and then",
157
+        "after that",
158
+        "also",
159
+        "as well",
160
+    ]
161
+    complex_count = sum(1 for indicator in complex_indicators if indicator in msg)
162
+
163
+    if complex_count >= 2 or word_count > 50:
164
+        return "complex"
165
+
166
+    simple_indicators = [
167
+        "what is",
168
+        "how do",
169
+        "show me",
170
+        "list",
171
+        "read",
172
+        "single",
173
+        "one",
174
+        "just",
175
+        "only",
176
+        "quick",
177
+    ]
178
+    if any(indicator in msg for indicator in simple_indicators) and word_count < 20:
179
+        return "simple"
180
+
181
+    return "moderate"
182
+
183
+
184
+def get_token_budget(complexity: str) -> tuple[int, int]:
185
+    """Get `(max_tokens, context_tokens)` for a complexity level."""
186
+
187
+    budgets = {
188
+        "trivial": (256, 2048),
189
+        "simple": (512, 4096),
190
+        "moderate": (1024, 8192),
191
+        "complex": (2048, 16384),
192
+    }
193
+    return budgets.get(complexity, (1024, 8192))
tests/test_task_classification.pyadded
@@ -0,0 +1,29 @@
1
+"""Tests for runtime-owned task classification helpers."""
2
+
3
+from __future__ import annotations
4
+
5
+from loader.runtime.task_classification import (
6
+    estimate_complexity,
7
+    get_token_budget,
8
+    is_conversational,
9
+)
10
+
11
+
12
+def test_is_conversational_detects_small_talk() -> None:
13
+    assert is_conversational("hi there") is True
14
+    assert is_conversational("thanks") is True
15
+    assert is_conversational("what can you do?") is True
16
+
17
+
18
+def test_is_conversational_rejects_actionable_tasks() -> None:
19
+    assert is_conversational("create a new README for this repo") is False
20
+    assert is_conversational("run the tests and fix failures") is False
21
+
22
+
23
+def test_estimate_complexity_and_token_budget_cover_common_paths() -> None:
24
+    assert estimate_complexity("hi") == "trivial"
25
+    assert estimate_complexity("show me the config file") == "simple"
26
+    assert estimate_complexity(
27
+        "implement a website and then refactor the database layer"
28
+    ) == "complex"
29
+    assert get_token_budget("simple") == (512, 4096)