Replicate ClawRouter v2 classifyByRules logic.
(prompt: str, system_prompt: str | None = None)
| 147 | |
| 148 | |
| 149 | def classify_clawrouter_v2(prompt: str, system_prompt: str | None = None) -> tuple[str, float]: |
| 150 | """Replicate ClawRouter v2 classifyByRules logic.""" |
| 151 | full_text = f"{system_prompt or ''} {prompt}" |
| 152 | estimated_tokens = max(1, len(full_text) // 4) # original: Math.ceil(length / 4) |
| 153 | user_text = prompt.lower() |
| 154 | |
| 155 | # Score dimensions |
| 156 | dims: dict[str, float] = {} |
| 157 | |
| 158 | tc_score, _ = _score_token_count(estimated_tokens) |
| 159 | dims["tokenCount"] = tc_score |
| 160 | dims["codePresence"] = _score_keywords(user_text, CODE_KEYWORDS, 1, 2, 0.5, 1.0) |
| 161 | dims["reasoningMarkers"] = _score_keywords(user_text, REASONING_KEYWORDS, 1, 2, 0.7, 1.0) |
| 162 | dims["technicalTerms"] = _score_keywords(user_text, TECHNICAL_KEYWORDS, 2, 4, 0.5, 1.0) |
| 163 | dims["creativeMarkers"] = _score_keywords(user_text, CREATIVE_KEYWORDS, 1, 2, 0.5, 0.7) |
| 164 | dims["simpleIndicators"] = _score_keywords(user_text, SIMPLE_KEYWORDS, 1, 2, -1.0, -1.0) |
| 165 | dims["multiStepPatterns"] = _score_multi_step(user_text) |
| 166 | dims["questionComplexity"] = _score_question_complexity(prompt) |
| 167 | dims["imperativeVerbs"] = _score_keywords(user_text, IMPERATIVE_KEYWORDS, 1, 2, 0.3, 0.5) |
| 168 | dims["constraintCount"] = _score_keywords(user_text, CONSTRAINT_KEYWORDS, 1, 3, 0.3, 0.7) |
| 169 | dims["outputFormat"] = _score_keywords(user_text, OUTPUT_FORMAT_KEYWORDS, 1, 2, 0.4, 0.7) |
| 170 | dims["referenceComplexity"] = _score_keywords(user_text, REFERENCE_KEYWORDS, 1, 2, 0.3, 0.5) |
| 171 | dims["negationComplexity"] = _score_keywords(user_text, NEGATION_KEYWORDS, 2, 3, 0.3, 0.5) |
| 172 | dims["domainSpecificity"] = _score_keywords(user_text, DOMAIN_KEYWORDS, 1, 2, 0.5, 0.8) |
| 173 | dims["agenticTask"] = _score_keywords(user_text, AGENTIC_KEYWORDS, 1, 2, 0.2, 0.5) |
| 174 | |
| 175 | # Reasoning override (from rules.ts) |
| 176 | reasoning_matches = sum(1 for kw in REASONING_KEYWORDS if kw.lower() in user_text) |
| 177 | if reasoning_matches >= 2: |
| 178 | return "REASONING", 0.85 |
| 179 | |
| 180 | # Weighted score |
| 181 | weighted_score = sum(dims[k] * DIMENSION_WEIGHTS.get(k, 0) for k in dims) |
| 182 | |
| 183 | # Tier mapping |
| 184 | sm = TIER_BOUNDARIES["simpleMedium"] |
| 185 | mc = TIER_BOUNDARIES["mediumComplex"] |
| 186 | cr = TIER_BOUNDARIES["complexReasoning"] |
| 187 | |
| 188 | if weighted_score < sm: |
| 189 | tier = "SIMPLE" |
| 190 | dist = sm - weighted_score |
| 191 | elif weighted_score < mc: |
| 192 | tier = "MEDIUM" |
| 193 | dist = min(weighted_score - sm, mc - weighted_score) |
| 194 | elif weighted_score < cr: |
| 195 | tier = "COMPLEX" |
| 196 | dist = min(weighted_score - mc, cr - weighted_score) |
| 197 | else: |
| 198 | tier = "REASONING" |
| 199 | dist = weighted_score - cr |
| 200 | |
| 201 | confidence = 1 / (1 + math.exp(-CONFIDENCE_STEEPNESS * dist)) |
| 202 | |
| 203 | if confidence < CONFIDENCE_THRESHOLD: |
| 204 | tier = "MEDIUM" # ambiguous default |
| 205 | |
| 206 | return tier, confidence |
nothing calls this directly
no test coverage detected