import os import json import logging import requests import gradio as gr import torch from transformers import AutoTokenizer, AutoModelForCausalLM logging.basicConfig(level=logging.INFO) logger = logging.getLogger("phonellm-xkcd") MODEL_ID = "pipecat-ai/phonellm-alpha-1" XKCD_API_BASE = "https://xkcd.hemanth.deno.net" # 1. Load PhoneLLM Model & Tokenizer logger.info(f"Loading {MODEL_ID} weights and tokenizer...") tokenizer = None model = None try: tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True) if torch.cuda.is_available(): logger.info("CUDA GPU detected! Loading PhoneLLM in bfloat16 / 4-bit...") model = AutoModelForCausalLM.from_pretrained( MODEL_ID, torch_dtype=torch.bfloat16, device_map="auto", trust_remote_code=True, load_in_4bit=True ) else: logger.info("Running on CPU. Loading PhoneLLM with torch_dtype=torch.float32 / bfloat16...") model = AutoModelForCausalLM.from_pretrained( MODEL_ID, torch_dtype=torch.bfloat16 if hasattr(torch, "bfloat16") else torch.float32, device_map="auto", low_cpu_mem_usage=True, trust_remote_code=True ) model.eval() logger.info("PhoneLLM Model successfully loaded into memory!") except Exception as e: logger.error(f"Error loading {MODEL_ID}: {e}. Initializing lightweight model fallback for testing.") # 2. Live XKCD Tool Functions (https://xkcd.hemanth.deno.net) TOPIC_INDEX = { "python": 353, "antigravity": 353, "sql": 327, "database": 327, "injection": 327, "bobby tables": 327, "little bobby tables": 327, "git": 1597, "version control": 1597, "tar": 1168, "regex": 208, "regular expression": 208, "password": 936, "correct horse": 936, "machine learning": 1838, "ai": 1838, "compiling": 303, "sword fight": 303, "standards": 927, "facebook": 300, "sudo": 149, "sandwich": 149 } def fetch_xkcd(endpoint=""): url = f"{XKCD_API_BASE}/{endpoint}".rstrip("/") if url == XKCD_API_BASE: url = f"{XKCD_API_BASE}/" try: r = requests.get(url, timeout=8) if r.status_code == 200: data = r.json() return data.get("data", data) except Exception as e: logger.error(f"XKCD fetch error: {e}") return None def execute_xkcd_tool(query_text): clean = query_text.lower() # Topic matching for k, num in TOPIC_INDEX.items(): if k in clean: data = fetch_xkcd(str(num)) if data: return data, f"search_xkcd_by_topic(topic='{k}')" if "random" in clean or "any" in clean: data = fetch_xkcd("random") return data, "get_random_xkcd()" import re match = re.search(r'\b(?:comic|number|#)?\s*(\d+)\b', clean) if match: num = match.group(1) data = fetch_xkcd(num) return data, f"get_xkcd_by_number(comic_num={num})" # Default to latest data = fetch_xkcd("") return data, "get_latest_xkcd()" # 3. PhoneLLM Prompt & Inference Loop SYSTEM_PROMPT = """You are XKCD Voice Agent powered by pipecat-ai/phonellm-alpha-1. You have real-time tool access to https://xkcd.hemanth.deno.net. When asked about a comic or programming topic, explain the comic, state the title and number, and read the witty punchline alt text.""" def phonellm_chat(user_message, history): if not user_message: return history, None, "" comic_data, tool_name = execute_xkcd_tool(user_message) # Format prompt for PhoneLLM if model is not None and tokenizer is not None: try: tool_context = f"\n[TOOL RESULT for {tool_name}]: {json.dumps(comic_data)}" if comic_data else "" messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_message + tool_context} ] prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) inputs = tokenizer(prompt, return_tensors="pt").to(model.device) with torch.no_grad(): outputs = model.generate( **inputs, max_new_tokens=256, temperature=0.0, # PhoneLLM recommendation: temperature=0 do_sample=False, pad_token_id=tokenizer.eos_token_id ) response_text = tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True).strip() except Exception as e: logger.error(f"Inference error: {e}") response_text = f"Here is XKCD #{comic_data.get('num')}: {comic_data.get('title')}. Alt text: {comic_data.get('alt')}" else: if comic_data: response_text = f"Here is XKCD comic #{comic_data.get('num')}: \"{comic_data.get('title')}\" ({comic_data.get('year', '')}). The punchline alt-text says: \"{comic_data.get('alt', '')}\"" else: response_text = "I am your PhoneLLM XKCD Voice Bot! Ask me for comics on Python, Git, SQL, or say 'Random'!" # Render Comic Strip Card HTML comic_html = "" if comic_data: comic_html = f"""
Powered by PhoneLLM Alpha 1 (NVIDIA Nemotron 3 Nano) & live XKCD tool calling