| import gradio as gr |
| import json |
| import time |
| import numpy as np |
| from pathlib import Path |
| from typing import List, Dict, Any, Optional |
| from llama_cpp import Llama |
| from huggingface_hub import hf_hub_download |
| import os |
| import re |
|
|
|
|
| |
| |
| |
| MODEL_REPO = "xubayer/LlamaPIE-GGUF" |
| SMALL_MODEL_FILENAME = "llamapie-1b-q8_0.gguf" |
| LARGE_MODEL_FILENAME = "llamapie-8b-q8_0.gguf" |
|
|
|
|
| |
| |
| |
| print("⏳ Downloading LlamaPIE models...") |
| small_model_path = hf_hub_download(repo_id=MODEL_REPO, filename=SMALL_MODEL_FILENAME) |
| large_model_path = hf_hub_download(repo_id=MODEL_REPO, filename=LARGE_MODEL_FILENAME) |
|
|
| print("🚀 Loading Classifier (1B)...") |
| classifier = Llama( |
| small_model_path, |
| n_ctx=2048, |
| n_threads=2, |
| verbose=False, |
| logits_all=True |
| ) |
|
|
| print("🚀 Loading Generator (8B)...") |
| generator = Llama(large_model_path, n_ctx=4096, n_threads=4, verbose=False) |
|
|
|
|
| |
| |
| |
| def classify_with_simple_continuation(model, dialogue_context: str) -> Dict: |
| """ |
| Simple approach: Just continue the dialogue and see what the model generates. |
| If it generates meaningful content, it's proactive. If not, it's reactive. |
| """ |
| |
| |
| prompt = dialogue_context + " |SILENCE >" |
| |
| output = model( |
| prompt, |
| max_tokens=15, |
| temperature=0.3, |
| top_p=0.9, |
| logprobs=5, |
| stop=["User:", "Speaker", "\n\n", "|SILENCE"] |
| ) |
| |
| generated_text = output['choices'][0]['text'].strip() |
| whisper_prob = 0.5 |
| |
| |
| try: |
| logprobs_data = output['choices'][0].get('logprobs', {}) |
| top_logprobs = logprobs_data.get('top_logprobs', []) |
| |
| if top_logprobs and len(top_logprobs) > 0: |
| first_token_probs = top_logprobs[0] |
| proactive_score = 0.0 |
| |
| for token, logprob in first_token_probs.items(): |
| prob = np.exp(logprob) |
| token_str = str(token).lower().strip() |
| |
| |
| if len(token_str) > 2 and any(c.isalpha() for c in token_str): |
| proactive_score += prob * 2.0 |
| |
| elif token_str in ['.', ',', '!', '?', '', ' ', '\n', '<', '|', '▁']: |
| proactive_score -= prob * 1.5 |
| else: |
| proactive_score += prob * 0.3 |
| |
| whisper_prob = 1.0 / (1.0 + np.exp(-proactive_score * 2.5)) |
| |
| except Exception as e: |
| print(f" Logprob error: {e}") |
| |
| |
| if not generated_text or len(generated_text.strip()) < 2: |
| whisper_prob = 0.1 |
| elif generated_text.strip() in ['.', ',', '!', '?', '|']: |
| whisper_prob = 0.2 |
| elif len(generated_text.split()) >= 3: |
| whisper_prob = max(whisper_prob, 0.9) |
| elif len(generated_text.split()) >= 2: |
| whisper_prob = max(whisper_prob, 0.8) |
| elif any(c.isalpha() for c in generated_text) and len(generated_text.strip()) > 3: |
| whisper_prob = max(whisper_prob, 0.7) |
| |
| silent_prob = 1.0 - whisper_prob |
| decision = "WHISPER" if whisper_prob > 0.5 else "SILENT" |
| confidence = max(whisper_prob, silent_prob) |
| |
| return { |
| "decision": decision, |
| "confidence": float(confidence), |
| "whisper_probability": float(whisper_prob), |
| "silent_probability": float(silent_prob), |
| "generated_text": generated_text |
| } |
|
|
|
|
| |
| |
| |
| class LlamaPIEInference: |
| def __init__(self, classifier, generator): |
| self.classifier = classifier |
| self.generator = generator |
| |
| def parse_dialogue(self, dialogue: str) -> List[Dict]: |
| """Parse dialogue into segments ending with |SILENCE > markers.""" |
| silence_positions = [m.end() for m in re.finditer(r'\|\s*SILENCE\s*\>', dialogue)] |
| segments = [] |
| |
| start = 0 |
| for pos in silence_positions: |
| segment = dialogue[start:pos].strip() |
| if segment: |
| segments.append({ |
| "text": segment, |
| "end_pos": pos, |
| "context": dialogue[max(0, pos-500):pos] |
| }) |
| start = pos |
| return segments |
|
|
| def classify_silence_position(self, context: str, confidence_threshold: float = 0.6) -> Dict: |
| """ |
| Simple classification: continue the dialogue and check output. |
| """ |
| print(f"\n🔍 Classifying...") |
| print(f" Last 80 chars: ...{context[-80:]}") |
| |
| try: |
| classification = classify_with_simple_continuation(self.classifier, context) |
| |
| classification["threshold_met"] = ( |
| classification["decision"] == "WHISPER" and |
| classification["whisper_probability"] >= confidence_threshold |
| ) |
| |
| print(f" Decision: {classification['decision']} (P={classification['whisper_probability']:.2f})") |
| print(f" Generated: '{classification['generated_text'][:60]}'") |
| |
| return classification |
| |
| except Exception as e: |
| print(f" ❌ Error: {e}") |
| return { |
| "decision": "SILENT", |
| "confidence": 0.5, |
| "whisper_probability": 0.5, |
| "silent_probability": 0.5, |
| "generated_text": "", |
| "threshold_met": False |
| } |
| |
| def generate_whisper(self, context: str, memory: str) -> str: |
| """ |
| Generate whisper using memory context. |
| """ |
| |
| memory_text = "" |
| if memory: |
| try: |
| memory_obj = json.loads(memory) |
| profile = memory_obj.get("profile", "") |
| events = memory_obj.get("events", {}) |
| |
| memory_parts = [] |
| if profile: |
| memory_parts.append(f"Profile: {profile}") |
| for event_key, event_val in events.items(): |
| if isinstance(event_val, str): |
| memory_parts.append(f"Memory: {event_val}") |
| |
| if memory_parts: |
| memory_text = "\n".join(memory_parts) + "\n\n" |
| except: |
| memory_text = f"{memory}\n\n" |
| |
| |
| prompt = f"""{memory_text}Conversation: |
| {context} |SILENCE > |
| |
| Provide a helpful 1-3 word whisper:""" |
| |
| print(f" 🎤 Generating whisper...") |
| |
| output = self.generator( |
| prompt, |
| max_tokens=10, |
| temperature=0.4, |
| top_p=0.8, |
| stop=["\n", "User:", "Speaker:", "Conversation:", "Profile:", "Memory:"], |
| ) |
| |
| raw_text = output['choices'][0]['text'].strip() |
| |
| |
| whisper = raw_text.split('\n')[0].strip() |
| whisper = re.sub(r'["\',;(){}[\]:]', '', whisper) |
| |
| |
| whisper = re.sub(r'^(whisper|hint|suggestion|reminder):\s*', '', whisper, flags=re.IGNORECASE) |
| |
| |
| words = [w for w in whisper.split() if len(w) > 0] |
| if len(words) == 0: |
| whisper = "Continue" |
| elif len(words) > 3: |
| whisper = " ".join(words[:3]) |
| else: |
| whisper = " ".join(words) |
| |
| |
| if whisper.lower() in ["the", "and", "or", "but", "a", "an", "is", "it", "to"]: |
| whisper = "Continue" |
| |
| print(f" Result: '{whisper}'") |
| |
| return whisper |
|
|
| def process_conversation(self, dialogue: str, memory: str = "", |
| confidence_threshold: float = 0.6) -> Dict[str, Any]: |
| """Main pipeline: Process full conversation.""" |
| start_time = time.time() |
| |
| segments = self.parse_dialogue(dialogue) |
| whispers = [] |
| |
| print(f"\n📝 Processing {len(segments)} silence positions with threshold={confidence_threshold}") |
| |
| for i, segment in enumerate(segments): |
| print(f"\n--- Position {i+1}/{len(segments)} ---") |
| classification = self.classify_silence_position( |
| segment["context"], |
| confidence_threshold |
| ) |
| |
| if classification["threshold_met"]: |
| whisper_result = self.generate_whisper(segment["context"], memory) |
| |
| whispers.append({ |
| "position": i + 1, |
| "text": whisper_result, |
| "confidence": classification["confidence"], |
| "whisper_probability": classification["whisper_probability"], |
| "silent_probability": classification["silent_probability"], |
| "context": segment["context"][-100:], |
| "classifier_output": classification.get("generated_text", "")[:50] |
| }) |
| |
| |
| annotated = dialogue |
| for whisper in whispers: |
| annotated = annotated.replace("|SILENCE >", f" [💬 {whisper['text']}] |SILENCE >", 1) |
| |
| print(f"\n✅ Complete! {len(whispers)}/{len(segments)} positions triggered") |
| |
| return { |
| "input": { |
| "dialogue": dialogue, |
| "memory": memory, |
| "confidence_threshold": confidence_threshold |
| }, |
| "whispers": whispers, |
| "annotated_dialogue": annotated, |
| "stats": { |
| "total_silence_positions": len(segments), |
| "whisper_decisions": len(whispers), |
| "processing_time_ms": int((time.time() - start_time) * 1000), |
| "whisper_frequency": f"{len(whispers)/max(1, len(segments))*100:.1f}%" |
| } |
| } |
|
|
|
|
| |
| pipeline = LlamaPIEInference(classifier, generator) |
|
|
|
|
| |
| |
| |
| css = """ |
| .gradio-container { max-width: 1100px !important; } |
| .output-box { background: #f8f9fa; border-radius: 8px; padding: 12px; } |
| """ |
|
|
|
|
| def run_inference(dialogue: str, memory: str, confidence_threshold: float): |
| """Gradio inference wrapper.""" |
| try: |
| result = pipeline.process_conversation(dialogue, memory, confidence_threshold) |
| |
| whisper_lines = [ |
| f"• Pos {w['position']}: '{w['text']}' (P={w['whisper_probability']:.2f})" |
| for w in result["whispers"] |
| ] |
| whispers_text = "\n".join(whisper_lines) if whisper_lines else "No whispers triggered" |
| |
| annotated = result["annotated_dialogue"] |
| if len(annotated) > 2000: |
| annotated = annotated[:2000] + "\n... (truncated)" |
| |
| output = ( |
| f"### 📊 **Results** ({result['stats']['processing_time_ms']}ms)\n" |
| f"**Whispers:** {result['stats']['whisper_decisions']}/{result['stats']['total_silence_positions']} positions " |
| f"({result['stats']['whisper_frequency']})\n\n" |
| f"**Generated Whispers:**\n" |
| f"```\n{whispers_text}\n```\n\n" |
| f"**Annotated Dialogue:**\n" |
| f"```\n{annotated}\n```\n" |
| ) |
| |
| return output, json.dumps(result, indent=2) |
| |
| except Exception as e: |
| import traceback |
| error_details = traceback.format_exc() |
| return f"❌ Error: {str(e)}\n\n```\n{error_details}\n```", "" |
|
|
|
|
| |
| |
| |
| with gr.Blocks(css=css, title="LlamaPIE Whisper Inference") as demo: |
| gr.Markdown("# 🤖 LlamaPIE Whisper Inference") |
| gr.Markdown("*Simple proactive assistant with memory-aware whispers*") |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| memory_input = gr.Textbox( |
| label="📚 Memory Context (JSON)", |
| placeholder='{"profile": "...", "events": {...}}', |
| lines=8, |
| value='{"profile": "User is interested in technology", "events": {}}' |
| ) |
| confidence_slider = gr.Slider( |
| minimum=0.0, |
| maximum=1.0, |
| value=0.65, |
| step=0.05, |
| label="🎯 Confidence Threshold (higher = fewer whispers)" |
| ) |
| |
| with gr.Column(scale=2): |
| dialogue_input = gr.Textbox( |
| label="💬 Dialogue (with |SILENCE > markers)", |
| placeholder="User: Hello |SILENCE > Agent: Hi there |SILENCE >", |
| lines=12 |
| ) |
| |
| submit_btn = gr.Button("🚀 Generate Whispers", variant="primary", size="lg") |
| |
| with gr.Row(): |
| with gr.Column(): |
| output_display = gr.Markdown(label="Results") |
| |
| with gr.Row(): |
| with gr.Column(): |
| json_output = gr.JSON(label="Full JSON Output") |
| |
| submit_btn.click( |
| fn=run_inference, |
| inputs=[dialogue_input, memory_input, confidence_slider], |
| outputs=[output_display, json_output] |
| ) |
| |
| gr.Examples( |
| examples=[ |
| [ |
| "User: I've been working on a project. |SILENCE > Agent: That's great! |SILENCE > User: I added the new database |SILENCE > feature. |SILENCE >", |
| '{"profile": "Software developer working on web app", "events": {"event0": "Struggled with database optimization last week"}}', |
| 0.65 |
| ], |
| [ |
| "User: I need to call mom. |SILENCE > Agent: Sure! |SILENCE > User: And that important |SILENCE > meeting tomorrow. |SILENCE >", |
| '{"profile": "Busy schedule, forgets appointments", "events": {"event0": "Mom\'s birthday coming up"}}', |
| 0.7 |
| ], |
| ], |
| inputs=[dialogue_input, memory_input, confidence_slider], |
| label="Example Conversations" |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| demo.launch() |