Timur / app.py
VoidOaz's picture
Update app.py
65cb1f6 verified
Raw History Blame Contribute Delete
9.23 kB
"""
Timur 1.6 Flash — AI Chat (Single File, Gradio 6.x Compatible)
"""
""" stdlib """
import os, sys, logging, time, threading
from typing import Generator
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(name)s] %(levelname)s — %(message)s",
)
logger = logging.getLogger(__name__)
""" 1. CONFIGURATION """
MODEL_FILE = os.environ.get("TIMUR_MODEL_FILE", "timur_16_flash.pt")
MODEL_DISPLAY = os.environ.get("TIMUR_MODEL_DISPLAY", "Timur 1.6 Flash")
FALLBACK_MODEL = os.environ.get("TIMUR_FALLBACK_MODEL","claude-sonnet-4-20250514")
ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
MAX_NEW_TOKENS = int(os.environ.get("MAX_NEW_TOKENS", "512"))
TEMPERATURE = float(os.environ.get("TEMPERATURE", "0.7"))
TOP_P = float(os.environ.get("TOP_P", "0.9"))
REP_PENALTY = float(os.environ.get("REP_PENALTY", "1.1"))
MAX_HISTORY_CHARS = int(os.environ.get("MAX_HISTORY_CHARS", "12000"))
MAX_HISTORY_TURNS = int(os.environ.get("MAX_HISTORY_TURNS", "20"))
MAX_RETRIES = int(os.environ.get("MAX_RETRIES", "3"))
RETRY_BASE_DELAY = float(os.environ.get("RETRY_BASE", "1.0"))
MAX_RPM = int(os.environ.get("MAX_RPM", "30"))
QUANTIZATION = os.environ.get("QUANTIZATION", "none")
PORT = int(os.environ.get("PORT", "7860"))
AVAILABLE_MODELS = [MODEL_DISPLAY]
""" 2. LOCAL MODEL LOADER """
_model = None
_tokenizer = None
_device = None
_load_error = None
_loaded = False
metrics = {
"total_requests": 0, "local_requests": 0,
"fallback_requests": 0, "errors": 0,
"total_tokens": 0, "total_latency_ms": 0,
}
def _detect_device() -> str:
try:
import torch
if torch.cuda.is_available():
logger.info(f"[Timur] GPU: {torch.cuda.get_device_name(0)}")
return "cuda"
if hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
logger.info("[Timur] GPU: Apple MPS")
return "mps"
except ImportError:
pass
logger.info("[Timur] CPU will be used")
return "cpu"
def _try_load_model() -> bool:
global _model, _tokenizer, _device, _load_error
if not os.path.exists(MODEL_FILE):
_load_error = f"{MODEL_FILE} not found"
logger.warning(f"[Timur] {_load_error}")
return False
try:
import torch
except ImportError:
_load_error = "torch is not installed"
logger.warning(f"[Timur] {_load_error}")
return False
_device = _detect_device()
logger.info(f"[Timur] Loading {MODEL_FILE}...")
""" Fallback directly to Anthropic for this simplified UI example """
_load_error = "Skipping local load for UI testing, using API fallback."
return False
def _ensure_loaded():
global _loaded
if _loaded:
return
_loaded = True
_try_load_model()
def _is_local() -> bool:
_ensure_loaded()
return _model is not None and _tokenizer is not None
""" 3. INFERENCE """
import anthropic as _ant
_api_client = _ant.Anthropic(api_key=ANTHROPIC_API_KEY or None)
_gen_lock = threading.Lock()
_request_times: list = []
_rate_lock = threading.Lock()
if not ANTHROPIC_API_KEY:
logger.warning("[Timur] ANTHROPIC_API_KEY is missing!")
def _rate_ok() -> bool:
now = time.time()
with _rate_lock:
_request_times[:] = [t for t in _request_times if now - t < 60]
if len(_request_times) >= MAX_RPM:
return False
_request_times.append(now)
return True
def _trim_history(history):
if len(history) > MAX_HISTORY_TURNS:
history = history[-MAX_HISTORY_TURNS:]
while history:
total = sum(len(u or "") + len(a or "") for u, a in history)
if total <= MAX_HISTORY_CHARS:
break
history = history[1:]
return history
def _api_stream(messages, system) -> Generator[str, None, None]:
last_exc = None
for attempt in range(MAX_RETRIES):
try:
with _api_client.messages.stream(
model=FALLBACK_MODEL, max_tokens=MAX_NEW_TOKENS,
system=system, messages=messages,
) as s:
for tok in s.text_stream:
yield tok
return
except Exception as e:
logger.error(f"[Timur] API error: {e}")
last_exc = e; break
raise RuntimeError(f"All attempts failed: {last_exc}")
def _build_api_msgs(history, user_msg):
msgs = []
for u, a in history:
if u: msgs.append({"role": "user", "content": u})
if a: msgs.append({"role": "assistant", "content": a})
msgs.append({"role": "user", "content": user_msg})
return msgs
def chat_stream(message, history, model_choice):
if not message.strip():
yield history, ""; return
if not _rate_ok():
yield history + [[message, "Too many requests. Please wait."]], ""; return
if not _gen_lock.acquire(blocking=False):
yield history + [[message, "Currently processing another request."]], ""; return
metrics["total_requests"] += 1
t0 = time.time()
try:
system = f"You are {model_choice}, a helpful and thoughtful AI assistant."
trimmed = _trim_history(list(history))
history = trimmed + [[message, ""]]
metrics["fallback_requests"] += 1
api_msgs = _build_api_msgs(trimmed, message)
for tok in _api_stream(api_msgs, system):
history[-1][1] += tok
yield list(history), "streaming"
except Exception as e:
metrics["errors"] += 1
logger.error(f"[Timur] chat_stream error: {e}")
history[-1][1] = f"Error: {e}"
yield list(history), ""
finally:
_gen_lock.release()
metrics["total_latency_ms"] += int((time.time() - t0) * 1000)
yield list(history), ""
""" 4. CSS (Gemini Style) """
CSS = """
:root {
--bg: rgb(25, 25, 28);
--chat-bg: rgb(30, 31, 34);
--user-bg: rgb(50, 52, 56);
--txt: rgb(240, 240, 240);
--accent: rgb(138, 180, 248);
}
body, .gradio-container {
background-color: var(--bg) !important;
color: var(--txt) !important;
font-family: 'Google Sans', 'Sora', sans-serif !important;
}
[id="chat-container"] {
max-width: 900px !important;
margin: 0 auto !important;
border: none !important;
}
[id="chatbot"] {
background: transparent !important;
border: none !important;
}
.message.user {
background-color: var(--user-bg) !important;
color: var(--txt) !important;
border-radius: 20px 20px 4px 20px !important;
padding: 12px 18px !important;
border: none !important;
}
.message.bot {
background-color: transparent !important;
color: var(--txt) !important;
border: none !important;
padding: 12px 0px !important;
}
[id="input-container"] {
max-width: 900px !important;
margin: 0 auto !important;
background-color: var(--chat-bg) !important;
border-radius: 30px !important;
padding: 8px 16px !important;
border: 1px solid rgb(60, 60, 65) !important;
}
[id="input-container"] textarea {
background: transparent !important;
border: none !important;
box-shadow: none !important;
color: var(--txt) !important;
font-size: 1rem !important;
}
[id="send-btn"] {
background-color: transparent !important;
border: none !important;
color: var(--accent) !important;
cursor: pointer !important;
box-shadow: none !important;
}
"""
""" 5. GRADIO UI """
import gradio as gr
def on_submit(message, history, model_choice):
if not message.strip():
yield "", history, history
return
final_hist = history
for new_hist, st in chat_stream(message, history, model_choice):
final_hist = new_hist
yield "", new_hist, new_hist
yield "", final_hist, final_hist
def on_new_chat():
return [], []
with gr.Blocks(fill_height=True, css=CSS, theme=gr.themes.Default()) as demo:
history_state = gr.State([])
with gr.Column(elem_id="chat-container", scale=1):
chatbot = gr.Chatbot(
elem_id="chatbot",
show_label=False,
scale=1,
avatar_images=(None, None),
render_markdown=True,
sanitize_html=True,
)
with gr.Row(elem_id="input-container"):
msg = gr.Textbox(
show_label=False,
placeholder="Ask Timur...",
lines=1,
max_lines=5,
scale=8,
container=False
)
model_dd = gr.Dropdown(
choices=AVAILABLE_MODELS, value=MODEL_DISPLAY,
show_label=False, interactive=True,
scale=1, container=False
)
send_btn = gr.Button("Send", elem_id="send-btn", scale=1)
sub_in = [msg, history_state, model_dd]
sub_out = [msg, chatbot, history_state]
send_btn.click(fn=on_submit, inputs=sub_in, outputs=sub_out, queue=True)
msg.submit(fn=on_submit, inputs=sub_in, outputs=sub_out, queue=True)
if __name__ == "__main__":
demo.queue().launch(
server_name="0.0.0.0",
server_port=PORT,
share=False,
show_api=False
)