Files
stack/apps/gateway/src/agent/routing/task-classifier.ts
Jason Woltje fa84bde6f6
Some checks failed
ci/woodpecker/push/ci Pipeline failed
feat(routing): task classifier + default rules + CI test fixes — M4-004/005 (#316)
Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
2026-03-23 00:26:49 +00:00

160 lines
4.6 KiB
TypeScript

import type { TaskType, Complexity, Domain, TaskClassification } from './routing.types.js';
// ─── Pattern Banks ──────────────────────────────────────────────────────────
const CODING_PATTERNS: RegExp[] = [
/\bcode\b/i,
/\bfunction\b/i,
/\bimplement\b/i,
/\bdebug\b/i,
/\bfix\b/i,
/\brefactor\b/i,
/\btypescript\b/i,
/\bjavascript\b/i,
/\bpython\b/i,
/\bSQL\b/i,
/\bAPI\b/i,
/\bendpoint\b/i,
/\bclass\b/i,
/\bmethod\b/i,
/`[^`]*`/,
];
const RESEARCH_PATTERNS: RegExp[] = [
/\bresearch\b/i,
/\bfind\b/i,
/\bsearch\b/i,
/\bwhat is\b/i,
/\bexplain\b/i,
/\bhow do(es)?\b/i,
/\bcompare\b/i,
/\banalyze\b/i,
];
const SUMMARIZATION_PATTERNS: RegExp[] = [
/\bsummariz(e|ation)\b/i,
/\bsummary\b/i,
/\btldr\b/i,
/\bcondense\b/i,
/\bbrief\b/i,
];
const CREATIVE_PATTERNS: RegExp[] = [
/\bwrite\b/i,
/\bstory\b/i,
/\bpoem\b/i,
/\bgenerate\b/i,
/\bcreate content\b/i,
/\bblog post\b/i,
];
const ANALYSIS_PATTERNS: RegExp[] = [
/\banalyze\b/i,
/\breview\b/i,
/\bevaluate\b/i,
/\bassess\b/i,
/\baudit\b/i,
];
// ─── Complexity Indicators ───────────────────────────────────────────────────
const COMPLEX_KEYWORDS: RegExp[] = [
/\barchitecture\b/i,
/\bdesign\b/i,
/\bcomplex\b/i,
/\bsystem\b/i,
];
const SIMPLE_QUESTION_PATTERN = /^[^.!?]+[?]$/;
/** Counts occurrences of triple-backtick code fences in the message */
function countCodeBlocks(message: string): number {
return (message.match(/```/g) ?? []).length / 2;
}
// ─── Domain Indicators ───────────────────────────────────────────────────────
const FRONTEND_PATTERNS: RegExp[] = [
/\breact\b/i,
/\bcss\b/i,
/\bhtml\b/i,
/\bcomponent\b/i,
/\bUI\b/,
/\btailwind\b/i,
/\bnext\.js\b/i,
];
const BACKEND_PATTERNS: RegExp[] = [
/\bAPI\b/i,
/\bserver\b/i,
/\bdatabase\b/i,
/\bendpoint\b/i,
/\bnest(js)?\b/i,
/\bexpress\b/i,
];
const DEVOPS_PATTERNS: RegExp[] = [
/\bdocker(file|compose|hub)?\b/i,
/\bCI\b/,
/\bdeploy\b/i,
/\bpipeline\b/i,
/\bkubernetes\b/i,
];
const DOCS_PATTERNS: RegExp[] = [/\bdocumentation\b/i, /\breadme\b/i, /\bguide\b/i];
// ─── Helpers ─────────────────────────────────────────────────────────────────
function matchesAny(message: string, patterns: RegExp[]): boolean {
return patterns.some((p) => p.test(message));
}
// ─── Classifier ──────────────────────────────────────────────────────────────
/**
* Classify a task based on the user's message using deterministic regex/keyword matching.
* No LLM calls are made — this is a pure, fast, synchronous classification.
*/
export function classifyTask(message: string): TaskClassification {
return {
taskType: detectTaskType(message),
complexity: estimateComplexity(message),
domain: detectDomain(message),
requiredCapabilities: [],
};
}
function detectTaskType(message: string): TaskType {
if (matchesAny(message, CODING_PATTERNS)) return 'coding';
if (matchesAny(message, SUMMARIZATION_PATTERNS)) return 'summarization';
if (matchesAny(message, CREATIVE_PATTERNS)) return 'creative';
if (matchesAny(message, ANALYSIS_PATTERNS)) return 'analysis';
if (matchesAny(message, RESEARCH_PATTERNS)) return 'research';
return 'conversation';
}
function estimateComplexity(message: string): Complexity {
const trimmed = message.trim();
const codeBlocks = countCodeBlocks(trimmed);
// Complex: long messages, multiple code blocks, or complexity keywords
if (trimmed.length > 500 || codeBlocks > 1 || matchesAny(trimmed, COMPLEX_KEYWORDS)) {
return 'complex';
}
// Simple: short messages or a single direct question
if (trimmed.length < 100 || SIMPLE_QUESTION_PATTERN.test(trimmed)) {
return 'simple';
}
return 'moderate';
}
function detectDomain(message: string): Domain {
if (matchesAny(message, DEVOPS_PATTERNS)) return 'devops';
if (matchesAny(message, DOCS_PATTERNS)) return 'docs';
if (matchesAny(message, FRONTEND_PATTERNS)) return 'frontend';
if (matchesAny(message, BACKEND_PATTERNS)) return 'backend';
return 'general';
}