Spaces:
Sleeping
Sleeping
File size: 8,123 Bytes
d1d1019 | 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 |
import requests
import json
import os
import joblib
import numpy as np
from sentence_transformers import SentenceTransformer
OLLAMA_API_URL = "http://localhost:11434/api/generate"
MODEL_NAME = "llama3.2"
class LLMInterface:
def __init__(self):
self.available = self.check_availability()
self.reward_model = self.load_reward_model()
# We need an encoder here for the reward model if it's not passed in
# Ideally we share the one from RAG engine, but for simplicity/robustness loading a small one here or passing it in generate
try:
self.encoder = SentenceTransformer('all-MiniLM-L6-v2')
except: self.encoder = None
if self.available:
print(f"✓ Connected to Ollama ({MODEL_NAME})")
else:
print("⚠ Ollama not detected. Using Logic-Based Fallback.")
def load_reward_model(self):
try:
data = joblib.load("model/rlhf_model.joblib")
print("✓ Loaded RLHF Models (Reward + VPL)")
return data
except:
print("⚠ No RLHF Models found. Self-Correction disabled.")
return None
def check_availability(self):
try:
response = requests.get("http://localhost:11434/api/tags", timeout=2)
if response.status_code == 200:
models = [m['name'] for m in response.json().get('models', [])]
if any(MODEL_NAME in m for m in models):
return True
print(f"⚠ Ollama running but '{MODEL_NAME}' not found. Please run 'ollama pull {MODEL_NAME}'")
return False
except:
return False
def score_response(self, query, response_text):
"""Calculate Reward Score & Uncertainty using VPL"""
if not self.reward_model or not self.encoder:
return 0.5, 0.0 # Neutral score, no uncertainty
try:
full_text = f"Q: {query} A: {response_text}"
embedding = self.encoder.encode([full_text])
# Use VPL if available (Pluralistic Alignment)
if 'vpl' in self.reward_model:
vpl = self.reward_model['vpl']
mean_reward, std_reward = vpl.predict_reward(embedding)
score = 1 / (1 + np.exp(-mean_reward[0]))
uncertainty = std_reward[0]
return score, uncertainty
# Fallback to simple RM
elif 'rm' in self.reward_model:
score = self.reward_model['rm'].forward(embedding)[0]
return 1 / (1 + np.exp(-score)), 0.0
return 0.5, 0.0
except Exception as e:
print(f"Scoring Error: {e}")
return 0.5, 0.0
def generate_response(self, user_query, rag_context, user_profile=None, journal_context=None):
if not self.available:
return self.fallback_response(user_query, rag_context)
# Self-Correction Loop
best_response = ""
best_score = -1.0
final_uncertainty = 0.0
system_prompt = self.build_system_prompt(user_profile)
context_str = self.format_context(rag_context, journal_context)
for attempt in range(2):
prompt = self.build_prompt(user_query, context_str, attempt, best_response)
current_response = self.call_ollama(prompt, system_prompt)
current_score, current_uncertainty = self.score_response(user_query, current_response)
print(f"Attempt {attempt+1}: Score={current_score:.2f}, Uncertainty={current_uncertainty:.2f}")
if current_score > best_score:
best_score = current_score
best_response = current_response
final_uncertainty = current_uncertainty
if current_score > 0.7:
break
# Active Learning Trigger (Silent)
# We still calculate uncertainty for logging, but we don't annoy the user.
if final_uncertainty > 0.1:
pass # Log internally if needed
elif best_score > 0.8:
pass # Log internally
return best_response
def rewrite_query(self, user_query):
"""Optimize user query for vector search using LLM"""
if not self.available:
return user_query # Fallback
system_prompt = "You are an expert search engine optimizer. Your goal is to rewrite queries to be precise, medical, and context-rich for a mental health database."
prompt = f"""
Original Query: {user_query}
Task: Rewrite this query to improve retrieval of relevant mental health advice.
- Expand vague terms (e.g. "tired" -> "fatigue burnout symptoms").
- Keep it concise (max 10 words).
- Remove conversational filler.
- Output ONLY the rewritten query string.
"""
rewritten = self.call_ollama(prompt, system_prompt)
# Clean up response (some models are chatty)
clean = rewritten.replace('"', '').replace("Here is the rewritten query:", "").strip()
print(f"RAG Optimization: '{user_query}' -> '{clean}'")
return clean
def build_prompt(self, query, context, attempt, previous_bad_response):
prompt = f"""
Context Information:
{context}
User Question: {query}
Instructions:
Answer the user's question using the provided context.
Be empathetic, professional, and concise.
"""
if attempt > 0:
prompt += f"\nCRITIQUE: Your previous answer was: '{previous_bad_response[:100]}...'. It was not helpful enough. Improve it."
return prompt
def call_ollama(self, prompt, system_prompt):
try:
payload = {
"model": MODEL_NAME,
"prompt": prompt,
"system": system_prompt,
"stream": False,
"options": {
"temperature": 0.7,
"num_ctx": 4096
}
}
response = requests.post(OLLAMA_API_URL, json=payload, timeout=60)
if response.status_code == 200:
return response.json().get('response', "I'm having trouble thinking right now.")
return f"Error: {response.text}"
except Exception as e:
print(f"Gen Error: {e}")
return "Thinking failed."
def format_context(self, rag_context, journal_context):
context_str = ""
for item in rag_context:
context_str += f"- [{item['source']}] {item['title']}: {item['content'][:300]}...\n"
if journal_context:
context_str += f"\n[User Journal Context]: {journal_context}\n"
return context_str
def build_system_prompt(self, user_profile):
base_prompt = (
"You are the DC Well Being Assistant, a knowledgeable mental health expert. "
"Your goal is to answer the user's questions DIRECTLY and FACTUALLY. "
"DO NOT act like a therapist asking reflection questions (e.g. 'How does that make you feel?'). "
"DO NOT say 'I'm here to support you' or 'I glad you reached out'. "
"If the user asks for an explanation (e.g. 'explain x'), provide the explanation immediately. "
"Be empathetic but conscise. "
)
if user_profile:
name = user_profile.get('name', 'User')
risk = user_profile.get('overall_risk', 'unknown')
base_prompt += f"\nUser: {name}. Risk: '{risk}'."
return base_prompt
def fallback_response(self, query, rag_context):
"""Legacy logic-based response if LLM is down"""
text = "Based on my database:\n\n"
for i, r in enumerate(rag_context[:3], 1):
text += f"**{r['title']}**\n{r['content'][:200]}...\n\n"
text += "_[Ollama Llama 3.2 is offline]_"
return text
|