Building a Multi-Model AI Chatbot with Python

Jul 18 · Tutorial
AIWave Homepage

# Building a Multi-Model AI Chatbot with Python — Route by Task Complexity

All pricing from [AIWave](https://aiwave.live/?utm_source=dev.to&utm_medium=organic&utm_campaign=SEO_ARTICLES), 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](https://aiwave.live/models/ernie-tiny-8k?utm_source=dev.to&utm_medium=organic&utm_campaign=SEO_ARTICLES)** (budget-tier available, $0.1781/1M tokens) for simple queries and **[DeepSeek V4 Pro](https://aiwave.live/models/deepseek-v4-pro?utm_source=dev.to&utm_medium=organic&utm_campaign=SEO_ARTICLES)** ($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.1781 | $0.1781 | 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?utm_source=dev.to&utm_medium=organic&utm_campaign=SEO_ARTICLES"

MODELS = {
    "simple": "ernie-tiny-8k",
    "complex": "deepseek-v4-pro",
}

# Pricing data (USD per 1M tokens)
PRICING = {
    "ernie-tiny-8k": {"input": 0.1781, "output": 0.1781},
    "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](https://aiwave.live/models/glm-4.7-flash?utm_source=dev.to&utm_medium=organic&utm_campaign=SEO_ARTICLES), which is free on the budget tier).
    """
    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",  # Free (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](https://aiwave.live/?utm_source=dev.to&utm_medium=organic&utm_campaign=SEO_ARTICLES) 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](https://aiwave.live/models/?utm_source=dev.to&utm_medium=organic&utm_campaign=SEO_ARTICLES) and the [pricing page](https://aiwave.live/pricing?utm_source=dev.to&utm_medium=organic&utm_campaign=SEO_ARTICLES) for detailed cost breakdowns.

---

*Building production AI systems? Share your architecture in the [AIWave Discord](https://discord.gg/UDPgJBEdF).*

---

*We're a small team behind AIWave. No VC money, no big marketing budget — just a few people who believe Chinese AI models should be accessible to everyone in the world. Your API calls keep this project alive. If you find value in what we're building, stick around. It means more than you know.*