Spaces:
Runtime error
Runtime error
File size: 7,365 Bytes
2cf7ce5 6bb0df2 2cf7ce5 6bb0df2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | """
AI Voice + Text Chatbot - Backend
Flask + OpenRouter API with automatic multi-model fallback.
RUN:
pip install -r requirements.txt
python app.py
Then open http://127.0.0.1:5000 in Chrome or Edge (Web Speech API requirement).
"""
import time
import logging
import requests
from flask import Flask, render_template, request, jsonify
# ============================================================
# CONFIGURATION - put your OpenRouter API key here directly
# ============================================================
API_KEY = "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" # <-- REPLACE THIS with your real key
OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
# Models are tried in this exact order. If one fails (error, timeout,
# rate limit, empty reply) the next one is tried automatically until
# one succeeds or the list is exhausted.
MODELS = [
"nvidia/nemotron-3-ultra-550b-a55b:free",
"google/gemma-4-26b-a4b-it:free",
"openai/gpt-oss-20b:free",
"google/gemma-4-31b-it:free",
"inclusionai/ling-3.0-flash:free",
"cohere/north-mini-code:free",
"poolside/laguna-s-2.1:free",
"nvidia/nemotron-3-nano-30b-a3b:free",
]
REQUEST_TIMEOUT = 30 # seconds allowed per model attempt
# ============================================================
# ANSWER STYLE
# ============================================================
# This system prompt makes every model answer like an experienced human
# teacher: natural introduction -> detailed theory -> key points -> real
# world examples -> and, if code is relevant, the FULL working code first,
# followed by its explanation.
SYSTEM_PROMPT = """You are an experienced, friendly human teacher explaining concepts to a student.
Never answer with only bullet points or a dry list. Write naturally, in full sentences and
paragraphs, the way a good teacher speaks, and use clear section headers.
Follow this structure for every answer:
1. Short Introduction
Explain the topic naturally in 1-3 paragraphs, in plain conversational language.
2. Detailed Theory
Explain everything clearly using simple, professional language, step by step. Cover the
important concepts and, where relevant, advantages and disadvantages. Full sentences,
not just bullets.
3. Key Points
A bullet list summarizing the most important takeaways.
4. Real World Example
Give at least one, ideally two, practical real-world examples of the concept in use
(e.g. banking, hospital systems, e-commerce, student records, interviews, etc).
5. Code (only if the question needs code)
If the question requires code, put the COMPLETE, FULL WORKING CODE FIRST, before any
explanation, inside a fenced code block. Never explain before showing the code. After
the code, in this order: explanation of how it works, the expected output, time/space
complexity if relevant, best practices, and common mistakes to avoid.
If the question does not need code, simply skip section 5.
"""
# ============================================================
# LOGGING
# ============================================================
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
)
logger = logging.getLogger("voice-chatbot")
# ============================================================
# FLASK APP
# ============================================================
app = Flask(__name__)
def call_openrouter(model_name, messages):
"""
Send a single chat completion request to OpenRouter for one model.
Returns the reply text on success, raises RuntimeError on any failure.
"""
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
# These two headers are recommended by OpenRouter (optional but nice to have)
"HTTP-Referer": "http://localhost:5000",
"X-Title": "AI Voice Chatbot",
}
payload = {
"model": model_name,
"messages": messages,
}
response = requests.post(
OPENROUTER_URL,
headers=headers,
json=payload,
timeout=REQUEST_TIMEOUT,
)
if response.status_code != 200:
raise RuntimeError(f"HTTP {response.status_code}: {response.text[:250]}")
data = response.json()
choices = data.get("choices")
if not choices:
raise RuntimeError("No choices returned from model")
reply = choices[0].get("message", {}).get("content", "")
if not reply or not reply.strip():
raise RuntimeError("Empty response from model")
return reply.strip()
def get_ai_response(user_text):
"""
Try every model in MODELS, in order, until one succeeds.
Returns (reply_text, model_used, elapsed_seconds).
Raises RuntimeError only if every single model failed.
"""
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_text},
]
errors = []
for model_name in MODELS:
start = time.time()
try:
logger.info(f"Trying model: {model_name}")
reply = call_openrouter(model_name, messages)
elapsed = round(time.time() - start, 2)
logger.info(f"SUCCESS with {model_name} in {elapsed}s")
return reply, model_name, elapsed
except Exception as exc:
elapsed = round(time.time() - start, 2)
logger.warning(f"FAILED {model_name} after {elapsed}s: {exc}")
errors.append(f"{model_name} -> {exc}")
continue
raise RuntimeError("All models failed:\n" + "\n".join(errors))
# ============================================================
# ROUTES
# ============================================================
@app.route("/")
def home():
return render_template("index.html")
@app.route("/chat", methods=["POST"])
def chat():
"""
Receives the merged (voice transcript + manual text) prompt from the
browser and returns an AI answer, trying each OpenRouter model in the
fallback list until one succeeds.
"""
data = request.get_json(silent=True) or {}
user_text = (data.get("text") or "").strip()
if not user_text:
return jsonify({"success": False, "error": "No text provided. Speak or type something first."}), 400
if API_KEY == "YOUR_OPENROUTER_API_KEY":
return jsonify({
"success": False,
"error": "Server misconfigured: set your real OpenRouter API key in app.py (API_KEY variable)."
}), 400
try:
reply, model_used, elapsed = get_ai_response(user_text)
return jsonify({
"success": True,
"reply": reply,
"model": model_used,
"response_time": elapsed,
})
except Exception as exc:
logger.error(f"Chat failed completely: {exc}")
return jsonify({"success": False, "error": str(exc)}), 500
@app.route("/status")
def status():
return jsonify({
"status": "ready",
"models_configured": len(MODELS),
"models": MODELS,
"api_key_set": API_KEY != "YOUR_OPENROUTER_API_KEY",
})
import os
if __name__ == "__main__":
port = int(os.environ.get("PORT", 7860))
logger.info(f"Starting AI Voice Chatbot on port {port}")
app.run(
host="0.0.0.0",
port=port,
debug=False
) |