Building a Multi-Model AI Chatbot with Python ??Route by Task Complexity
All pricing from AIWave, July 2026. Code is production-ready.
Most AI chatbots use a single model for everything. That's simple to build but expensive to run. A more intelligent approach: route different tasks to different models based on complexity, and let your routing logic handle the cost optimization automatically.
Here's a complete, runnable Python chatbot that uses ERNIE Tiny (budget-tier available, $0.178/1M tokens) for simple queries and DeepSeek V4 Pro ($0.42/$0.84 per 1M tokens, 1M context) for complex tasks.
Why Multi-Model Routing Matters
The cost difference between models isn't marginal ??it's orders of magnitude:
| Model | Input (1M tokens) | Output (1M tokens) | Context | Use Case |
|---|---|---|---|---|
| ERNIE Tiny 8K | $0.178 | $0.178 | 8K | Simple Q&A, greetings |
| DeepSeek V4 Pro | $0.42 | $0.84 | 1M | Complex reasoning, long docs |
A greeting ("Hi, how are you?") costs ~$0.000024 with ERNIE Tiny. Sending it to DeepSeek V4 Pro costs ~$0.000058. That's 2.4? more expensive for zero quality gain. Multiply by millions of daily requests, and routing becomes a real lever.
The Complete Project
multi_model_bot/
????? bot.py # Main entry point
????? router.py # Task complexity classifier
????? models.py # Model configurations
????? config.py # API key and settings
????? requirements.txt
config.py
API_KEY = "your-aiwave-api-key"
BASE_URL = "https://aiwave.live/v1"
MODELS = {
"simple": "ernie-tiny-8k",
"complex": "deepseek-v4-pro",
}
# Pricing data (USD per 1M tokens)
PRICING = {
"ernie-tiny-8k": {"input": 0.178, "output": 0.178},
"deepseek-v4-pro": {"input": 0.42, "output": 0.84},
}
router.py ??The Intelligence Layer
import re
from typing import Literal
RouteDecision = Literal["simple", "complex"]
# Patterns that indicate complex reasoning is needed
COMPLEX_PATTERNS = [
r"\b(analyze|analysis|compare|evaluate|explain why)\b",
r"\b(algorithm|architecture|design|refactor|optimize)\b",
r"\b(debug|troubleshoot|fix|error|exception)\b",
r"\b(write|implement|create|build|develop)\b",
r"\b(summarize|extract|translate).{0,20}(long|document|paper)\b",
r"\b(what if|how would|assuming|given that)\b",
]
# Patterns that are clearly simple
SIMPLE_PATTERNS = [
r"^(hi|hello|hey|thanks|bye|ok)\b",
r"^(what is|who is|where is|when is|define)\b",
r"\b(how do i|how to)\b",
]
def classify_complexity(message: str, message_length: int = 0) -> RouteDecision:
"""
Classify whether a message needs complex reasoning.
Uses heuristic pattern matching + length-based thresholds.
In production, replace with a small classifier model
(e.g., glm-4.7-flash, which is extremely affordable at $0.03/1M).
"""
msg_lower = message.lower().strip()
# Length heuristic: very short messages are rarely complex
if len(msg_lower) < 30:
# But check if it's asking something non-trivial
if any(re.search(p, msg_lower) for p in COMPLEX_PATTERNS):
return "complex"
return "simple"
# Pattern matching for complex tasks
complex_score = sum(
1 for p in COMPLEX_PATTERNS if re.search(p, msg_lower)
)
simple_score = sum(
1 for p in SIMPLE_PATTERNS if re.search(p, msg_lower)
)
if complex_score >= 1:
return "complex"
if simple_score >= 1 and complex_score == 0:
return "simple"
# Default: medium-length messages without clear signals
return "simple" if len(msg_lower) < 200 else "complex"
models.py ??Model Interaction
import openai
from config import API_KEY, BASE_URL, MODELS, PRICING
client = openai.OpenAI(api_key=API_KEY, base_url=BASE_URL)
# Conversation history per model to maintain context
conversation_history: dict[str, list[dict]] = {
"ernie-tiny-8k": [],
"deepseek-v4-pro": [],
}
def call_model(model_key: str, message: str, system_prompt: str = "") -> dict:
"""
Call the specified model and return response + cost info.
"""
model_name = MODELS[model_key]
history = conversation_history[model_name]
if system_prompt and not history:
history.append({"role": "system", "content": system_prompt})
history.append({"role": "user", "content": message})
response = client.chat.completions.create(
model=model_name,
messages=history,
temperature=0.3 if model_key == "complex" else 0.7,
max_tokens=2048 if model_key == "complex" else 512,
)
assistant_msg = response.choices[0].message.content
history.append({"role": "assistant", "content": assistant_msg})
# Trim history to prevent context overflow
if len(history) > 20:
history[:] = history[-16:] # Keep system + last 8 exchanges
# Calculate real cost
pricing = PRICING[model_name]
cost = (
response.usage.prompt_tokens * pricing["input"] / 1_000_000
+ response.usage.completion_tokens * pricing["output"] / 1_000_000
)
return {
"text": assistant_msg,
"model": model_name,
"route": model_key,
"input_tokens": response.usage.prompt_tokens,
"output_tokens": response.usage.completion_tokens,
"cost_usd": cost,
}
bot.py ??Putting It Together
from router import classify_complexity
from models import call_model
import json
SYSTEM_PROMPTS = {
"simple": "You are a helpful assistant. Keep responses concise and friendly.",
"complex": (
"You are a senior software engineer and technical analyst. "
"Provide thorough, well-reasoned responses with code examples "
"where appropriate. Think step-by-step."
),
}
class MultiModelChat:
def __init__(self):
self.total_cost = 0.0
self.request_count = 0
self.route_counts = {"simple": 0, "complex": 0}
def chat(self, message: str) -> dict:
self.request_count += 1
route = classify_complexity(message)
self.route_counts[route] += 1
result = call_model(
model_key=route,
message=message,
system_prompt=SYSTEM_PROMPTS[route],
)
self.total_cost += result["cost_usd"]
result["running_total_usd"] = self.total_cost
result["route_distribution"] = dict(self.route_counts)
return result
def print_response(self, result: dict):
route_label = "?? SIMPLE" if result["route"] == "simple" else "?? COMPLEX"
print(f"\n{'='*60}")
print(f"Route: {route_label} ??{result['model']}")
print(f"Tokens: {result['input_tokens']} in / {result['output_tokens']} out")
print(f"Cost: ${result['cost_usd']:.6f}")
print(f"Running total: ${result['running_total']:.6f} ({self.request_count} requests)")
print(f"{'='*60}")
print(result["text"])
if __name__ == "__main__":
bot = MultiModelChat()
# Simulate a realistic conversation
test_messages = [
"Hi, what can you help me with?", # Simple
"What is a REST API?", # Simple
"Compare the performance characteristics of ERNIE Tiny vs DeepSeek V4 Pro "
"for a production chatbot handling 10K daily requests, considering latency, "
"accuracy, and cost. Provide a recommendation.", # Complex
"Thanks, that's helpful!", # Simple
"Write a Python function that implements a concurrent "
"task queue with priority scheduling, rate limiting, "
"and retry logic with exponential backoff.", # Complex
]
for msg in test_messages:
result = bot.chat(msg)
bot.print_response(result)
print(f"\n{'='*60}")
print(f"SUMMARY: {bot.request_count} requests, ${bot.total_cost:.6f} total")
print(f"Routes: {bot.route_counts}")
Understanding the Cost Flow
Let's trace a realistic session:
| Message | Route | Model | Est. Input | Est. Output | Est. Cost |
|---|---|---|---|---|---|
| "Hi, what can you help me with?" | Simple | ERNIE Tiny | 20 tokens | 50 tokens | $0.000003 |
| "What is a REST API?" | Simple | ERNIE Tiny | 35 tokens | 200 tokens | $0.000012 |
| Complex comparison question | Complex | DeepSeek V4 Pro | 80 tokens | 800 tokens | $0.000137 |
| "Thanks!" | Simple | ERNIE Tiny | 60 tokens | 20 tokens | $0.000004 |
| Concurrent task queue | Complex | DeepSeek V4 Pro | 70 tokens | 1200 tokens | $0.000182 |
Session total: ~$0.000338 for 5 real queries, mixing simple and complex.
If every query went to DeepSeek V4 Pro, the same session would cost ~$0.000355. Not a huge difference for 5 queries ??but at 10,000 daily requests where ~60% are simple, you're looking at $6/day vs $10/day. That's $120/month saved by routing intelligently.
Production Considerations
1. Replace the heuristic router with a classifier. The pattern-matching router works but a small model like GLM-4.7-Flash (budget tier on AIWave) can classify complexity with much higher accuracy:
def ml_classify(message: str) -> str:
response = client.chat.completions.create(
model="glm-4.7-flash", # Budget tier
messages=[
{"role": "system", "content": (
"Classify the user message. Reply ONLY 'simple' or 'complex'. "
"Simple: greetings, definitions, short factual questions. "
"Complex: code generation, analysis, comparisons, debugging."
)},
{"role": "user", "content": message}
],
max_tokens=10,
temperature=0.0
)
return response.choices[0].message.content.strip().lower()
2. Add caching. Identical or near-identical queries shouldn't hit the API twice. Use a simple hash-based cache with TTL.
3. Monitor your routing distribution. If 90% of queries route to "complex," your complexity threshold is too low and you're spending unnecessarily.
Get Started
All models in this article are available on AIWave with an OpenAI-compatible API. New accounts get $5 free credit ??enough to process thousands of queries and validate your routing strategy before committing.
Check the full model catalog at aiwave.live/models and the pricing page for detailed cost breakdowns.
Building production AI systems? Share your architecture in the AIWave Discord.
Top comments (0)