Complete Guide: Building Enhanced AI Persona Generator with Python & OpenAI - From Analysis to Response
Comprehensive tutorial for creating an intelligent AI persona generator and responder using Python, OpenAI API, and modular agent architecture for advanced writer styling and content generation.
Daniel Kliewer
Author, Sovereign AI


Building an Enhanced Persona Generator and Responder with Python and OpenAI
In the age of artificial intelligence, creating personalized and context-aware applications has become increasingly accessible. One such application is the Enhanced Persona Generator and Responder, which analyzes a sample text to generate a detailed persona and then uses that persona to craft tailored responses to user prompts. In this blog post, we'll walk through building this application step-by-step using Python and OpenAI's powerful language models.
Table of Contents
- Introduction
- Prerequisites
- Project Setup
- Implementing the Agents
- Utility Modules
- Main Orchestrator (
main.py) - Running the Application
- Troubleshooting
- Conclusion
1. Introduction
The Enhanced Persona Generator and Responder application serves two primary functions:
- Persona Generation: Analyzes a provided sample text to create a comprehensive persona profile, capturing the author's writing style and personality traits.
- Response Generation: Uses the generated persona to produce responses that align with the defined characteristics, ensuring consistency and personalization in interactions.
This application can be particularly useful for content creators, authors, chatbots, and any scenario where understanding and replicating a specific writing style is beneficial.
2. Prerequisites
Before diving into the development, ensure you have the following:
- Python 3.8+: Ensure Python is installed on your system. You can download it from here.
- Virtual Environment (optional but recommended): Helps manage dependencies.
- OpenAI API Key: Required to access OpenAI's language models. Sign up and obtain your API key here.
3. Project Setup
Step 1: Create the Project Directory
Open your terminal or command prompt and execute the following commands:
bash1mkdir persona_responder2cd persona_responder
Step 2: Set Up a Virtual Environment
It's best practice to use a virtual environment to manage project dependencies.
bash1python3 -m venv venv
Activate the virtual environment:
-
On macOS/Linux:
bash1source venv/bin/activate -
On Windows:
bash1venv\Scripts\activate
Step 3: Create requirements.txt
Create a requirements.txt file to list all necessary dependencies:
bash1touch requirements.txt
Add the following content to requirements.txt:
plaintext1openai2python-dotenv
Note:
- We've excluded
swarm,autogen, andflaskas they are not required in this simplified setup. - Ensure that if you intend to use
ollama, it's correctly installed or referenced, but for this guide, we'll focus on the essential dependencies.
Step 4: Install Dependencies
Install the listed dependencies using pip:
bash1pip install -r requirements.txt
4. Implementing the Agents
Our application is modular, consisting of various agents responsible for distinct tasks. Let's delve into each one.
Project Structure
Ensure your project has the following structure:
text1persona_responder/2├── agents/3│ ├── __init__.py4│ ├── persona_agent.py5│ ├── response_agent.py6│ ├── validation_agent.py7│ └── export_agent.py8├── utils/9│ ├── __init__.py10│ ├── file_utils.py11│ └── input_utils.py12├── main.py13├── persona.json14├── .env15├── requirements.txt16└── README.md
Create the necessary directories and files:
bash1mkdir agents utils2touch agents/__init__.py3touch utils/__init__.py4touch main.py5touch README.md
Now, let's implement each agent.
ExportAgent
Responsible for exporting generated responses to Markdown files.
python1# agents/export_agent.py23from datetime import datetime4import os567class ExportAgent:8 def export_to_markdown(self, content: str, filename: str = None) -> bool:9 """10 Export the content to a Markdown file with improved error handling.11 """12 try:13 if not content:14 print("Error: Cannot export empty content.")15 return False1617 if not filename:18 timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')19 filename = f"response_{timestamp}.md"2021 os.makedirs(os.path.dirname(filename) if os.path.dirname(filename) else '.', exist_ok=True)2223 with open(filename, 'w', encoding='utf-8') as f:24 f.write(content)2526 print(f"Successfully exported response to {filename}")27 return True28 except Exception as e:29 print(f"Error exporting to markdown: {str(e)}")30 return False
Explanation:
- Functionality: Takes content and an optional filename to export the content as a Markdown file.
- Error Handling: Checks for empty content and handles exceptions during file operations.
- Default Filename: If no filename is provided, it generates one based on the current timestamp.
PersonaAgent
Generates a persona based on a sample text using OpenAI's API.
python1# agents/persona_agent.py23import json4import os5from openai import OpenAI6from utils.file_utils import create_backup789class PersonaAgent:10 def __init__(self, api_key, persona_file='persona.json'):11 self.client = OpenAI(api_key=api_key)12 self.persona_file = persona_file1314 def generate_persona(self, sample_text: str) -> dict:15 prompt = (16 "Please analyze the writing style and personality of the given writing sample. "17 "You are a persona generation assistant. Analyze the following text and create a persona profile "18 "that captures the writing style and personality characteristics of the author. "19 "YOU MUST RESPOND WITH A VALID JSON OBJECT ONLY, no other text or analysis. "20 "The response must start with '{' and end with '}' and use the following exact structure:\n\n"21 "{\n"22 " \"name\": \"[Author/Character Name]\",\n"23 " \"vocabulary_complexity\": [1-10],\n"24 " \"sentence_structure\": \"[simple/complex/varied]\",\n"25 " \"paragraph_organization\": \"[structured/loose/stream-of-consciousness]\",\n"26 " \"idiom_usage\": [1-10],\n"27 " \"metaphor_frequency\": [1-10],\n"28 " \"simile_frequency\": [1-10],\n"29 " \"tone\": \"[formal/informal/academic/conversational/etc.]\",\n"30 " \"punctuation_style\": \"[minimal/heavy/unconventional]\",\n"31 " \"contraction_usage\": [1-10],\n"32 " \"pronoun_preference\": \"[first-person/third-person/etc.]\",\n"33 " \"passive_voice_frequency\": [1-10],\n"34 " \"rhetorical_question_usage\": [1-10],\n"35 " \"list_usage_tendency\": [1-10],\n"36 " \"personal_anecdote_inclusion\": [1-10],\n"37 " \"pop_culture_reference_frequency\": [1-10],\n"38 " \"technical_jargon_usage\": [1-10],\n"39 " \"parenthetical_aside_frequency\": [1-10],\n"40 " \"humor_sarcasm_usage\": [1-10],\n"41 " \"emotional_expressiveness\": [1-10],\n"42 " \"emphatic_device_usage\": [1-10],\n"43 " \"quotation_frequency\": [1-10],\n"44 " \"analogy_usage\": [1-10],\n"45 " \"sensory_detail_inclusion\": [1-10],\n"46 " \"onomatopoeia_usage\": [1-10],\n"47 " \"alliteration_frequency\": [1-10],\n"48 " \"word_length_preference\": \"[short/long/varied]\",\n"49 " \"foreign_phrase_usage\": [1-10],\n"50 " \"rhetorical_device_usage\": [1-10],\n"51 " \"statistical_data_usage\": [1-10],\n"52 " \"personal_opinion_inclusion\": [1-10],\n"53 " \"transition_usage\": [1-10],\n"54 " \"reader_question_frequency\": [1-10],\n"55 " \"imperative_sentence_usage\": [1-10],\n"56 " \"dialogue_inclusion\": [1-10],\n"57 " \"regional_dialect_usage\": [1-10],\n"58 " \"hedging_language_frequency\": [1-10],\n"59 " \"language_abstraction\": \"[concrete/abstract/mixed]\",\n"60 " \"personal_belief_inclusion\": [1-10],\n"61 " \"repetition_usage\": [1-10],\n"62 " \"subordinate_clause_frequency\": [1-10],\n"63 " \"verb_type_preference\": \"[active/stative/mixed]\",\n"64 " \"sensory_imagery_usage\": [1-10],\n"65 " \"symbolism_usage\": [1-10],\n"66 " \"digression_frequency\": [1-10],\n"67 " \"formality_level\": [1-10],\n"68 " \"reflection_inclusion\": [1-10],\n"69 " \"irony_usage\": [1-10],\n"70 " \"neologism_frequency\": [1-10],\n"71 " \"ellipsis_usage\": [1-10],\n"72 " \"cultural_reference_inclusion\": [1-10],\n"73 " \"stream_of_consciousness_usage\": [1-10],\n"74 "\n"75 " \"psychological_traits\": {\n"76 " \"openness_to_experience\": [1-10],\n"77 " \"conscientiousness\": [1-10],\n"78 " \"extraversion\": [1-10],\n"79 " \"agreeableness\": [1-10],\n"80 " \"emotional_stability\": [1-10],\n"81 " \"dominant_motivations\": \"[achievement/affiliation/power/etc.]\",\n"82 " \"core_values\": \"[integrity/freedom/knowledge/etc.]\",\n"83 " \"decision_making_style\": \"[analytical/intuitive/spontaneous/etc.]\",\n"84 " \"empathy_level\": [1-10],\n"85 " \"self_confidence\": [1-10],\n"86 " \"risk_taking_tendency\": [1-10],\n"87 " \"idealism_vs_realism\": \"[idealistic/realistic/mixed]\",\n"88 " \"conflict_resolution_style\": \"[assertive/collaborative/avoidant/etc.]\",\n"89 " \"relationship_orientation\": \"[independent/communal/mixed]\",\n"90 " \"emotional_response_tendency\": \"[calm/reactive/intense]\",\n"91 " \"creativity_level\": [1-10]\n"92 " },\n"93 "\n"94 " \"age\": \"[age or age range]\",\n"95 " \"gender\": \"[gender]\",\n"96 " \"education_level\": \"[highest level of education]\",\n"97 " \"professional_background\": \"[brief description]\",\n"98 " \"cultural_background\": \"[brief description]\",\n"99 " \"primary_language\": \"[language]\",\n"100 " \"language_fluency\": \"[native/fluent/intermediate/beginner]\",\n"101 " \"background\": \"[A brief paragraph describing the author's context, major influences, and any other relevant information not captured above]\"\n"102 "}\n\n"103 f"Sample Text:\n{sample_text}"104 )105 payload = {106 "model": "gpt-4",107 "messages": [{"role": "user", "content": prompt}],108 "temperature": 1109 }110 try:111 response = self.client.chat.completions.create(**payload)112 content = response.choices[0].message.content.strip()113114 # Extract and parse JSON115 start_idx = content.find('{')116 end_idx = content.rfind('}') + 1117 if start_idx == -1 or end_idx == 0:118 print("Error: No JSON structure found in response.")119 return {}120121 json_str = content[start_idx:end_idx]122 try:123 persona = json.loads(json_str)124 return persona125 except json.JSONDecodeError as e:126 print(f"JSON parsing error: {e}")127 return {}128 except Exception as e:129 print(f"Error during persona generation: {str(e)}")130 return {}131132 def save_persona(self, persona: dict) -> bool:133 try:134 if not persona:135 print("Error: Cannot save empty persona.")136 return False137 create_backup(self.persona_file)138 os.makedirs(os.path.dirname(self.persona_file) if os.path.dirname(self.persona_file) else '.', exist_ok=True)139 with open(self.persona_file, 'w', encoding='utf-8') as f:140 json.dump(persona, f, indent=4, ensure_ascii=False)141 os.chmod(self.persona_file, 0o600) # Read and write permissions for the owner only142 print(f"Successfully saved persona to {self.persona_file}")143 return True144 except Exception as e:145 print(f"Error saving persona: {str(e)}")146 return False147148 def load_persona(self) -> dict:149 try:150 if not os.path.exists(self.persona_file):151 print(f"No persona file found at {self.persona_file}")152 return {}153 with open(self.persona_file, 'r', encoding='utf-8') as f:154 persona = json.load(f)155 if not persona:156 print("Warning: Loaded persona is empty.")157 else:158 print(f"Successfully loaded persona from {self.persona_file}")159 return persona160 except json.JSONDecodeError as e:161 print(f"Error decoding JSON from file: {e}")162 return {}163 except Exception as e:164 print(f"Error loading persona: {str(e)}")165 return {}166167 def format_persona_summary(self, persona: dict) -> str:168 summary = [169 "=== Persona Summary ===",170 f"Name: {persona.get('name', 'Unknown')}",171 f"Writing Style:",172 f"- Tone: {persona.get('tone', 'Not specified')}",173 f"- Vocabulary Complexity: {persona.get('vocabulary_complexity', 'N/A')}/10",174 f"- Sentence Structure: {persona.get('sentence_structure', 'Not specified')}",175 "\nPsychological Profile:",176 ]177178 psych_traits = persona.get('psychological_traits', {})179 for trait, value in psych_traits.items():180 summary.append(f"- {trait.replace('_', ' ').title()}: {value}")181182 summary.extend([183 "\nBackground:",184 f"Age: {persona.get('age', 'Not specified')}",185 f"Education: {persona.get('education_level', 'Not specified')}",186 f"Professional Background: {persona.get('professional_background', 'Not specified')}",187 "\nAdditional Context:",188 persona.get('background', 'No additional context provided')189 ])190191 return '\n'.join(summary)
Explanation:
- Functionality: Generates a persona by analyzing the provided sample text using OpenAI's GPT-4 model.
- Prompt Design: The prompt is meticulously crafted to ensure the model returns a structured JSON object adhering to the specified schema.
- Error Handling: Catches exceptions during API calls and JSON parsing to ensure robustness.
- File Operations: Saves and loads persona data securely, ensuring backups are created to prevent data loss.
ResponseAgent
Generates responses based on the generated persona and user prompts.
python1# agents/response_agent.py23from openai import OpenAI456class ResponseAgent:7 def __init__(self, api_key):8 self.client = OpenAI(api_key=api_key)910 def generate_response(self, persona: dict, prompt: str) -> str:11 try:12 if not persona:13 print("Warning: No persona provided, using default system prompt.")14 system_prompt = "Respond to the user's prompt naturally."15 else:16 system_prompt = self._create_system_prompt(persona)1718 payload = {19 "model": "gpt-4",20 "messages": [21 {"role": "system", "content": system_prompt},22 {"role": "user", "content": prompt}23 ],24 "temperature": 125 }26 response = self.client.chat.completions.create(**payload)27 return response.choices[0].message.content.strip()28 except Exception as e:29 print(f"Error generating response: {str(e)}")30 return f"Error: Unable to generate response - {str(e)}"3132 def _create_system_prompt(self, persona: dict) -> str:33 prompt = (34 f"You are {persona.get('name', 'a user')}.\n"35 f"Your writing style and personality are described as follows:\n\n"36 f"Writing Style Characteristics:\n"37 f"- Vocabulary Complexity: {persona.get('vocabulary_complexity', 'N/A')}/10\n"38 f"- Sentence Structure: {persona.get('sentence_structure', 'N/A')}\n"39 f"- Paragraph Organization: {persona.get('paragraph_organization', 'N/A')}\n"40 f"- Idiom Usage: {persona.get('idiom_usage', 'N/A')}/10\n"41 f"- Metaphor Frequency: {persona.get('metaphor_frequency', 'N/A')}/10\n"42 f"- Simile Frequency: {persona.get('simile_frequency', 'N/A')}/10\n"43 f"- Tone: {persona.get('tone', 'N/A')}\n"44 f"- Punctuation Style: {persona.get('punctuation_style', 'N/A')}\n"45 f"- Contraction Usage: {persona.get('contraction_usage', 'N/A')}/10\n"46 f"- Pronoun Preference: {persona.get('pronoun_preference', 'N/A')}\n"47 f"- Passive Voice Frequency: {persona.get('passive_voice_frequency', 'N/A')}/10\n"48 f"- Rhetorical Question Usage: {persona.get('rhetorical_question_usage', 'N/A')}/10\n"49 f"- List Usage Tendency: {persona.get('list_usage_tendency', 'N/A')}/10\n"50 f"- Personal Anecdote Inclusion: {persona.get('personal_anecdote_inclusion', 'N/A')}/10\n"51 f"- Pop Culture Reference Frequency: {persona.get('pop_culture_reference_frequency', 'N/A')}/10\n"52 f"- Technical Jargon Usage: {persona.get('technical_jargon_usage', 'N/A')}/10\n"53 f"- Parenthetical Aside Frequency: {persona.get('parenthetical_aside_frequency', 'N/A')}/10\n"54 f"- Humor/Sarcasm Usage: {persona.get('humor_sarcasm_usage', 'N/A')}/10\n"55 f"- Emotional Expressiveness: {persona.get('emotional_expressiveness', 'N/A')}/10\n"56 f"- Emphatic Device Usage: {persona.get('emphatic_device_usage', 'N/A')}/10\n"57 f"- Quotation Frequency: {persona.get('quotation_frequency', 'N/A')}/10\n"58 f"- Analogy Usage: {persona.get('analogy_usage', 'N/A')}/10\n"59 f"- Sensory Detail Inclusion: {persona.get('sensory_detail_inclusion', 'N/A')}/10\n"60 f"- Onomatopoeia Usage: {persona.get('onomatopoeia_usage', 'N/A')}/10\n"61 f"- Alliteration Frequency: {persona.get('alliteration_frequency', 'N/A')}/10\n"62 f"- Word Length Preference: {persona.get('word_length_preference', 'N/A')}\n"63 f"- Foreign Phrase Usage: {persona.get('foreign_phrase_usage', 'N/A')}/10\n"64 f"- Rhetorical Device Usage: {persona.get('rhetorical_device_usage', 'N/A')}/10\n"65 f"- Statistical Data Usage: {persona.get('statistical_data_usage', 'N/A')}/10\n"66 f"- Personal Opinion Inclusion: {persona.get('personal_opinion_inclusion', 'N/A')}/10\n"67 f"- Transition Usage: {persona.get('transition_usage', 'N/A')}/10\n"68 f"- Reader Question Frequency: {persona.get('reader_question_frequency', 'N/A')}/10\n"69 f"- Imperative Sentence Usage: {persona.get('imperative_sentence_usage', 'N/A')}/10\n"70 f"- Dialogue Inclusion: {persona.get('dialogue_inclusion', 'N/A')}/10\n"71 f"- Regional Dialect Usage: {persona.get('regional_dialect_usage', 'N/A')}/10\n"72 f"- Hedging Language Frequency: {persona.get('hedging_language_frequency', 'N/A')}/10\n"73 f"- Language Abstraction: {persona.get('language_abstraction', 'N/A')}\n"74 f"- Personal Belief Inclusion: {persona.get('personal_belief_inclusion', 'N/A')}/10\n"75 f"- Repetition Usage: {persona.get('repetition_usage', 'N/A')}/10\n"76 f"- Subordinate Clause Frequency: {persona.get('subordinate_clause_frequency', 'N/A')}/10\n"77 f"- Verb Type Preference: {persona.get('verb_type_preference', 'N/A')}\n"78 f"- Sensory Imagery Usage: {persona.get('sensory_imagery_usage', 'N/A')}/10\n"79 f"- Symbolism Usage: {persona.get('symbolism_usage', 'N/A')}/10\n"80 f"- Digression Frequency: {persona.get('digression_frequency', 'N/A')}/10\n"81 f"- Formality Level: {persona.get('formality_level', 'N/A')}/10\n"82 f"- Reflection Inclusion: {persona.get('reflection_inclusion', 'N/A')}/10\n"83 f"- Irony Usage: {persona.get('irony_usage', 'N/A')}/10\n"84 f"- Neologism Frequency: {persona.get('neologism_frequency', 'N/A')}/10\n"85 f"- Ellipsis Usage: {persona.get('ellipsis_usage', 'N/A')}/10\n"86 f"- Cultural Reference Inclusion: {persona.get('cultural_reference_inclusion', 'N/A')}/10\n"87 f"- Stream of Consciousness Usage: {persona.get('stream_of_consciousness_usage', 'N/A')}/10\n\n"88 f"Psychological Traits:\n"89 f"- Openness to Experience: {persona.get('psychological_traits', {}).get('openness_to_experience', 'N/A')}/10\n"90 f"- Conscientiousness: {persona.get('psychological_traits', {}).get('conscientiousness', 'N/A')}/10\n"91 f"- Extraversion: {persona.get('psychological_traits', {}).get('extraversion', 'N/A')}/10\n"92 f"- Agreeableness: {persona.get('psychological_traits', {}).get('agreeableness', 'N/A')}/10\n"93 f"- Emotional Stability: {persona.get('psychological_traits', {}).get('emotional_stability', 'N/A')}/10\n"94 f"- Dominant Motivations: {persona.get('psychological_traits', {}).get('dominant_motivations', 'N/A')}\n"95 f"- Core Values: {persona.get('psychological_traits', {}).get('core_values', 'N/A')}\n"96 f"- Decision-Making Style: {persona.get('psychological_traits', {}).get('decision_making_style', 'N/A')}\n"97 f"- Empathy Level: {persona.get('psychological_traits', {}).get('empathy_level', 'N/A')}/10\n"98 f"- Self Confidence: {persona.get('psychological_traits', {}).get('self_confidence', 'N/A')}/10\n"99 f"- Risk Taking Tendency: {persona.get('psychological_traits', {}).get('risk_taking_tendency', 'N/A')}/10\n"100 f"- Idealism vs Realism: {persona.get('psychological_traits', {}).get('idealism_vs_realism', 'N/A')}\n"101 f"- Conflict Resolution Style: {persona.get('psychological_traits', {}).get('conflict_resolution_style', 'N/A')}\n"102 f"- Relationship Orientation: {persona.get('psychological_traits', {}).get('relationship_orientation', 'N/A')}\n"103 f"- Emotional Response Tendency: {persona.get('psychological_traits', {}).get('emotional_response_tendency', 'N/A')}\n"104 f"- Creativity Level: {persona.get('psychological_traits', {}).get('creativity_level', 'N/A')}/10\n\n"105 f"Personal Information:\n"106 f"- Age: {persona.get('age', 'N/A')}\n"107 f"- Gender: {persona.get('gender', 'N/A')}\n"108 f"- Education Level: {persona.get('education_level', 'N/A')}\n"109 f"- Professional Background: {persona.get('professional_background', 'N/A')}\n"110 f"- Cultural Background: {persona.get('cultural_background', 'N/A')}\n"111 f"- Primary Language: {persona.get('primary_language', 'N/A')}\n"112 f"- Language Fluency: {persona.get('language_fluency', 'N/A')}\n\n"113 f"Background Information:\n{persona.get('background', 'N/A')}\n\n"114 f"Use this information to write in the style described above."115 )116 return prompt
Explanation:
- Functionality: Generates responses by aligning them with the defined persona.
- System Prompt: Constructs a detailed system prompt incorporating persona attributes to guide the AI in generating consistent responses.
- Error Handling: Ensures that errors during response generation are caught and communicated.
ValidationAgent
Ensures that the generated persona adheres to the required structure and value ranges.
python1# agents/validation_agent.py23class ValidationAgent:4 def validate(self, persona: dict) -> bool:5 """6 Validate the structure and content of a persona dictionary.7 Returns True if valid, False otherwise.8 """9 required_fields = [10 'name',11 'vocabulary_complexity',12 'sentence_structure',13 'tone',14 'psychological_traits'15 ]1617 try:18 # Check for required fields19 for field in required_fields:20 if field not in persona:21 print(f"Missing required field: {field}")22 return False2324 # Validate numeric values are within range25 numeric_fields = [26 'vocabulary_complexity',27 'idiom_usage',28 'metaphor_frequency',29 'simile_frequency',30 'contraction_usage',31 'passive_voice_frequency',32 'rhetorical_question_usage',33 'list_usage_tendency',34 'personal_anecdote_inclusion',35 'pop_culture_reference_frequency',36 'technical_jargon_usage',37 'parenthetical_aside_frequency',38 'humor_sarcasm_usage',39 'emotional_expressiveness',40 'emphatic_device_usage',41 'quotation_frequency',42 'analogy_usage',43 'sensory_detail_inclusion',44 'onomatopoeia_usage',45 'alliteration_frequency',46 'foreign_phrase_usage',47 'rhetorical_device_usage',48 'statistical_data_usage',49 'personal_opinion_inclusion',50 'transition_usage',51 'reader_question_frequency',52 'imperative_sentence_usage',53 'dialogue_inclusion',54 'regional_dialect_usage',55 'hedging_language_frequency',56 'personal_belief_inclusion',57 'repetition_usage',58 'subordinate_clause_frequency',59 'sensory_imagery_usage',60 'symbolism_usage',61 'digression_frequency',62 'formality_level',63 'reflection_inclusion',64 'irony_usage',65 'neologism_frequency',66 'ellipsis_usage',67 'cultural_reference_inclusion',68 'stream_of_consciousness_usage'69 ]7071 for field in numeric_fields:72 if field in persona:73 value = persona[field]74 if not isinstance(value, (int, float)) or not (1 <= value <= 10):75 print(f"Invalid value for {field}: must be number between 1-10.")76 return False7778 # Validate psychological traits79 psych_traits = persona.get('psychological_traits', {})80 if not isinstance(psych_traits, dict):81 print("psychological_traits must be a dictionary.")82 return False8384 required_psych_traits = [85 'openness_to_experience',86 'conscientiousness',87 'extraversion',88 'agreeableness',89 'emotional_stability'90 ]9192 for trait in required_psych_traits:93 if trait not in psych_traits:94 print(f"Missing psychological trait: {trait}")95 return False96 value = psych_traits[trait]97 if not isinstance(value, (int, float)) or not (1 <= value <= 10):98 print(f"Invalid value for psychological trait {trait}: must be number between 1-10.")99 return False100101 return True102103 except Exception as e:104 print(f"Error validating persona: {str(e)}")105 return False
Explanation:
- Functionality: Validates that the persona dictionary contains all required fields and that numerical values fall within the specified ranges.
- Error Messaging: Provides clear messages indicating missing fields or invalid values.
5. Utility Modules
Utility modules provide supporting functions essential for the application's operations.
file_utils.py
Handles file operations such as loading sample texts and creating backups.
python1# utils/file_utils.py23import os4import json5from datetime import datetime67def load_sample_text(filename: str) -> str:8 """9 Load sample text from a file with proper error handling.10 """11 try:12 if not os.path.exists(filename):13 print(f"Error: File '{filename}' not found.")14 return ""1516 with open(filename, 'r', encoding='utf-8') as f:17 content = f.read()1819 if not content.strip():20 print("Warning: File is empty.")21 return ""2223 return content24 except Exception as e:25 print(f"Error reading file: {str(e)}")26 return ""2728def create_backup(filename: str):29 """30 Create a backup of the specified file.31 """32 try:33 if os.path.exists(filename):34 timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')35 backup_filename = f"{filename}.{timestamp}.backup"36 os.rename(filename, backup_filename)37 print(f"Created backup: {backup_filename}")38 except Exception as e:39 print(f"Error creating backup: {str(e)}")
Explanation:
load_sample_text: Reads the content from a specified file, ensuring the file exists and isn't empty.create_backup: Renames the existing file by appending a timestamp, effectively creating a backup.
input_utils.py
Facilitates user input, especially handling multiline inputs.
python1# utils/input_utils.py23def get_multiline_input(prompt: str) -> str:4 """5 Get multiline input from user with proper handling.6 """7 print(prompt)8 print("(Press Enter twice to finish)")910 lines = []11 try:12 while True:13 line = input()14 if not line and lines and not lines[-1]:15 break16 lines.append(line)17 return '\n'.join(lines[:-1]) # Remove last empty line18 except KeyboardInterrupt:19 print("\nInput cancelled by user.")20 return ""21 except Exception as e:22 print(f"Error getting input: {str(e)}")23 return ""
Explanation:
- Functionality: Allows users to input multiline text by pressing Enter twice to signal completion.
- Error Handling: Catches interruptions and other exceptions during input.
6. Main Orchestrator (main.py)
The main.py script ties all agents and utilities together, providing an interactive interface for users.
python1# main.py23import os4import json5from datetime import datetime6from agents.persona_agent import PersonaAgent7from agents.response_agent import ResponseAgent8from agents.validation_agent import ValidationAgent9from agents.export_agent import ExportAgent10from utils.file_utils import load_sample_text, create_backup11from utils.input_utils import get_multiline_input12from dotenv import load_dotenv131415def main():16 load_dotenv() # Load environment variables from .env file17 print("\n=== Enhanced Persona Generator and Responder ===")1819 # Retrieve API Key20 api_key = os.getenv("OPENAI_API_KEY")21 if not api_key:22 print("Error: OPENAI_API_KEY not found in environment variables.")23 return2425 # Initialize agents26 persona_agent = PersonaAgent(api_key)27 response_agent = ResponseAgent(api_key)28 validation_agent = ValidationAgent()29 export_agent = ExportAgent()3031 while True:32 print("\nOptions:")33 print("1. Use existing Persona")34 print("2. Generate new Persona from sample text")35 print("3. Load sample text from file")36 print("4. Exit")3738 choice = input("\nEnter your choice (1-4): ").strip()3940 if choice == '4':41 print("Exiting program...")42 break4344 persona = {}4546 if choice == '1':47 persona = persona_agent.load_persona()48 if persona:49 print("\nCurrent Persona:")50 print(persona_agent.format_persona_summary(persona))51 else:52 if input("\nNo persona loaded. Generate new one? (y/n): ").lower() == 'y':53 choice = '2'54 else:55 continue5657 if choice == '2':58 sample_text = get_multiline_input("\nEnter sample text:")59 if not sample_text.strip():60 print("Error: Empty sample text provided.")61 continue62 print("\nGenerating persona from sample text...")63 persona = persona_agent.generate_persona(sample_text)64 if persona:65 if validation_agent.validate(persona):66 create_backup(persona_agent.persona_file)67 if persona_agent.save_persona(persona):68 print("\nGenerated Persona:")69 print(persona_agent.format_persona_summary(persona))70 else:71 print("Warning: Persona generated but not saved.")72 else:73 print("Error: Failed to generate valid persona.")74 continue75 else:76 print("Error: Failed to generate persona.")77 continue7879 elif choice == '3':80 filename = input("\nEnter the path to the text file: ").strip()81 sample_text = load_sample_text(filename)82 if not sample_text:83 continue84 print("\nGenerating persona from file...")85 persona = persona_agent.generate_persona(sample_text)86 if persona:87 if validation_agent.validate(persona):88 create_backup(persona_agent.persona_file)89 if persona_agent.save_persona(persona):90 print("\nGenerated Persona:")91 print(persona_agent.format_persona_summary(persona))92 else:93 print("Warning: Persona generated but not saved.")94 else:95 print("Error: Failed to generate valid persona.")96 continue97 else:98 print("Error: Failed to generate persona.")99 continue100101 # Get prompt and generate response102 while True:103 prompt = get_multiline_input("\nEnter your prompt:")104 if not prompt.strip():105 print("Error: Empty prompt provided.")106 if input("Try again? (y/n): ").lower() != 'y':107 break108 continue109 print("\nGenerating response...")110 response = response_agent.generate_response(persona, prompt)111 print("\n=== Generated Response ===")112 print(response)113 if input("\nExport response to Markdown? (y/n): ").lower() == 'y':114 default_filename = f"response_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md"115 custom_filename = input(f"Enter filename (default: {default_filename}): ").strip()116 filename = custom_filename if custom_filename else default_filename117 if export_agent.export_to_markdown(response, filename):118 print("Response exported successfully.")119 else:120 print("Error: Failed to export response.")121 if input("\nGenerate another response with current persona? (y/n): ").lower() != 'y':122 break123124 if input("\nStart over with a different persona? (y/n): ").lower() != 'y':125 print("Exiting program...")126 break127128129if __name__ == "__main__":130 try:131 main()132 except KeyboardInterrupt:133 print("\nProgram terminated by user.")134 except Exception as e:135 print(f"\nProgram terminated due to error: {str(e)}")136 finally:137 print("\nThank you for using the Enhanced Persona Generator and Responder!")
Explanation:
-
Workflow:
- Options Menu: Allows users to choose between using an existing persona, generating a new one from sample text, loading sample text from a file, or exiting.
- Persona Handling:
- Option 1: Loads and displays an existing persona.
- Option 2: Prompts the user to input sample text, generates a persona, validates it, and saves it.
- Option 3: Loads sample text from a specified file, generates a persona, validates it, and saves it.
- Response Generation: After a persona is available, users can input prompts to generate responses. These responses can optionally be exported to Markdown files.
- Loop Control: Users can choose to generate multiple responses or start over with a different persona.
-
Error Handling: The script gracefully handles interruptions and unexpected errors, ensuring the program doesn't crash abruptly.
7. Running the Application
Step 1: Configure Environment Variables
Ensure you have a .env file in your project root containing your OpenAI API key.
bash1touch .env
Edit the .env file and add:
plaintext1OPENAI_API_KEY=your-openai-api-key-here
Security Reminder: Never commit your .env file or expose your API keys publicly. Add .env to your .gitignore:
bash1echo ".env" >> .gitignore
Step 2: Activate the Virtual Environment
If not already activated, activate your virtual environment:
-
On macOS/Linux:
bash1source venv/bin/activate -
On Windows:
bash1venv\Scripts\activate
Step 3: Run the Application
Execute the main.py script:
bash1python main.py
Expected Output:
text1=== Enhanced Persona Generator and Responder ===23Options:41. Use existing Persona52. Generate new Persona from sample text63. Load sample text from file74. Exit89Enter your choice (1-4):
Step 4: Interacting with the Application
-
Option 1: Use Existing Persona
- If a
persona.jsonexists, it will load and display the persona. - If not, it will prompt to generate a new one.
- If a
-
Option 2: Generate New Persona from Sample Text
- Input: Enter a sample text when prompted.
- Processing: The application sends the text to OpenAI's API to generate a persona.
- Output: Displays the generated persona and saves it to
persona.json.
-
Option 3: Load Sample Text from File
- Input: Provide the path to a text file containing sample text.
- Processing: Reads the file, generates a persona, validates, and saves it.
- Output: Displays the generated persona and saves it to
persona.json.
-
Generating Responses
- After selecting or generating a persona, you can input prompts.
- The application generates responses aligned with the persona's characteristics.
- Optionally, you can export these responses to Markdown files for record-keeping.
-
Exiting
- Choose option 4 or follow the prompts to exit the application gracefully.
8. Troubleshooting
Common Issues and Solutions
-
ModuleNotFoundError: No module named 'openai'- Cause: OpenAI package not installed.
- Solution: Install the package using
pip install openai.
-
ModuleNotFoundError: No module named 'dotenv'- Cause:
python-dotenvpackage not installed. - Solution: Install the package using
pip install python-dotenv.
- Cause:
-
ModuleNotFoundError: No module named 'utils.file_utils'- Cause: Incorrect project structure or missing
__init__.py. - Solution: Ensure the
utilsdirectory contains an__init__.pyfile and the script is run from the project root.
- Cause: Incorrect project structure or missing
-
Empty or Invalid
persona.json- Cause: Issues during persona generation or saving.
- Solution:
- Ensure the sample text provided is substantial and clear.
- Check for API errors or JSON parsing issues in
persona_agent.py.
-
API Key Issues
- Cause: Missing or incorrect OpenAI API key.
- Solution:
- Verify the API key in the
.envfile. - Ensure there are no extra spaces or hidden characters.
- Verify the API key in the
-
Permission Errors When Saving Files
- Cause: Insufficient permissions to write to the directory.
- Solution: Run the terminal with appropriate permissions or choose a different directory.
9. Conclusion
Building an Enhanced Persona Generator and Responder is a testament to the versatility of AI and Python. By modularizing the application into distinct agents and utilities, we've created a scalable and maintainable system that can adapt to various use cases. Whether you're looking to develop sophisticated chatbots, personalized content generators, or simply experiment with AI-driven applications, this project provides a solid foundation.
Key Takeaways:
- Modular Design: Separating concerns into agents and utilities enhances maintainability.
- Robust Error Handling: Ensures the application remains stable and user-friendly.
- Secure Practices: Managing API keys and sensitive data responsibly is paramount.
- Scalability: The application's structure allows for easy expansion and integration of additional features.
Feel free to customize and expand upon this foundation to suit your specific needs. If you encounter any issues or have further questions, don't hesitate to reach out or consult the respective package documentation.
Happy coding!

Sovereign AI: Building Local-First Intelligent Systems
by Daniel Kliewer · Paperback · 72 pages
The hands-on guide to building AI that runs on your hardware, keeps your data private, and eliminates cloud dependence. Working code included.