DreamMachine / llm_agent.py
Dave Roby
Fix model availability issues and disable Zero GPU
77a06d0
"""
LLM Agent Module for DReamMachine
Handles all interactions with HuggingFace models via Inference API
"""
import os
import json
import time
import logging
from typing import Dict, List, Optional, Any
from huggingface_hub import InferenceClient
import yaml
logger = logging.getLogger(__name__)
class LLMAgent:
"""Manages LLM API calls to HuggingFace models"""
def __init__(self, config_path: str = "config.yaml", hf_token: Optional[str] = None):
"""
Initialize LLM Agent
Args:
config_path: Path to configuration YAML file
hf_token: HuggingFace API token (if not provided, uses HF_TOKEN env var)
"""
# Load configuration
with open(config_path, 'r') as f:
self.config = yaml.safe_load(f)
# Get HuggingFace token
self.hf_token = hf_token or os.getenv('HF_TOKEN')
if not self.hf_token:
raise ValueError("HuggingFace token required. Set HF_TOKEN environment variable.")
# Initialize Inference Client
self.client = InferenceClient(token=self.hf_token)
# Load model configurations
self.models = self.config.get('models', {})
self.use_zero_gpu = self.config.get('huggingface', {}).get('use_zero_gpu', False)
logger.info("LLMAgent initialized successfully")
def call_hf_model(
self,
model_id: str,
system_prompt: str,
user_prompt: str,
temperature: float = 0.7,
max_tokens: int = 1000,
retries: int = 3
) -> str:
"""
Standard function for HuggingFace model API calls
Args:
model_id: HuggingFace model identifier
system_prompt: System-level instructions for the model
user_prompt: User prompt/query
temperature: Sampling temperature (higher = more creative)
max_tokens: Maximum tokens to generate
retries: Number of retry attempts on failure
Returns:
Generated text response
"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
for attempt in range(retries):
try:
logger.info(f"Calling {model_id} (attempt {attempt + 1}/{retries})")
response = self.client.chat_completion(
model=model_id,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
stream=False
)
# Extract generated text
result = response.choices[0].message.content
logger.info(f"Successfully received response from {model_id}")
return result
except Exception as e:
logger.warning(f"Error calling {model_id}: {str(e)}")
if attempt < retries - 1:
wait_time = (attempt + 1) * 2 # Exponential backoff
logger.info(f"Retrying in {wait_time} seconds...")
time.sleep(wait_time)
else:
logger.error(f"Failed to call {model_id} after {retries} attempts")
raise
return "" # Should not reach here
def get_dreamer_output(
self,
prompt: str,
model_config: Optional[Dict[str, Any]] = None,
model_index: int = 0
) -> str:
"""
Specialized wrapper for Dreamer LLM calls (high creativity)
Args:
prompt: The dream prompt
model_config: Optional model configuration override
model_index: Which dreamer model to use (0-2)
Returns:
Creative dream output
"""
if model_config is None:
dreamers = self.models.get('dreamers', [])
if model_index >= len(dreamers):
model_index = 0
model_config = dreamers[model_index]
system_prompt = """You are a creative genius and visionary inventor. Your purpose is to
imagine breakthrough innovations that could change the world. Think freely, boldly, and without
conventional limitations. This is a controlled creative hallucination - let your imagination soar
while staying grounded in the realm of physical possibility."""
return self.call_hf_model(
model_id=model_config['model_id'],
system_prompt=system_prompt,
user_prompt=prompt,
temperature=model_config.get('temperature', 0.9),
max_tokens=model_config.get('max_tokens', 1000)
)
def get_writer_output(self, prompt: str) -> str:
"""Specialized wrapper for Writer LLM (narrative creation)"""
model_config = self.models.get('writer', {})
system_prompt = """You are an expert technical storyteller. You transform complex
innovations into compelling narratives that inspire and educate. Write with clarity,
emotion, and vision."""
return self.call_hf_model(
model_id=model_config.get('model_id', 'mistralai/Mistral-7B-Instruct-v0.2'),
system_prompt=system_prompt,
user_prompt=prompt,
temperature=model_config.get('temperature', 0.6),
max_tokens=model_config.get('max_tokens', 1200)
)
def get_logger_output(self, prompt: str) -> str:
"""Specialized wrapper for Logger LLM (technical extraction)"""
model_config = self.models.get('logger', {})
system_prompt = """You are a technical analyst. Extract and organize technical
specifications with precision and clarity. Focus on concrete details and requirements."""
return self.call_hf_model(
model_id=model_config.get('model_id', 'mistralai/Mistral-7B-Instruct-v0.2'),
system_prompt=system_prompt,
user_prompt=prompt,
temperature=model_config.get('temperature', 0.4),
max_tokens=model_config.get('max_tokens', 800)
)
def get_narrator_output(self, prompt: str) -> str:
"""Specialized wrapper for Narrator LLM (presentation)"""
model_config = self.models.get('narrator', {})
system_prompt = """You are a world-class presenter and communicator. Create
engaging, inspiring presentations that connect with audiences emotionally while
conveying complex ideas clearly."""
return self.call_hf_model(
model_id=model_config.get('model_id', 'NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO'),
system_prompt=system_prompt,
user_prompt=prompt,
temperature=model_config.get('temperature', 0.5),
max_tokens=model_config.get('max_tokens', 1000)
)
def get_deep_thinker_output(self, prompt: str) -> str:
"""Specialized wrapper for Deep Thinker LLM (feasibility analysis)"""
model_config = self.models.get('deep_thinker', {})
system_prompt = """You are a senior research scientist with expertise across physics,
engineering, chemistry, and materials science. Analyze proposals with rigorous scientific
thinking. Be honest about challenges while remaining constructive."""
return self.call_hf_model(
model_id=model_config.get('model_id', 'meta-llama/Meta-Llama-3-70B-Instruct'),
system_prompt=system_prompt,
user_prompt=prompt,
temperature=model_config.get('temperature', 0.3),
max_tokens=model_config.get('max_tokens', 1500)
)
def get_curator_score(self, prompt: str) -> Dict[str, Any]:
"""
Specialized wrapper for Curator LLM (evaluation & scoring)
Forces JSON output for scoring
Args:
prompt: Curator evaluation prompt
Returns:
Dictionary containing scorecard data
"""
model_config = self.models.get('curator', {})
system_prompt = """You are a rigorous innovation evaluator. You assess breakthrough
ideas across multiple dimensions and provide structured scoring. You MUST respond with
valid JSON only, following the exact schema provided in the prompt."""
response_text = self.call_hf_model(
model_id=model_config.get('model_id', 'meta-llama/Meta-Llama-3-70B-Instruct'),
system_prompt=system_prompt,
user_prompt=prompt,
temperature=model_config.get('temperature', 0.2),
max_tokens=model_config.get('max_tokens', 800)
)
# Parse JSON response
try:
# Try to extract JSON from response
response_text = response_text.strip()
# Handle potential markdown code blocks
if response_text.startswith('```'):
# Remove code block markers
lines = response_text.split('\n')
response_text = '\n'.join(lines[1:-1]) if len(lines) > 2 else response_text
scorecard = json.loads(response_text)
logger.info("Successfully parsed curator scorecard")
return scorecard
except json.JSONDecodeError as e:
logger.error(f"Failed to parse curator JSON response: {str(e)}")
logger.error(f"Raw response: {response_text}")
# Return a default scorecard
return {
"originality": 5,
"originality_reasoning": "Failed to parse response",
"feasibility": 5,
"feasibility_reasoning": "Failed to parse response",
"global_impact": 5,
"global_impact_reasoning": "Failed to parse response",
"narrative_coherence": 5,
"narrative_coherence_reasoning": "Failed to parse response",
"reforge_flag": False,
"reforge_reasoning": "Failed to parse curator response",
"overall_assessment": f"Error parsing response: {str(e)}",
"next_steps": "Retry curation step"
}
def run_parallel_dreamers(
self,
prompt: str,
num_dreamers: int = 3
) -> List[str]:
"""
Run multiple dreamer models in parallel (simulated sequential for now)
Args:
prompt: Dream prompt to send to all dreamers
num_dreamers: Number of dreamer outputs to generate
Returns:
List of dream outputs
"""
dreams = []
dreamers = self.models.get('dreamers', [])
for i in range(min(num_dreamers, len(dreamers))):
logger.info(f"Running Dreamer {i + 1}/{num_dreamers}")
try:
dream = self.get_dreamer_output(prompt, model_index=i)
dreams.append(dream)
except Exception as e:
logger.error(f"Dreamer {i + 1} failed: {str(e)}")
dreams.append(f"[Dreamer {i + 1} failed: {str(e)}]")
return dreams
# Convenience function
def create_agent(config_path: str = "config.yaml", hf_token: Optional[str] = None) -> LLMAgent:
"""Create and return a configured LLM Agent"""
return LLMAgent(config_path, hf_token)