Semantic Routing Plugin — simple_semantic_routing#
Heuristic-based model selection plugin for the LLM‑Router system.
When payload["model"] == "auto", the plugin analyses the user's input and selects the most appropriate model from a
configured pool.
1. Overview#
1.1 What it does#
The plugin performs two-stage heuristic routing:
| Stage | Description |
|---|---|
| Intent Classification | Scans the text for keywords, multi-word phrases, and regex patterns to determine the user's intent (code, math, creative, general, or none). |
| Complexity Analysis | Estimates token count (word count × 1.25) and maps it to a complexity level (simple, medium, or complex). |
| Model Selection | Combines the intent target with the complexity level to pick the best model from the pool. |
1.2 Intent Categories#
| Intent | Description | Examples |
|---|---|---|
code |
Programming, debugging, implementation | "napisz funkcję", "fix bug", "debug code", "git commit" |
math |
Mathematics, calculations, statistics | "oblicz prawdopodobieństwo", "solve equation", "calculate mean" |
creative |
Creative writing, editing, brainstorming | "napisz opowiadanie", "write a story", "brainstorm" |
general |
General questions, explanations, comparisons | "wyjaśnij jak", "what is the difference", "compare" |
none |
No specific intent (greetings, etc.) | "cześć", "hello", "dziękuję" |
2. Architecture#
2.1 Data Flow#
User Input
│
▼
classify_intent(text) ← keywords + phrases + regex patterns
│
▼
estimate_tokens(text) ← word_count × 1.25
│
▼
complexity_level(tokens) ← simple / medium / complex
│
▼
select_model(intent, complexity) ← weighted score → model index
│
▼
Updated payload["model"]
2.2 Intent Classification Algorithm#
Each intent is defined in simple.json with four complementary signal types:
a) Keywords — single words#
Each keyword has an optional weight. If no weight is specified, the default is 1.0.
"keywords": ["code", "debug", "funkcja"],
"weights": {"debug": 5, "kod": 4, "code": 2}
Matching "debug" adds 5 to the score; matching "code" adds 2.
b) Phrases — multi-word expressions#
Phrases use the format "text:weight". The default weight is 2.0 when omitted.
"phrases": ["write code:5", "fix bug:4", "napisz funkcję:4"]
Matching "write code" adds 5 to the score.
c) Regex Patterns — structural detection#
Patterns add a flat +3.0 per match, useful for detecting code structures, math formulas, and question patterns.
"patterns": [
"def\\s+\\w+\\s*\\(",
"class\\s+\\w+\\s*[(:]",
"\\b\\d+\\s*[+\\-*/^]\\s*\\d+\\b",
"\\bwhat\\s+(is|are|does)\\b"
]
d) Weights — keyword importance#
The weights object maps individual keywords to boost factors, allowing fine-grained control:
"weights": {
"debug": 5, // Very strong signal for code intent
"błąd": 5, // Very strong signal for code intent
"kod": 2, // Moderate signal
"python": 2 // Moderate signal
}
2.3 Model Selection Algorithm#
1. complexity_map = {"simple": 0, "medium": n//2, "complex": n-1}
2. idx = complexity_map[complexity]
3. if intent has a target in intent_adjustment:
intent_idx = model_index_for(intent.target)
idx = max(idx, intent_idx) // boost toward intent's target
else:
idx -= 1 // demote (no intent)
4. idx = clamp(idx, 0, n-1)
5. return models[idx]
Boost / Demote Examples:
| Intent | Complexity | Base Index | Intent Adjustment | Result |
|---|---|---|---|---|
code → medium |
simple (idx=0) | 0 | intent targets index 1 | max(0, 1) = 1 |
none |
complex (idx=2) | 2 | no target → demote | 2-1 = 1 |
math → medium |
complex (idx=3) | 3 | intent targets index 1 | max(3, 1) = 3 |
creative → simple |
medium (idx=1) | 1 | intent targets index 0 | max(1, 0) = 1 |
3. Configuration#
3.1 JSON Config (simple.json)#
All configuration lives in llm_router_plugins/resources/routing/semantic/simple.json.
{
"settings": {
"len_thresholds_max": {
"simple": 25,
"medium": 150
},
"default_models": {
"simple": "gpt-oss:120b",
"medium": "qwen3.6:35b"
},
"intent_adjustment": {
"code": "medium",
"math": "medium",
"creative": "simple",
"general": "simple",
"none": ""
}
},
"intents": {
"code": {
"keywords": [
...
],
"phrases": [
"write code:5",
"fix bug:4"
],
"patterns": [
"def\\s+\\w+\\s*\\(",
"class\\s+\\w+"
],
"weights": {
"debug": 5,
"błąd": 4
}
},
"math": {
"keywords": [
...
],
"phrases": [
"calculate:4",
"solve equation:4"
],
"patterns": [
"\\b\\d+\\s*[+\\-*/^]\\s*\\d+\\b"
],
"weights": {
"calculate": 4,
"solve": 4
}
},
"creative": {
"keywords": [
...
],
"phrases": [
"write a story:4",
"napisz wiersz:4"
],
"patterns": [
"napisz\\s+(mi|ci|go|ją)"
],
"weights": {
"napisz": 4,
"write": 3
}
},
"general": {
"keywords": [
...
],
"phrases": [
"what is:4",
"how to:4"
],
"patterns": [
"\\bwhat\\s+(is|are|does)\\b"
],
"weights": {
"wyjaśnij": 3,
"help": 2
}
}
},
"none": {
"keywords": [
"hello",
"cześć",
"thanks"
],
"phrases": [
"hello:1",
"thanks:1"
],
"patterns": [
"^\\b(hello|hi|cześć)\\b"
],
"weights": {}
}
}
3.2 Configurable Values#
| Setting | Description | Default |
|---|---|---|
len_thresholds_max.simple |
Max tokens for "simple" complexity | 25 |
len_thresholds_max.medium |
Max tokens for "medium" complexity | 150 |
default_models.simple |
Model for simple complexity | gpt-oss:120b |
default_models.medium |
Model for medium complexity | qwen3.6:35b |
intent_adjustment.code |
Target complexity for code intent | medium |
intent_adjustment.math |
Target complexity for math intent | medium |
intent_adjustment.creative |
Target complexity for creative intent | simple |
intent_adjustment.general |
Target complexity for general intent | simple |
3.3 Environment Variable Overrides#
All environment variables are prefixed with LLM_ROUTER_ROUTING_.
| Variable | Format | Description |
|---|---|---|
LLM_ROUTER_ROUTING_MODELS |
model-a\|model-b\|model-c |
Comma-separated model pool (pipe-delimited) |
LLM_ROUTER_ROUTING_COMPLEXITY_THRESHOLDS |
simple_threshold\|medium_threshold |
Token count thresholds (e.g. 10\|50) |
LLM_ROUTER_ROUTING_DEFAULT_MODEL |
model-name |
Fallback model when no text content is found |
LLM_ROUTER_ROUTING_INTENT_<CATEGORY> |
kw1\|kw2:5\|phrase:3 |
Override intent keywords/phrases for a category |
Example:
export LLM_ROUTER_ROUTING_MODELS="tiny-model\|medium-model\|large-model"
export LLM_ROUTER_ROUTING_COMPLEXITY_THRESHOLDS="10\|50"
export LLM_ROUTER_ROUTING_INTENT_CODE="code\|debug\|implement"
export LLM_ROUTER_ROUTING_DEFAULT_MODEL="fallback-model"
4. Usage#
4.1 Basic Usage#
from llm_router_plugins.utils.routing.semantic.simple import DefaultSemanticRoutingPlugin
plugin = DefaultSemanticRoutingPlugin()
payload = {
"model": "auto",
"messages": [{"role": "user", "content": "Napisz funkcję w Pythonie"}]
}
result = plugin.apply(payload)
# result["model"] → selected model (e.g., "qwen3.6:35b")
4.2 Payload Text Sources#
The plugin extracts text from the payload in this priority order:
payload["messages"][-1]["content"]— last message contentpayload["user_last_statement"]payload["query"]payload["prompt"]payload["input"]
4.3 Logging#
When a logger is provided, the plugin logs routing decisions:
import logging
logger = logging.getLogger("llm_router")
plugin = DefaultSemanticRoutingPlugin(logger=logger)
# On apply:
# INFO: Semantic routing: intent=code, complexity=medium (12 tokens) -> qwen3.6:35b
5. Adding New Intents#
To add a new intent category:
- Add the intent to
simple.json:
{
"intents": {
"my_intent": {
"keywords": [
"keyword1",
"keyword2"
],
"phrases": [
"phrase one:4",
"phrase two:3"
],
"patterns": [
"pattern\\s+here"
],
"weights": {
"keyword1": 5,
"keyword2": 2
}
}
},
"settings": {
"intent_adjustment": {
"my_intent": "medium"
}
}
}
- Or override via environment variable:
export LLM_ROUTER_ROUTING_INTENT_MY_INTENT="keyword1\|keyword2:5\|phrase:4"
5.1 Weight Tuning Guidelines#
| Weight Range | Meaning | Use Case |
|---|---|---|
| 1 | Weak / baseline | Generic terms that appear in many contexts |
| 2–3 | Moderate | Common but not definitive signals |
| 4–5 | Strong | Highly specific, unambiguous signals (e.g., "debug", "błąd" for code) |
| 1 (none) | Neutral | Greetings and filler phrases that shouldn't influence routing |
5.2 Phrase Format Guidelines#
| Format | Weight | Example |
|---|---|---|
"text:weight" |
Explicit | "write code:5" |
"text" |
Default (2.0) | "fix bug" |
6. Complexity Levels#
| Level | Token Range | Model Selection |
|---|---|---|
simple |
0 – thresholds[simple] |
Cheapest / simplest model (index 0) |
medium |
thresholds[simple] + 1 – thresholds[medium] |
Middle model (index n // 2) |
complex |
> thresholds[medium] |
Strongest model (index n - 1) |
Token estimation: round(word_count × 1.25)
7. Built-in Patterns Reference#
7.1 Code Patterns#
| Pattern | Matches |
|---|---|
def\s+\w+\s*\( |
Python function definitions |
class\s+\w+\s*[(:] |
Python/class definitions |
import\s+\w+ |
Import statements |
try\s*:, except\s*: |
Try/except blocks |
git\s+(commit\|push\|pull\|branch\|merge) |
Git commands |
npm\s+(install\|run\|start\|build) |
NPM commands |
docker\s+(build\|run\|compose) |
Docker commands |
SELECT\s+.*\s+FROM |
SQL SELECT queries |
7.2 Math Patterns#
| Pattern | Matches |
|---|---|
\b\d+\s*[+\-*/^]\s*\d+\b |
Arithmetic expressions |
\bsin\s*\(, \bcos\s*\(, \btan\s*\( |
Trig functions |
\bsqrt\s*\(, \blog\s*\( |
Math functions |
\bf\(x\)\s*= |
Function notation |
\b∑\b, \b∫\b |
Sigma / integral symbols |
7.3 General Question Patterns#
| Pattern | Matches |
|---|---|
\bwhat\s+(is\|are\|does)\b |
What questions |
\bhow\s+(to\|does\|do)\b |
How questions |
\bwhy\s+(is\|does)\b |
Why questions |
\bporównaj\s+ |
Polish comparison questions |
8. Running Tests#
pytest tests/test_default_semantic_routing.py -v
9. File Locations#
| File | Purpose |
|---|---|
llm_router_plugins/utils/routing/semantic/simple.py |
Plugin implementation |
llm_router_plugins/resources/routing/semantic/simple.json |
Intent definitions & config |
tests/test_default_semantic_routing.py |
Unit tests |