·19 min

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.

DK

Daniel Kliewer

Author, Sovereign AI

OpenAIPersona GeneratorNLPPythonLLMsTutorialOpenAI APIContent GenerationAI AgentsWriter StylingMachine Learning
Sovereign AI book cover

From the Book

This is from Sovereign AI: Building Local-First Intelligent Systems.

Get the Book — $88
Complete Guide: Building Enhanced AI Persona Generator with Python & OpenAI - From Analysis to Response

Image

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

  1. Introduction
  2. Prerequisites
  3. Project Setup
  4. Implementing the Agents
  5. Utility Modules
  6. Main Orchestrator (main.py)
  7. Running the Application
  8. Troubleshooting
  9. Conclusion

1. Introduction

The Enhanced Persona Generator and Responder application serves two primary functions:

  1. Persona Generation: Analyzes a provided sample text to create a comprehensive persona profile, capturing the author's writing style and personality traits.
  2. 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:

bash
1mkdir persona_responder
2cd persona_responder

Step 2: Set Up a Virtual Environment

It's best practice to use a virtual environment to manage project dependencies.

bash
1python3 -m venv venv

Activate the virtual environment:

  • On macOS/Linux:

    bash
    1source venv/bin/activate
  • On Windows:

    bash
    1venv\Scripts\activate

Step 3: Create requirements.txt

Create a requirements.txt file to list all necessary dependencies:

bash
1touch requirements.txt

Add the following content to requirements.txt:

plaintext
1openai
2python-dotenv

Note:

  • We've excluded swarm, autogen, and flask as 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:

bash
1pip 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:

text
1persona_responder/
2├── agents/
3│ ├── __init__.py
4│ ├── persona_agent.py
5│ ├── response_agent.py
6│ ├── validation_agent.py
7│ └── export_agent.py
8├── utils/
9│ ├── __init__.py
10│ ├── file_utils.py
11│ └── input_utils.py
12├── main.py
13├── persona.json
14├── .env
15├── requirements.txt
16└── README.md

Create the necessary directories and files:

bash
1mkdir agents utils
2touch agents/__init__.py
3touch utils/__init__.py
4touch main.py
5touch README.md

Now, let's implement each agent.


ExportAgent

Responsible for exporting generated responses to Markdown files.

python
1# agents/export_agent.py
2
3from datetime import datetime
4import os
5
6
7class 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 False
16
17 if not filename:
18 timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
19 filename = f"response_{timestamp}.md"
20
21 os.makedirs(os.path.dirname(filename) if os.path.dirname(filename) else '.', exist_ok=True)
22
23 with open(filename, 'w', encoding='utf-8') as f:
24 f.write(content)
25
26 print(f"Successfully exported response to {filename}")
27 return True
28 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.

python
1# agents/persona_agent.py
2
3import json
4import os
5from openai import OpenAI
6from utils.file_utils import create_backup
7
8
9class PersonaAgent:
10 def __init__(self, api_key, persona_file='persona.json'):
11 self.client = OpenAI(api_key=api_key)
12 self.persona_file = persona_file
13
14 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": 1
109 }
110 try:
111 response = self.client.chat.completions.create(**payload)
112 content = response.choices[0].message.content.strip()
113
114 # Extract and parse JSON
115 start_idx = content.find('{')
116 end_idx = content.rfind('}') + 1
117 if start_idx == -1 or end_idx == 0:
118 print("Error: No JSON structure found in response.")
119 return {}
120
121 json_str = content[start_idx:end_idx]
122 try:
123 persona = json.loads(json_str)
124 return persona
125 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 {}
131
132 def save_persona(self, persona: dict) -> bool:
133 try:
134 if not persona:
135 print("Error: Cannot save empty persona.")
136 return False
137 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 only
142 print(f"Successfully saved persona to {self.persona_file}")
143 return True
144 except Exception as e:
145 print(f"Error saving persona: {str(e)}")
146 return False
147
148 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 persona
160 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 {}
166
167 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 ]
177
178 psych_traits = persona.get('psychological_traits', {})
179 for trait, value in psych_traits.items():
180 summary.append(f"- {trait.replace('_', ' ').title()}: {value}")
181
182 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 ])
190
191 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.

python
1# agents/response_agent.py
2
3from openai import OpenAI
4
5
6class ResponseAgent:
7 def __init__(self, api_key):
8 self.client = OpenAI(api_key=api_key)
9
10 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)
17
18 payload = {
19 "model": "gpt-4",
20 "messages": [
21 {"role": "system", "content": system_prompt},
22 {"role": "user", "content": prompt}
23 ],
24 "temperature": 1
25 }
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)}"
31
32 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.

