| 312 | # ─── Main Entry ─── |
| 313 | |
| 314 | def classify( |
| 315 | prompt: str, |
| 316 | system_prompt: str | None = None, |
| 317 | config: ScoringConfig | None = None, |
| 318 | context_features: dict[str, float] | None = None, |
| 319 | ) -> ScoringResult: |
| 320 | if config is None: |
| 321 | config = ScoringConfig() |
| 322 | |
| 323 | feature_text = _merge_feature_text(prompt, system_prompt) |
| 324 | estimated_tokens = estimate_tokens(feature_text) |
| 325 | _ensure_model_loaded() |
| 326 | |
| 327 | trivial = _check_trivial(feature_text, estimated_tokens) |
| 328 | if trivial is not None: |
| 329 | trivial_complexity = 0.0 if trivial is Tier.SIMPLE else 0.90 |
| 330 | return ScoringResult( |
| 331 | tier=trivial, confidence=0.95, |
| 332 | signals=(f"trivial:{trivial.value}",), |
| 333 | complexity=trivial_complexity, |
| 334 | ) |
| 335 | |
| 336 | all_features = _extract_all_features( |
| 337 | prompt, |
| 338 | system_prompt=system_prompt, |
| 339 | context_features=context_features, |
| 340 | ) |
| 341 | |
| 342 | if _model is not None: |
| 343 | complexity, tier_str, confidence = _model.predict_complexity(all_features) |
| 344 | normalized_tier = "COMPLEX" if tier_str == "REASONING" else tier_str |
| 345 | tier = Tier(normalized_tier) |
| 346 | tier = _soften_short_code_question(prompt, estimated_tokens, tier) |
| 347 | tier = _soften_low_structure_question(prompt, estimated_tokens, tier, all_features) |
| 348 | normalized_tier = tier.value |
| 349 | if tier is Tier.MEDIUM and complexity > 0.40: |
| 350 | complexity = 0.40 |
| 351 | signals = (f"model:{normalized_tier}({confidence:.2f})", f"complexity:{complexity:.2f}") |
| 352 | return ScoringResult( |
| 353 | tier=tier, confidence=confidence, |
| 354 | signals=signals, complexity=complexity, |
| 355 | ) |
| 356 | |
| 357 | tier, confidence = _rule_based_classify(all_features, config) |
| 358 | _TIER_TO_COMPLEXITY = {Tier.SIMPLE: 0.0, Tier.MEDIUM: 0.40, Tier.COMPLEX: 0.90} |
| 359 | complexity = _TIER_TO_COMPLEXITY.get(tier, 0.33) |
| 360 | struct_dims = extract_structural_features(prompt) |
| 361 | signals = [d.signal for d in struct_dims if d.signal is not None] |
| 362 | signals.append("rule-fallback") |
| 363 | signals.append(f"complexity:{complexity:.2f}") |
| 364 | |
| 365 | if confidence < config.confidence_threshold: |
| 366 | return ScoringResult( |
| 367 | tier=None, confidence=confidence, |
| 368 | signals=tuple(signals), dimensions=tuple(struct_dims), |
| 369 | complexity=complexity, |
| 370 | ) |
| 371 | |