xubayer's picture
Update app.py
f05c8ae verified
Raw
History Blame Contribute Delete
11.4 kB
import gradio as gr
import json
import time # ← ADD THIS LINE
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
# ====================================
# CONFIGURATION
# ====================================
MODEL_REPO = "xubayer/LlamaPIE-GGUF"
SMALL_MODEL_FILENAME = "llamapie-1b-q8_0.gguf"
LARGE_MODEL_FILENAME = "llamapie-8b-q8_0.gguf"
# ====================================
# MODEL LOADING
# ====================================
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)
print("🚀 Loading Generator (8B)...")
generator = Llama(large_model_path, n_ctx=2048, n_threads=2, verbose=False)
# ====================================
# CORE INFERENCE LOGIC
# ====================================
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."""
# Split by |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-200):pos] # Last 200 chars
})
start = pos
return segments
def classify_silence_position(self, context: str, confidence_threshold: float = 0.6) -> Dict:
"""
Stricter Classifier: Force model to choose between specific outcomes if possible,
or use a better heuristic for open-ended generation.
"""
# PROMPT ENGINEERING:
# We explicitly tell the 1B model what to do.
# If your model was trained with a specific prompt wrapper, use it here.
# Assuming raw completion:
prompt = context + " |SILENCE >"
output = self.classifier(
prompt,
max_tokens=5, # Generate a bit more to see intent
temperature=0.1,
top_p=0.9,
# CRITICAL: Stop generating if it starts a new turn
stop=["User:", "Speaker", "\n", "|"]
)
generated = output['choices'][0]['text'].strip()
# DEBUG: Print what it actually generated to help you debug
print(f"DEBUG Classifier [Pos ?]: '{generated}'")
# LOGIC FIX:
# If it generates "(whisper)" or purely structural tokens, that might actually be its way of saying "I have nothing to add"
# unless your training data explicitly used "(whisper)" as the positive label.
# New Heuristic:
# 1. If it generates nothing or just whitespace -> SILENT
# 2. If it generates brackets like (silence) or (music) -> SILENT
# 3. If it generates actual words -> WHISPER
is_silence = False
if not generated:
is_silence = True
elif generated.lower() in ["(silence)", "(no)", "no", "."]:
is_silence = True
elif len(generated) < 2:
is_silence = True
# FORCE CONFIDENCE SCORE
# We can't get true logits easily in the high-level API without 'logprobs=True'
# So we simulate confidence based on clarity.
if is_silence:
decision = "SILENT"
confidence = 0.1
else:
decision = "WHISPER"
confidence = 0.95 # It produced text, so it's confident it wants to speak
return {
"decision": decision,
"confidence": confidence,
"generated_preview": generated,
"threshold_met": decision == "WHISPER"
}
def generate_whisper(self, context: str, memory: Optional[str] = None) -> str:
"""Generate concise, helpful whispers with better prompting."""
# Structured prompt for consistent output
if memory:
prompt = f"""You are a helpful AI assistant that whispers short, useful hints (1-3 words).
Memory: {memory}
Conversation: {context} |SILENCE >
Whisper a short, helpful hint (1-3 words): """
else:
prompt = f"""You are a helpful AI assistant that whispers short, useful hints (1-3 words).
Conversation: {context} |SILENCE >
Whisper a short, helpful hint (1-3 words): """
output = self.generator(
prompt,
max_tokens=8, # Enough for 1-3 words
temperature=0.3, # Lower for more consistent output
top_p=0.8,
stop=["\n", ".", "|", "Conversation:", "User:", "Speaker"]
)
raw_text = output['choices'][0]['text'].strip()
# Clean and validate the whisper
whisper = raw_text.split('\n')[0] # Take only first line
# Remove quotes and unwanted characters
whisper = re.sub(r'["\',;]', '', whisper)
# Ensure it's 1-3 words
words = whisper.split()
if len(words) == 0:
return "Continue" # Default fallback
elif len(words) > 3:
# Take key words: usually verb + object
if len(words) >= 3:
whisper = " ".join(words[:3])
else:
whisper = " ".join(words)
# Make sure whisper is action-oriented
whisper = whisper.strip()
if not whisper or whisper.lower() in ["the", "and", "or", "but", "then"]:
whisper = "Go on" # Safe default
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 = []
for i, segment in enumerate(segments):
classification = self.classify_silence_position(segment["context"], confidence_threshold)
whisper_result = None
if classification["threshold_met"]:
whisper_result = self.generate_whisper(segment["context"], memory)
whispers.append({
"position": i + 1,
"text": whisper_result,
"confidence": classification["confidence"],
"context": segment["context"][-100:], # Last 100 chars
"preview_token": classification["generated_preview"]
})
# Reconstruct annotated dialogue
annotated = dialogue
for whisper in whispers:
annotated = annotated.replace("|SILENCE >", f" Agent: {whisper['text']} |SILENCE >", 1)
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}%"
}
}
# Instantiate pipeline
pipeline = LlamaPIEInference(classifier, generator)
# ====================================
# GRADIO UI
# ====================================
css = """
.gradio-container { max-width: 900px !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)
# Format output for display
whisper_lines = [
f"• Pos {w['position']}: '{w['text']}' (conf: {w['confidence']:.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) > 1000:
annotated = annotated[:1000] + "..."
output = (
f"### 📊 **Results** ({result['stats']['processing_time_ms']}ms)\n"
f"**Whispers Found:** {result['stats']['whisper_frequency']}\n\n"
f"**Whispers:**\n"
f"```\n{whispers_text}\n```\n\n"
f"**Annotated Dialogue Preview:**\n"
f"```\n{annotated}\n```\n\n"
f"**Full Stats:**\n"
f"- Total silence positions: {result['stats']['total_silence_positions']}\n"
f"- Whispers triggered: {result['stats']['whisper_decisions']}"
)
return output, json.dumps(result, indent=2)
except Exception as e:
return f"❌ Error: {str(e)}", ""
# ====================================
# GRADIO INTERFACE
# ====================================
with gr.Blocks(css=css) as demo:
gr.Markdown("# 🤖 LlamaPIE Whisper Inference")
with gr.Row():
with gr.Column():
dialogue_input = gr.Textbox(
label="Dialogue (with |SILENCE > markers)",
placeholder="User: Hello |SILENCE > Agent: Hi there |SILENCE >",
lines=5
)
memory_input = gr.Textbox(
label="Memory Context (optional)",
placeholder="Previous conversation summary...",
lines=2
)
confidence_slider = gr.Slider(
minimum=0.0,
maximum=1.0,
value=0.6,
step=0.1,
label="Confidence Threshold"
)
submit_btn = gr.Button("🚀 Generate Whispers", variant="primary")
with gr.Row():
with gr.Column():
output_display = gr.Markdown(label="Results")
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: What's the weather? |SILENCE > Agent: It's sunny today. |SILENCE >", "", 0.6],
["Speaker A: I'm thinking about... |SILENCE > Speaker B: Go ahead. |SILENCE >", "Previous: discussing plans", 0.7],
],
inputs=[dialogue_input, memory_input, confidence_slider]
)
if __name__ == "__main__":
demo.launch()