python
1# agents/validation_agent.py
2
3class 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 ]
16
17 try:
18 # Check for required fields
19 for field in required_fields:
20 if field not in persona:
21 print(f"Missing required field: {field}")
22 return False
23
24 # Validate numeric values are within range
25 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 ]
70
71 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 False
77
78 # Validate psychological traits
79 psych_traits = persona.get('psychological_traits', {})
80 if not isinstance(psych_traits, dict):
81 print("psychological_traits must be a dictionary.")
82 return False
83
84 required_psych_traits = [
85 'openness_to_experience',
86 'conscientiousness',
87 'extraversion',
88 'agreeableness',
89 'emotional_stability'
90 ]
91
92 for trait in required_psych_traits:
93 if trait not in psych_traits:
94 print(f"Missing psychological trait: {trait}")
95 return False
96 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 False
100
101 return True
102
103 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.

python
1# utils/file_utils.py
2
3import os
4import json
5from datetime import datetime
6
7def 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 ""
15
16 with open(filename, 'r', encoding='utf-8') as f:
17 content = f.read()
18
19 if not content.strip():
20 print("Warning: File is empty.")
21 return ""
22
23 return content
24 except Exception as e:
25 print(f"Error reading file: {str(e)}")
26 return ""
27
28def 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.

python
1# utils/input_utils.py
2
3def 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)")
9
10 lines = []
11 try:
12 while True:
13 line = input()
14 if not line and lines and not lines[-1]:
15 break
16 lines.append(line)
17 return '\n'.join(lines[:-1]) # Remove last empty line
18 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.

python
1# main.py
2
3import os
4import json
5from datetime import datetime
6from agents.persona_agent import PersonaAgent
7from agents.response_agent import ResponseAgent
8from agents.validation_agent import ValidationAgent
9from agents.export_agent import ExportAgent
10from utils.file_utils import load_sample_text, create_backup
11from utils.input_utils import get_multiline_input
12from dotenv import load_dotenv
13
14
15def main():
16 load_dotenv() # Load environment variables from .env file
17 print("\n=== Enhanced Persona Generator and Responder ===")
18
19 # Retrieve API Key
20 api_key = os.getenv("OPENAI_API_KEY")
21 if not api_key:
22 print("Error: OPENAI_API_KEY not found in environment variables.")
23 return
24
25 # Initialize agents
26 persona_agent = PersonaAgent(api_key)
27 response_agent = ResponseAgent(api_key)
28 validation_agent = ValidationAgent()
29 export_agent = ExportAgent()
30
31 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")
37
38 choice = input("\nEnter your choice (1-4): ").strip()
39
40 if choice == '4':
41 print("Exiting program...")
42 break
43
44 persona = {}
45
46 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 continue
56
57 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 continue
62 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 continue
75 else:
76 print("Error: Failed to generate persona.")
77 continue
78
79 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 continue
84 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 continue
97 else:
98 print("Error: Failed to generate persona.")
99 continue
100
101 # Get prompt and generate response
102 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 break
108 continue
109 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_filename
117 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 break
123
124 if input("\nStart over with a different persona? (y/n): ").lower() != 'y':
125 print("Exiting program...")
126 break
127
128
129if __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:

    1. 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.
    2. 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.
    3. Response Generation: After a persona is available, users can input prompts to generate responses. These responses can optionally be exported to Markdown files.
    4. 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.

bash
1touch .env

Edit the .env file and add:

plaintext
1OPENAI_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:

bash
1echo ".env" >> .gitignore

Step 2: Activate the Virtual Environment

If not already activated, activate your virtual environment:

  • On macOS/Linux:

    bash
    1source venv/bin/activate
  • On Windows:

    bash
    1venv\Scripts\activate

Step 3: Run the Application

Execute the main.py script:

bash
1python main.py

Expected Output:

text
1=== Enhanced Persona Generator and Responder ===
2
3Options:
41. Use existing Persona
52. Generate new Persona from sample text
63. Load sample text from file
74. Exit
8
9Enter your choice (1-4):

Step 4: Interacting with the Application

  • Option 1: Use Existing Persona

    • If a persona.json exists, it will load and display the persona.
    • If not, it will prompt to generate a new one.
  • 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

  1. ModuleNotFoundError: No module named 'openai'

    • Cause: OpenAI package not installed.
    • Solution: Install the package using pip install openai.
  2. ModuleNotFoundError: No module named 'dotenv'

    • Cause: python-dotenv package not installed.
    • Solution: Install the package using pip install python-dotenv.
  3. ModuleNotFoundError: No module named 'utils.file_utils'

    • Cause: Incorrect project structure or missing __init__.py.
    • Solution: Ensure the utils directory contains an __init__.py file and the script is run from the project root.
  4. 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.
  5. API Key Issues

    • Cause: Missing or incorrect OpenAI API key.
    • Solution:
      • Verify the API key in the .env file.
      • Ensure there are no extra spaces or hidden characters.
  6. 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 book cover

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.