← all conversations

Integrating LLM API

2024-11-212 turns29,017 charso1-preview
llm-api-integrationpython-code-reviewfastapi-configuration

Summary

user wants to integrate llm api with existing persona generation code

Messages

Help me use the following code and integrate it with how I use LLM calls in this repo: https://github.com/kliewerdaniel/PersonaGen # api.py from fastapi import FastAPI, File, UploadFile from pydantic import BaseModel from typing import Optional import uvicorn app = FastAPI() # Store persona instance globally (in production, you'd want proper session management) persona_interface = None class ChatRequest(BaseModel): message: str adjust_emotional_variability: Optional[float] = None u/app.post("/initialize") async def initialize_persona(audio_file: UploadFile = File(...)): global persona_interface # Save uploaded file temporarily file_location = f"temp_{audio_file.filename}" with open(file_location, "wb+") as file_object: file_object.write(await audio_file.read()) try: persona_interface = PersonaInterface() persona_interface.initialize_from_audio(file_location) # Clean up temporary file import os os.remove(file_location) return {"status": "success", "message": "Persona initialized successfully"} except Exception as e: return {"status": "error", "message": str(e)} u/app.post("/chat") async def chat(request: ChatRequest): global persona_interface if not persona_interface: return {"status": "error", "message": "Persona not initialized"} try: # Adjust emotional variability if specified if request.adjust_emotional_variability is not None: persona_interface.adjust_emotional_variability(request.adjust_emotional_variability) # Generate response response = persona_interface.chat(request.message) # Get current metrics metrics = persona_interface.get_persona_metrics() return { "status": "success", "response": response, "emotional_state": metrics['emotional_state'], "personality_metrics": metrics['personality'] } except Exception as e: return {"status": "error", "message": str(e)} u/app.get("/metrics") async def get_metrics(): global persona_interface if not persona_interface: return {"status": "error", "message": "Persona not initialized"} return { "status": "success", "metrics": persona_interface.get_persona_metrics() } u/app.post("/save") async def save_persona(filename: str): global persona_interface if not persona_interface: return {"status": "error", "message": "Persona not initialized"} try: persona_interface.save_persona(f"{filename}.json") return {"status": "success", "message": "Persona saved successfully"} except Exception as e: return {"status": "error", "message": str(e)} u/app.post("/load") async def load_persona(filename: str): global persona_interface try: persona_interface = PersonaInterface() persona_interface.load_persona(f"{filename}.json") return {"status": "success", "message": "Persona loaded successfully"} except Exception as e: return {"status": "error", "message": str(e)} if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000) # emotional_core.py import numpy as np from textblob import TextBlob from typing import Dict, List, Tuple class EmotionalCore: def __init__(self, base_emotional_state: Dict[str, float] = None): self.base_emotional_state = base_emotional_state or { 'valence': 0.0, # positive/negative (-1 to 1) 'arousal': 0.0, # energy level (0 to 1) 'dominance': 0.5 # confidence level (0 to 1) } self.emotional_history = [] self.chaos_factor = 0.2 # adjustable chaos/stability parameter def analyze_emotion(self, text: str) -> Dict[str, float]: """Analyze emotional content of text using TextBlob""" analysis = TextBlob(text) # Extract sentiment metrics sentiment = analysis.sentiment return { 'valence': sentiment.polarity, 'arousal': abs(sentiment.polarity) * sentiment.subjectivity, 'dominance': sentiment.subjectivity } def apply_emotional_dynamics(self) -> Dict[str, float]: """Apply chaotic dynamics to emotional state""" current_state = self.base_emotional_state.copy() # Add controlled randomness for dimension in current_state: noise = np.random.normal(0, self.chaos_factor) current_state[dimension] = np.clip( current_state[dimension] + noise, -1.0, 1.0 ) self.emotional_history.append(current_state) return current_state # emotional_memory.py from collections import deque from typing import Dict, List import numpy as np class EmotionalMemory: def __init__(self, memory_size: int = 10): self.memory_size = memory_size self.interaction_history = deque(maxlen=memory_size) self.emotional_trends = { 'valence': deque(maxlen=memory_size), 'arousal': deque(maxlen=memory_size), 'dominance': deque(maxlen=memory_size) } def add_interaction(self, prompt: str, response: str, emotional_state: Dict[str, float]): """Store interaction and emotional state""" self.interaction_history.append({ 'prompt': prompt, 'response': response, 'emotional_state': emotional_state }) for dimension, value in emotional_state.items(): if dimension in self.emotional_trends: self.emotional_trends[dimension].append(value) def get_emotional_context(self) -> Dict[str, float]: """Calculate emotional context based on recent history""" if not self.interaction_history: return None context = {} for dimension in self.emotional_trends: if self.emotional_trends[dimension]: # Calculate weighted average, giving more weight to recent interactions weights = np.exp(np.linspace(-1, 0, len(self.emotional_trends[dimension]))) values = np.array(list(self.emotional_trends[dimension])) context[dimension] = np.average(values, weights=weights) return context def get_relevant_memories(self, prompt: str, k: int = 3) -> List[Dict]: """Retrieve relevant past interactions based on prompt similarity""" if not self.interaction_history: return [] # Simple keyword-based relevance (could be improved with embedding similarity) prompt_words = set(prompt.lower().split()) relevant = [] for interaction in reversed(self.interaction_history): interaction_words = set(interaction['prompt'].lower().split()) similarity = len(prompt_words.intersection(interaction_words)) / len(prompt_words) if similarity > 0.2: # threshold for relevance relevant.append(interaction) if len(relevant) >= k: break return relevant # example_usage.py def main(): # Initialize the persona interface persona = PersonaInterface() # Load from an audio interview try: persona.initialize_from_audio("interview.wav") except Exception as e: print(f"Error loading audio: {e}") return # Example conversation loop print("Persona initialized. Start chatting! (type 'quit' to exit)") while True: user_input = input("You: ").strip() if user_input.lower() == 'quit': break try: response = persona.chat(user_input) print(f"Persona: {response}") # Optional: print current emotional state metrics = persona.get_persona_metrics() print(f"\nCurrent emotional state: {metrics['emotional_state']}") except Exception as e: print(f"Error generating response: {e}") # Save persona state persona.save_persona("persona_state.json") if __name__ == "__main__": main() # persona_generator.py from typing import List, Dict import whisper from emotional_core import EmotionalCore class PersonaGenerator: def __init__(self): self.emotional_core = EmotionalCore() self.transcriber = whisper.load_model("base") self.persona_metrics = {} def transcribe_interview(self, audio_path: str) -> str: """Transcribe audio interview to text""" result = self.transcriber.transcribe(audio_path) return result["text"] def parse_qa_pairs(self, transcript: str) -> List[Dict[str, str]]: """Parse transcript into question-answer pairs""" # This is a simplified version - you'd need more sophisticated parsing qa_pairs = [] segments = transcript.split("\n") for i in range(0, len(segments), 2): if i + 1 < len(segments): qa_pairs.append({ 'question': segments[i], 'answer': segments[i + 1] }) def analyze_qa_pairs(self, qa_pairs: List[Dict[str, str]]) -> Dict: """Analyze Q&A pairs for emotional patterns and personality metrics""" emotional_patterns = [] personality_metrics = { 'openness': 0.0, 'conscientiousness': 0.0, 'extraversion': 0.0, 'agreeableness': 0.0, 'neuroticism': 0.0 } for qa in qa_pairs: # Analyze answer emotions emotion = self.emotional_core.analyze_emotion(qa['answer']) emotional_patterns.append(emotion) # Update personality metrics based on answer content # This is a simplified example - you'd want more sophisticated analysis personality_metrics['openness'] += emotion['valence'] * 0.2 personality_metrics['extraversion'] += emotion['arousal'] * 0.3 personality_metrics['neuroticism'] += (1 - emotion['dominance']) * 0.25 # Normalize personality metrics for metric in personality_metrics: personality_metrics[metric] = np.clip(personality_metrics[metric], 0, 1) return { 'emotional_patterns': emotional_patterns, 'personality_metrics': personality_metrics } def generate_persona(self, audio_path: str) -> Dict: """Generate complete persona from audio interview""" # Transcribe interview transcript = self.transcribe_interview(audio_path) # Parse into Q&A pairs qa_pairs = self.parse_qa_pairs(transcript) # Analyze patterns analysis = self.analyze_qa_pairs(qa_pairs) # Create persona profile persona = { 'qa_database': qa_pairs, 'emotional_baseline': analysis['emotional_patterns'], 'personality_metrics': analysis['personality_metrics'], 'response_style': self.extract_response_style(qa_pairs) } self.persona_metrics = persona return persona def extract_response_style(self, qa_pairs: List[Dict[str, str]]) -> Dict: """Extract linguistic style patterns from responses""" style_metrics = { 'avg_response_length': 0, 'vocabulary_diversity': 0, 'formality_level': 0 } all_words = [] for qa in qa_pairs: words = qa['answer'].split() all_words.extend(words) style_metrics['avg_response_length'] += len(words) style_metrics['avg_response_length'] /= len(qa_pairs) style_metrics['vocabulary_diversity'] = len(set(all_words)) / len(all_words) return style_metrics # persona_interface.py class PersonaInterface: def __init__(self, audio_path: str = None): self.persona_generator = PersonaGenerator() self.emotional_memory = EmotionalMemory() self.response_generator = None if audio_path: self.initialize_from_audio(audio_path) def initialize_from_audio(self, audio_path: str): """Initialize persona from audio interview""" persona_metrics = self.persona_generator.generate_persona(audio_path) self.response_generator = ResponseGenerator(persona_metrics) def chat(self, prompt: str) -> str: """Generate a response to user input""" if not self.response_generator: raise ValueError("Persona not initialized. Please provide an audio interview first.") # Get emotional context emotional_context = self.emotional_memory.get_emotional_context() # Generate response response = self.response_generator.generate_response(prompt, emotional_context) # Store interaction current_emotional_state = self.response_generator.emotional_core.base_emotional_state self.emotional_memory.add_interaction(prompt, response, current_emotional_state) return response def get_persona_metrics(self) -> Dict: """Get current persona metrics and state""" if not self.response_generator: return None return { 'personality': self.persona_generator.persona_metrics['personality_metrics'], 'emotional_state': self.response_generator.emotional_core.base_emotional_state, 'response_style': self.persona_generator.persona_metrics['response_style'], 'interaction_history': len(self.emotional_memory.interaction_history) } def adjust_emotional_variability(self, chaos_factor: float): """Adjust how variable the emotional responses are""" if not 0 <= chaos_factor <= 1: raise ValueError("Chaos factor must be between 0 and 1") self.response_generator.emotional_core.chaos_factor = chaos_factor def save_persona(self, filepath: str): """Save persona state to file""" import json state = { 'persona_metrics': self.persona_generator.persona_metrics, 'emotional_state': self.response_generator.emotional_core.base_emotional_state, 'emotional_history': list(self.emotional_memory.interaction_history) } with open(filepath, 'w') as f: json.dump(state, f) def load_persona(self, filepath: str): """Load persona state from file""" import json with open(filepath, 'r') as f: state = json.load(f) self.persona_generator.persona_metrics = state['persona_metrics'] self.response_generator = ResponseGenerator(state['persona_metrics']) self.response_generator.emotional_core.base_emotional_state = state['emotional_state'] # Restore interaction history for interaction in state['emotional_history']: self.emotional_memory.add_interaction( interaction['prompt'], interaction['response'], interaction['emotional_state'] ) # response_generator.py from typing import Dict import numpy as np from transformers import GPT2LMHeadModel, GPT2Tokenizer class ResponseGenerator: def __init__(self, persona_metrics: Dict): self.persona = persona_metrics self.emotional_core = EmotionalCore( base_emotional_state=self.calculate_emotional_baseline() ) self.model = GPT2LMHeadModel.from_pretrained('gpt2') self.tokenizer = GPT2Tokenizer.from_pretrained('gpt2') def calculate_emotional_baseline(self) -> Dict[str, float]: """Calculate baseline emotional state from persona metrics""" emotional_patterns = self.persona['emotional_baseline'] return { 'valence': np.mean([e['valence'] for e in emotional_patterns]), 'arousal': np.mean([e['arousal'] for e in emotional_patterns]), 'dominance': np.mean([e['dominance'] for e in emotional_patterns]) } def adjust_response_style(self, response: str) -> str: """Adjust response based on persona's linguistic style""" style_metrics = self.persona['response_style'] # Adjust response length target_length = style_metrics['avg_response_length'] current_length = len(response.split()) if current_length > target_length * 1.5: # Truncate response return ' '.join(response.split()[:int(target_length * 1.2)]) elif current_length < target_length * 0.5: # Expand response (you'd want more sophisticated expansion) return response + " " + self.generate_expansion(response) return response def generate_expansion(self, base_response: str) -> str: """Generate additional content to expand a response""" prompt = f"{base_response} Furthermore," inputs = self.tokenizer(prompt, return_tensors="pt") outputs = self.model.generate( inputs['input_ids'], max_length=50, num_return_sequences=1, temperature=0.7 ) expansion = self.tokenizer.decode(outputs[0], skip_special_tokens=True) return expansion.replace(prompt, "") def generate_response(self, prompt: str, context: Dict = None) -> str: """Generate a response based on prompt and emotional state""" # Get current emotional state emotional_state = self.emotional_core.apply_emotional_dynamics() # Prepare prompt with emotional context emotional_prompt = self.prepare_emotional_prompt(prompt, emotional_state) # Generate base response inputs = self.tokenizer(emotional_prompt, return_tensors="pt") outputs = self.model.generate( inputs['input_ids'], max_length=100, num_return_sequences=1, temperature=0.8, top_p=0.9 ) response = self.tokenizer.decode(outputs[0], skip_special_tokens=True) # Apply style adjustments response = self.adjust_response_style(response) return response def prepare_emotional_prompt(self, prompt: str, emotional_state: Dict[str, float]) -> str: """Prepare prompt with emotional context""" emotion_descriptor = self.emotion_to_text(emotional_state) return f"[{emotion_descriptor}] {prompt}" def emotion_to_text(self, emotional_state: Dict[str, float]) -> str: """Convert emotional state to text description""" valence = emotional_state['valence'] arousal = emotional_state['arousal'] if valence > 0.3: if arousal > 0.5: return "enthusiastically" else: return "contentedly" elif valence < -0.3: if arousal > 0.5: return "angrily" else: return "sadly" else: if arousal > 0.5: return "energetically" else: return "calmly"
To help you integrate the provided code with how you use LLM calls in your repository, we'll focus on modifying the parts of the code where the language model is utilized. This primarily involves replacing the current GPT-2 model with your preferred LLM API calls, ensuring seamless integration with your existing workflow. --- ### **1. Identify Where LLM Calls Are Made** The main interactions with the language model occur in the `ResponseGenerator` class within the `response_generator.py` file. Here's the relevant section: ```python # response_generator.py from transformers import GPT2LMHeadModel, GPT2Tokenizer class ResponseGenerator: def __init__(self, persona_metrics: Dict): # ... initialization code ... self.model = GPT2LMHeadModel.from_pretrained('gpt2') self.tokenizer = GPT2Tokenizer.from_pretrained('gpt2') def generate_response(self, prompt: str, context: Dict = None) -> str: # ... prepare prompt ... inputs = self.tokenizer(emotional_prompt, return_tensors="pt") outputs = self.model.generate( inputs['input_ids'], max_length=100, num_return_sequences=1, temperature=0.8, top_p=0.9 ) response = self.tokenizer.decode(outputs[0], skip_special_tokens=True) # ... adjust response style ... return response ``` --- ### **2. Replace GPT-2 with Your Preferred LLM API** Assuming you're using OpenAI's API (e.g., GPT-3, GPT-4), you can modify the `ResponseGenerator` class to replace the GPT-2 model with API calls to your preferred LLM. #### **Steps:** - **Remove the GPT-2 Model Dependencies:** ```python # Remove these imports from transformers import GPT2LMHeadModel, GPT2Tokenizer ``` - **Import Your LLM API Client:** ```python import openai # For OpenAI's API ``` - **Set Up the API Key:** ```python openai.api_key = 'YOUR_OPENAI_API_KEY' ``` - **Modify the `generate_response` Method:** Replace the code that uses the GPT-2 model with API calls to your LLM. --- ### **3. Implement the Modified `ResponseGenerator`** Here's how the updated `ResponseGenerator` class might look: ```python # response_generator.py import openai from typing import Dict class ResponseGenerator: def __init__(self, persona_metrics: Dict): self.persona = persona_metrics self.emotional_core = EmotionalCore( base_emotional_state=self.calculate_emotional_baseline() ) openai.api_key = 'YOUR_OPENAI_API_KEY' # Replace with your actual API key def calculate_emotional_baseline(self) -> Dict[str, float]: # ... existing code ... pass def adjust_response_style(self, response: str) -> str: # ... existing code ... pass def generate_response(self, prompt: str, context: Dict = None) -> str: # Get current emotional state emotional_state = self.emotional_core.apply_emotional_dynamics() # Prepare prompt with emotional context emotional_prompt = self.prepare_emotional_prompt(prompt, emotional_state) # Use OpenAI's API to generate the response try: response = openai.Completion.create( engine='text-davinci-003', # Choose the appropriate engine prompt=emotional_prompt, max_tokens=150, temperature=0.8, top_p=0.9, n=1, stop=None, ) response_text = response.choices[0].text.strip() except Exception as e: # Handle exceptions (e.g., API errors) response_text = "I'm sorry, but I can't provide a response right now." # Apply style adjustments response_text = self.adjust_response_style(response_text) return response_text def prepare_emotional_prompt(self, prompt: str, emotional_state: Dict[str, float]) -> str: # Convert emotional state to descriptive text emotion_descriptor = self.emotion_to_text(emotional_state) # Incorporate emotional context into the prompt emotional_prompt = f"You are a persona who responds {emotion_descriptor}.\nUser: {prompt}\nPersona:" return emotional_prompt def emotion_to_text(self, emotional_state: Dict[str, float]) -> str: # Map emotional state to textual descriptions valence = emotional_state['valence'] arousal = emotional_state['arousal'] if valence > 0.3: if arousal > 0.5: return "in an enthusiastic and joyful manner" else: return "in a calm and happy manner" elif valence < -0.3: if arousal > 0.5: return "in an angry and agitated manner" else: return "in a sad and subdued manner" else: if arousal > 0.5: return "in a neutral yet energetic manner" else: return "in a neutral and calm manner" ``` --- ### **4. Update the Prompt Engineering** Ensure that the prompts sent to your LLM are well-crafted to elicit responses that align with your persona's characteristics. #### **Tips:** - **Provide Clear Instructions:** Include descriptors in the prompt that guide the LLM on the desired response style. - **Use System Messages (If Supported):** For models like `gpt-3.5-turbo`, you can use system messages to set the assistant's behavior. --- ### **5. Adjust Other Components if Necessary** Check other parts of the code to see if they interact with the language model. For example, the `PersonaGenerator` class may also need adjustments if it uses the LLM for analysis or generation. --- ### **6. Ensure Compatibility with Your LLM's API** Verify that the parameters and usage align with your LLM's API. For OpenAI's API, refer to the [official documentation](https://beta.openai.com/docs/api-reference/introduction) for details on parameters like `engine`, `temperature`, and `max_tokens`. --- ### **7. Handle API Exceptions and Errors** Implement error handling to manage exceptions that may occur during API calls, such as rate limits or connectivity issues. ```python try: # API call pass except openai.error.RateLimitError: # Handle rate limit error pass except openai.error.APIError as e: # Handle general API errors pass ``` --- ### **8. Update Dependency Management** Since you're removing the `transformers` library and adding the OpenAI library, update your `requirements.txt`: ```plaintext # requirements.txt openai numpy textblob fastapi uvicorn whisper # ... other dependencies ... ``` --- ### **9. Test the Integrated System** After making the changes, thoroughly test the system to ensure that: - Responses are generated correctly. - Emotional context influences the outputs as expected. - There are no runtime errors or issues. --- ### **10. Consider API Usage and Costs** Be mindful of the API usage limits and associated costs with your LLM. Implement features like: - **Caching Responses:** Cache frequent or repetitive responses to minimize API calls. - **User Input Validation:** Validate and sanitize user inputs to prevent unnecessary API usage. --- ### **Example of the Modified `persona_interface.py`** Ensure that the `PersonaInterface` class uses the updated `ResponseGenerator`. ```python # persona_interface.py class PersonaInterface: def __init__(self, audio_path: str = None): self.persona_generator = PersonaGenerator() self.emotional_memory = EmotionalMemory() self.response_generator = None if audio_path: self.initialize_from_audio(audio_path) def initialize_from_audio(self, audio_path: str): """Initialize persona from audio interview""" persona_metrics = self.persona_generator.generate_persona(audio_path) self.response_generator = ResponseGenerator(persona_metrics) def chat(self, prompt: str) -> str: """Generate a response to user input""" if not self.response_generator: raise ValueError("Persona not initialized. Please provide an audio interview first.") # Get emotional context emotional_context = self.emotional_memory.get_emotional_context() # Generate response response = self.response_generator.generate_response(prompt, emotional_context) # Store interaction current_emotional_state = self.response_generator.emotional_core.base_emotional_state self.emotional_memory.add_interaction(prompt, response, current_emotional_state) return response # ... rest of the class remains the same ... ``` --- ### **Additional Considerations** - **Security:** - Never hardcode API keys in the code. Use environment variables or configuration files. - **Environment Variables:** ```python import os openai.api_key = os.getenv('OPENAI_API_KEY') ``` - **Logging:** Implement logging to monitor interactions and debug issues. - **Compliance:** Ensure that your use of the LLM complies with the provider's policies and terms of service. --- ### **Conclusion** By replacing the GPT-2 model with your LLM API calls, you can integrate the provided code seamlessly with your existing LLM usage in the repository. The key steps involve modifying the `ResponseGenerator` class, adjusting prompts, handling API responses, and testing the updated system. --- ### **Next Steps** - **Test the Updated Code:** Run unit tests and simulate conversations to validate the behavior. - **Optimize Prompt Engineering:** Experiment with different prompt structures to achieve the desired response styles. - **Monitor and Iterate:** Collect feedback and iterate on the system to improve its performance and user experience. If you encounter any issues during the integration or need further assistance with specific parts of the code, feel free to ask!
o1-preview