mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
fix(complexity_router): Address Greptile review round 2
1. **Empty user message handling** - Changed from falsy check to None check
to properly distinguish 'no user message' from 'empty string message'
2. **ReDoS prevention** - Changed 'first.*then' to 'first.*?then' (non-greedy)
to prevent regex backtracking on pathological inputs
3. **Documentation sync** - Updated README.md to match actual config values:
- Tier boundaries: 0.15/0.35/0.60 (not 0.25/0.50/0.75)
- Dimension weights: tokenCount=0.10, codePresence=0.30, technicalTerms=0.25,
simpleIndicators=0.05, multiStepPatterns=0.03, questionComplexity=0.02
4. **Missing UI component** - Added ComplexityRouterConfig.tsx with:
- Tier-to-model dropdown selectors
- Descriptions and examples for each tier
- How classification works explanation
5. **Inline import comment** - Added explanation for why ComplexityRouter
import is inline (matches AutoRouter pattern, avoids circular imports)
This commit is contained in:
parent
c73b8ec385
commit
93688aea8d
4 changed files with 163 additions and 24 deletions
|
|
@ -6301,6 +6301,8 @@ class Router:
|
|||
|
||||
This will initialize the complexity-router and add it to the complexity-routers dictionary.
|
||||
"""
|
||||
# Import here to match AutoRouter pattern and avoid circular imports
|
||||
# (ComplexityRouter is a CustomLogger subclass that imports litellm internals)
|
||||
from litellm.router_strategy.complexity_router.complexity_router import ComplexityRouter
|
||||
|
||||
complexity_router_config: Optional[
|
||||
|
|
|
|||
|
|
@ -17,22 +17,22 @@ The router scores each request across 7 dimensions:
|
|||
|
||||
| Dimension | Description | Weight |
|
||||
|-----------|-------------|--------|
|
||||
| `tokenCount` | Short prompts = simple, long = complex | 0.15 |
|
||||
| `codePresence` | Code keywords (function, class, etc.) | 0.20 |
|
||||
| `tokenCount` | Short prompts = simple, long = complex | 0.10 |
|
||||
| `codePresence` | Code keywords (function, class, etc.) | 0.30 |
|
||||
| `reasoningMarkers` | "step by step", "think through", etc. | 0.25 |
|
||||
| `technicalTerms` | Domain complexity indicators | 0.15 |
|
||||
| `simpleIndicators` | "what is", "define" (negative weight) | 0.15 |
|
||||
| `multiStepPatterns` | "first...then", numbered steps | 0.05 |
|
||||
| `questionComplexity` | Multiple question marks | 0.05 |
|
||||
| `technicalTerms` | Domain complexity indicators | 0.25 |
|
||||
| `simpleIndicators` | "what is", "define" (negative weight) | 0.05 |
|
||||
| `multiStepPatterns` | "first...then", numbered steps | 0.03 |
|
||||
| `questionComplexity` | Multiple question marks | 0.02 |
|
||||
|
||||
The weighted sum is mapped to tiers using configurable boundaries:
|
||||
|
||||
| Tier | Score Range | Typical Use |
|
||||
|------|-------------|-------------|
|
||||
| SIMPLE | < 0.25 | Basic questions, greetings |
|
||||
| MEDIUM | 0.25 - 0.50 | Standard queries |
|
||||
| COMPLEX | 0.50 - 0.75 | Technical, multi-part requests |
|
||||
| REASONING | > 0.75 | Chain-of-thought, analysis |
|
||||
| SIMPLE | < 0.15 | Basic questions, greetings |
|
||||
| MEDIUM | 0.15 - 0.35 | Standard queries |
|
||||
| COMPLEX | 0.35 - 0.60 | Technical, multi-part requests |
|
||||
| REASONING | > 0.60 | Chain-of-thought, analysis |
|
||||
|
||||
## Configuration
|
||||
|
||||
|
|
@ -68,9 +68,9 @@ model_list:
|
|||
|
||||
# Tier boundaries (normalized scores)
|
||||
tier_boundaries:
|
||||
simple_medium: 0.25
|
||||
medium_complex: 0.50
|
||||
complex_reasoning: 0.75
|
||||
simple_medium: 0.15
|
||||
medium_complex: 0.35
|
||||
complex_reasoning: 0.60
|
||||
|
||||
# Token count thresholds
|
||||
token_thresholds:
|
||||
|
|
@ -79,13 +79,13 @@ model_list:
|
|||
|
||||
# Dimension weights (must sum to ~1.0)
|
||||
dimension_weights:
|
||||
tokenCount: 0.15
|
||||
codePresence: 0.20
|
||||
tokenCount: 0.10
|
||||
codePresence: 0.30
|
||||
reasoningMarkers: 0.25
|
||||
technicalTerms: 0.15
|
||||
simpleIndicators: 0.15
|
||||
multiStepPatterns: 0.05
|
||||
questionComplexity: 0.05
|
||||
technicalTerms: 0.25
|
||||
simpleIndicators: 0.05
|
||||
multiStepPatterns: 0.03
|
||||
questionComplexity: 0.02
|
||||
|
||||
# Override default keyword lists
|
||||
code_keywords:
|
||||
|
|
|
|||
|
|
@ -93,8 +93,9 @@ class ComplexityRouter(CustomLogger):
|
|||
self.simple_keywords = self.config.simple_keywords or DEFAULT_SIMPLE_KEYWORDS
|
||||
|
||||
# Pre-compile regex patterns for efficiency
|
||||
# Use non-greedy .*? to prevent ReDoS on pathological inputs
|
||||
self._multi_step_patterns = [
|
||||
re.compile(r"first.*then", re.IGNORECASE),
|
||||
re.compile(r"first.*?then", re.IGNORECASE),
|
||||
re.compile(r"step\s*\d", re.IGNORECASE),
|
||||
re.compile(r"\d+\.\s"),
|
||||
re.compile(r"[a-z]\)\s", re.IGNORECASE),
|
||||
|
|
@ -359,19 +360,19 @@ class ComplexityRouter(CustomLogger):
|
|||
return None
|
||||
|
||||
# Extract the last user message and the last system prompt
|
||||
user_message = ""
|
||||
system_prompt = None
|
||||
user_message: Optional[str] = None
|
||||
system_prompt: Optional[str] = None
|
||||
|
||||
for msg in reversed(messages):
|
||||
role = msg.get("role", "")
|
||||
content = msg.get("content", "")
|
||||
if isinstance(content, str):
|
||||
if role == "user" and not user_message:
|
||||
if role == "user" and user_message is None:
|
||||
user_message = content
|
||||
elif role == "system" and system_prompt is None:
|
||||
system_prompt = content
|
||||
|
||||
if not user_message:
|
||||
if user_message is None:
|
||||
verbose_router_logger.debug(
|
||||
"ComplexityRouter: No user message found, skipping routing"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { Select as AntdSelect, Card, Divider, Space, Tooltip, Typography } from "antd";
|
||||
import React from "react";
|
||||
import { ModelGroup } from "../playground/llm_calls/fetch_models";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface ComplexityTiers {
|
||||
SIMPLE: string;
|
||||
MEDIUM: string;
|
||||
COMPLEX: string;
|
||||
REASONING: string;
|
||||
}
|
||||
|
||||
interface ComplexityRouterConfigProps {
|
||||
modelInfo: ModelGroup[];
|
||||
value: ComplexityTiers;
|
||||
onChange: (tiers: ComplexityTiers) => void;
|
||||
}
|
||||
|
||||
const TIER_DESCRIPTIONS: Record<keyof ComplexityTiers, { label: string; description: string; examples: string }> = {
|
||||
SIMPLE: {
|
||||
label: "Simple",
|
||||
description: "Basic questions, greetings, simple factual queries",
|
||||
examples: '"Hello!", "What is Python?", "Thanks!"',
|
||||
},
|
||||
MEDIUM: {
|
||||
label: "Medium",
|
||||
description: "Standard queries requiring some reasoning or explanation",
|
||||
examples: '"Explain how REST APIs work", "Debug this error"',
|
||||
},
|
||||
COMPLEX: {
|
||||
label: "Complex",
|
||||
description: "Technical, multi-part requests requiring deep knowledge",
|
||||
examples: '"Design a microservices architecture", "Implement a rate limiter"',
|
||||
},
|
||||
REASONING: {
|
||||
label: "Reasoning",
|
||||
description: "Chain-of-thought, analysis, explicit reasoning requests",
|
||||
examples: '"Think step by step...", "Analyze the pros and cons..."',
|
||||
},
|
||||
};
|
||||
|
||||
const ComplexityRouterConfig: React.FC<ComplexityRouterConfigProps> = ({ modelInfo, value, onChange }) => {
|
||||
// Prepare model options for dropdowns
|
||||
const modelOptions = modelInfo.map((model) => ({
|
||||
value: model.model_group,
|
||||
label: model.model_group,
|
||||
}));
|
||||
|
||||
const handleTierChange = (tier: keyof ComplexityTiers, model: string) => {
|
||||
onChange({
|
||||
...value,
|
||||
[tier]: model,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-none">
|
||||
<Space align="center" style={{ marginBottom: 16 }}>
|
||||
<Typography.Title level={4} style={{ margin: 0 }}>
|
||||
Complexity Tier Configuration
|
||||
</Typography.Title>
|
||||
<Tooltip title="Map each complexity tier to a model. Simple queries use cheaper/faster models, complex queries use more capable models.">
|
||||
<InfoCircleOutlined className="text-gray-400" />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
|
||||
<Text type="secondary" style={{ display: "block", marginBottom: 24 }}>
|
||||
The complexity router automatically classifies requests by complexity using rule-based scoring (no API calls,
|
||||
<1ms latency). Configure which model handles each tier.
|
||||
</Text>
|
||||
|
||||
<Card>
|
||||
{(Object.keys(TIER_DESCRIPTIONS) as Array<keyof ComplexityTiers>).map((tier, index) => {
|
||||
const tierInfo = TIER_DESCRIPTIONS[tier];
|
||||
return (
|
||||
<div key={tier}>
|
||||
{index > 0 && <Divider style={{ margin: "16px 0" }} />}
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Text strong style={{ fontSize: 16 }}>
|
||||
{tierInfo.label} Tier
|
||||
</Text>
|
||||
<Tooltip title={tierInfo.description}>
|
||||
<InfoCircleOutlined className="text-gray-400" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Text type="secondary" style={{ display: "block", marginBottom: 8, fontSize: 12 }}>
|
||||
Examples: {tierInfo.examples}
|
||||
</Text>
|
||||
<AntdSelect
|
||||
value={value[tier]}
|
||||
onChange={(model) => handleTierChange(tier, model)}
|
||||
placeholder={`Select model for ${tierInfo.label.toLowerCase()} queries`}
|
||||
showSearch
|
||||
style={{ width: "100%" }}
|
||||
options={modelOptions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Card>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Card className="bg-gray-50">
|
||||
<Text strong style={{ display: "block", marginBottom: 8 }}>
|
||||
How Classification Works
|
||||
</Text>
|
||||
<Text type="secondary" style={{ fontSize: 13 }}>
|
||||
The router scores each request across 7 dimensions: token count, code presence, reasoning markers, technical
|
||||
terms, simple indicators, multi-step patterns, and question complexity. The weighted score determines the
|
||||
tier:
|
||||
</Text>
|
||||
<ul style={{ marginTop: 8, marginBottom: 0, paddingLeft: 20, fontSize: 13, color: "rgba(0, 0, 0, 0.45)" }}>
|
||||
<li>
|
||||
<strong>SIMPLE</strong>: Score < 0.15
|
||||
</li>
|
||||
<li>
|
||||
<strong>MEDIUM</strong>: Score 0.15 - 0.35
|
||||
</li>
|
||||
<li>
|
||||
<strong>COMPLEX</strong>: Score 0.35 - 0.60
|
||||
</li>
|
||||
<li>
|
||||
<strong>REASONING</strong>: Score > 0.60 (or 2+ reasoning markers)
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComplexityRouterConfig;
|
||||
Loading…
Add table
Reference in a new issue