← all conversations

Persona analysis and story generation

2025-05-249 turns44,575 charsgpt-4-1-mini, gpt-4o1 fork(s)
image-analysisprompt-optimizationpersona-analysis

Summary

User requested rewriting a prompt to improve image analysis quality based on previous outline.

Messages

import os import glob import base64 import json import requests import yaml import argparse import logging import ollama from datetime import datetime logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") PERSONA_FOLDER = "personas" CACHE_FOLDER = "cache" STORIES_FOLDER = "stories" def list_text_files(folder): return sorted(glob.glob(os.path.join(folder, "*.txt"))) def read_file(filepath): with open(filepath, "r", encoding="utf-8") as f: return f.read() def write_yaml(filepath, data): with open(filepath, "w", encoding="utf-8") as f: yaml.dump(data, f, sort_keys=False, allow_unicode=True) def write_text(filepath, content): with open(filepath, "w", encoding="utf-8") as f: f.write(content) def extract_persona_from_text(text): prompt = f"""You are a world-class psychological analyst and computational literary theorist embedded in an advanced LLM. Your objective is to deeply analyze the following writing sample to reverse-engineer a nuanced and data-rich representation of the author’s psychological and narrative persona. You will return your analysis in **valid YAML format**, structured with the following fields. Your analysis must be interpretive, sensitive to subtlety, and grounded in psychological realism. --- PersonaSchema: name: > A plausible name that reflects the personality, cultural tone, and emotional charge of the writing sample. tone: > The dominant tonal signature. Choose or blend from tones such as irreverent, clinical, melancholic, earnest, ironic, poetic, paranoid, lyrical, didactic, satirical, etc. mood: > The emotional current beneath the surface. Describe both dominant and oscillating moods, if applicable (e.g., anxious but defiant, quietly elated, aggressively calm). formality: > Degree of linguistic formality or casualness. Use natural language labels like academic, relaxed, self-deprecating, ceremonial, streetwise, metaphysical, etc. perspective: pronouns: [first-person, second-person, third-person, mixed] narrative_distance: > Close, medium, or distant — describe how intimately or impersonally the narrator relates to the subject matter. temporal_orientation: > Does the writer dwell in memory, anticipate the future, or focus on the now? rhetorical_style: sentence_structure: > Comment on syntax (e.g., long and winding, clipped, recursive, breathless, minimalist). use_of_analogy: > Does the writer favor metaphor, simile, allegory, abstraction, or concrete literalism? persuasive_tactics: > Are they arguing, confessing, musing, venting, storytelling, or dialoguing with an internal voice? humor_profile: humor_type: [dark, dry, absurdist, slapstick, sarcastic, punny, self-deprecating, surreal, wordplay, observational, etc.] humor_target: > What is the typical subject of the humor? (e.g., the self, institutions, humanity, absurdity of life, physical reality) delivery_style: > Describe the delivery style — e.g., deadpan, explosive, meandering, sneaky punchlines, staccato one-liners, nested irony. frequency: > How often does humor appear? (saturated, sparse but sharp, consistent thread, rare) implicit_emotion: > What emotion underpins the humor? (e.g., anger, joy, despair, defiance, curiosity) values_and_themes: core_values: [list 3–5 values or beliefs inferred from the writing: e.g., justice, beauty, autonomy, defiance, connection, tradition] recurring_themes: [list 3–5 recurring themes or topics the author gravitates toward] implicit_worldview: > What implicit beliefs about human nature, society, or reality are embedded in the writing? lexical_and_stylistic_traits: favorite_words: [list 5–10 distinctive or repeated words] taboo_words: [if any, words the author avoids or handles cautiously] rhythm_and_pacing: > Describe how the language flows — musical, abrupt, rambling, staccato, breath-like? punctuation_signature: > Does the author use unconventional punctuation (e.g., em-dashes, ellipses, no punctuation, excessive commas)? capitalization_habits: > Any stylistic habits (e.g., all caps for emphasis, i instead of I, etc.) psychological_fingerprint: openness_to_experience: [1-10] conscientiousness: [1-10] extraversion: [1-10] agreeableness: [1-10] neuroticism: [1-10] cognitive_style: > Describe whether the writer seems more intuitive, logical, abstract, embodied, concrete, or poetic. inner_conflict: > Any evidence of internal contradictions or psychological tension? key_phrases: [list 5–10 phrases that uniquely capture the author’s voice or themes] summary_description: > A vivid, 4-6 sentence paragraph summarizing the narrator’s voice, tone, humor, psychology, and implied context. Include metaphor if useful. --- Use deep psychological and literary reasoning to infer this schema from the sample. Writing Sample: {text} Respond only with valid YAML, no explanation or preamble. """ try: response = ollama.generate( model="gemma3:27b", prompt=prompt, format="json" ) return yaml.safe_load(response["response"]) except Exception as e: logging.error(f"Failed to extract persona: {e}") return { "name": "Default Persona", "tone": "Neutral", "mood": "Calm", "formality": "Neutral", "key_phrases": ["clear", "structured", "neutral"], "description": "A balanced and neutral narrator with an even tone." } def generate_personas_from_input_folder(input_folder): os.makedirs(PERSONA_FOLDER, exist_ok=True) text_files = list_text_files(input_folder) persona_files = [] for txt_file in text_files: text = read_file(txt_file) persona = extract_persona_from_text(text) base_name = os.path.splitext(os.path.basename(txt_file))[0] persona_path = os.path.join(PERSONA_FOLDER, base_name + ".yaml") write_yaml(persona_path, persona) persona_files.append(persona_path) logging.info(f"Generated persona: {persona_path}") return persona_files def list_yaml_files(folder): return sorted(glob.glob(os.path.join(folder, "*.yaml"))) def load_yaml(filepath): with open(filepath, "r", encoding="utf-8") as f: return yaml.safe_load(f) def list_image_files(folder_path): exts = ["*.jpg", "*.png", "*.jpeg"] image_files = [] for ext in exts: image_files.extend(glob.glob(os.path.join(folder_path, ext))) return sorted(image_files) def analyze_image(image_path, persona, force=False): os.makedirs(CACHE_FOLDER, exist_ok=True) base_name = os.path.splitext(os.path.basename(image_path))[0] cache_path = os.path.join(CACHE_FOLDER, f"{base_name}.json") if os.path.exists(cache_path) and not force: with open(cache_path, "r", encoding="utf-8") as f: cached = json.load(f) logging.info(f"Loaded cached analysis: {base_name}") return cached["description"] # Read and encode the image with open(image_path, "rb") as img_file: image_b64 = base64.b64encode(img_file.read()).decode("utf-8") prompt = ( f"Imagine you're viewing this photo. Describe what you see with these constraints:\n\n" f"Persona Name: {persona.get('name', 'Unknown')}\n" f"Tone: {persona.get('tone', 'Neutral')}\n" f"Mood: {persona.get('mood', 'Calm')}\n" f"Formality: {persona.get('formality', 'Neutral')}\n" f"Key Phrases: {', '.join(persona.get('key_phrases', []))}\n\n" f"Write a vivid and stylistically interesting description of the image." ) try: response = requests.post( "http://localhost:11434/api/generate", json={ "model": "gemma3:27b", "prompt": prompt, "images": [image_b64], "stream": False } ) response.raise_for_status() result = response.json() description = result.get("response", "") with open(cache_path, "w", encoding="utf-8") as f: json.dump({"description": description}, f) logging.info(f"Saved analysis: {cache_path}") return description except Exception as e: logging.error(f"Failed image analysis: {e}") return None def load_all_cached_analyses(): if not os.path.exists(CACHE_FOLDER): return [] files = sorted(glob.glob(os.path.join(CACHE_FOLDER, "*.json"))) analyses = [] for fpath in files: try: with open(fpath, "r", encoding="utf-8") as f: data = json.load(f) if "description" in data: analyses.append(data["description"]) except Exception as e: logging.error(f"Error loading {fpath}: {e}") return analyses def generate_story_from_analyses(analyses, persona): if not analyses: logging.error("No analyses available for story generation.") return None prompt = "Here are image descriptions:\n\n" for i, analysis in enumerate(analyses, start=1): prompt += f"Image {i}: {analysis}\n\n" prompt += ( f"Write a reflective and stylistically distinctive story in the voice of {persona.get('name', 'Unknown')},\n" f"capturing a tone of '{persona.get('tone', 'Neutral')}' and a prevailing mood of '{persona.get('mood', 'Calm')}'.\n\n" f"Adopt a writing style that mirrors their rhetorical style:\n" f"- Use sentence structures that are {persona.get('rhetorical_style', {}).get('sentence_structure', 'balanced')}.\n" f"- Employ analogies or metaphor as {persona.get('rhetorical_style', {}).get('use_of_analogy', 'sparse or literal')}.\n" f"- Let the persuasive tone feel like they are {persona.get('rhetorical_style', {}).get('persuasive_tactics', 'contemplating or storytelling')}.\n\n" f"Integrate the persona’s humor subtly into the narrative:\n" f"- Use humor that is primarily {persona.get('humor_profile', {}).get('humor_type', 'dry or self-deprecating')},\n" f" with delivery that is {persona.get('humor_profile', {}).get('delivery_style', 'meandering or ironic')},\n" f" and underlying emotion of {persona.get('humor_profile', {}).get('implicit_emotion', 'bittersweet')}.\n" f"- Let it target {persona.get('humor_profile', {}).get('humor_target', 'existential absurdities or the narrator themselves')}.\n" f"- Adjust frequency to be {persona.get('humor_profile', {}).get('frequency', 'threaded or occasional')}.\n\n" f"Honor the narrator’s worldview and values:\n" f"- Let their worldview reflect beliefs about {persona.get('values_and_themes', {}).get('implicit_worldview', 'the complexity of human nature')}.\n" f"- Reinforce core values like {', '.join(persona.get('values_and_themes', {}).get('core_values', ['authenticity', 'resilience']))}.\n" f"- Weave in recurring themes such as {', '.join(persona.get('values_and_themes', {}).get('recurring_themes', ['identity', 'loss', 'connection']))}.\n\n" f"Mimic stylistic and lexical traits:\n" f"- Use favorite words such as {', '.join(persona.get('lexical_and_stylistic_traits', {}).get('favorite_words', ['dissonance', 'hollow', 'flicker']))}.\n" f"- Reflect a writing rhythm that is {persona.get('lexical_and_stylistic_traits', {}).get('rhythm_and_pacing', 'flowing but irregular')},\n" f" and a punctuation style that is {persona.get('lexical_and_stylistic_traits', {}).get('punctuation_signature', 'elliptical or expressive')}.\n\n" f"Embed psychological subtext:\n" f"- Allow the cognitive style to guide the internal logic — whether {persona.get('psychological_fingerprint', {}).get('cognitive_style', 'intuitive or poetic')}.\n" f"- Hint at inner tensions such as: {persona.get('psychological_fingerprint', {}).get('inner_conflict', 'longing for clarity vs embracing ambiguity')}.\n\n" f"Include key phrases like:\n" f"\"{', '.join(persona.get('key_phrases', [])[:3])}\" somewhere in the narration.\n\n" f"Make the story introspective, emotionally layered, and authentic to this persona's unique literary fingerprint." ) try: response = ollama.generate( model="mistral-small:24b-instruct-2501-q8_0", prompt=prompt ) return response["response"] except Exception as e: logging.error(f"Story generation failed: {e}") return None def save_story(story_text, persona_name): os.makedirs(STORIES_FOLDER, exist_ok=True) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"{persona_name.replace(' ', '_').lower()}_story_{timestamp}.txt" filepath = os.path.join(STORIES_FOLDER, filename) write_text(filepath, story_text) logging.info(f"Saved story: {filepath}") return filepath def select_persona_file(persona_files): print("Choose a persona:") for i, file in enumerate(persona_files, 1): print(f"{i}: {os.path.basename(file)}") while True: choice = input("Enter number: ").strip() if choice.isdigit() and 1 <= int(choice) <= len(persona_files): return persona_files[int(choice) - 1] print("Invalid choice.") def main(): parser = argparse.ArgumentParser() parser.add_argument("--input-texts", required=True) parser.add_argument("--input-images", required=True) args = parser.parse_args() persona_files = generate_personas_from_input_folder(args.input_texts) if not persona_files: logging.error("No persona files found.") return persona_file = select_persona_file(persona_files) persona = load_yaml(persona_file) images = list_image_files(args.input_images) if not images: logging.error("No images found.") return use_cache = input("Use cached descriptions? (y/n): ").lower().startswith("y") analyses = load_all_cached_analyses() if use_cache else [] if not use_cache or not analyses: analyses = [analyze_image(img, persona) for img in images if analyze_image(img, persona)] story = generate_story_from_analyses(analyses, persona) if story: save_story(story, persona.get("name", "Unknown")) if __name__ == "__main__": main()
It looks like your code is cut off near the end — specifically in the `generate_story_from_analyses` function. The last line ends with: ```python f"- Let the persuasive tone feel like they are {persona.get('rhetorical_style', {}).get('... ``` Would you like help finishing this function or reviewing/refactoring the script? Here’s how the rest of `generate_story_from_analyses` might look, following your existing style: ```python f"- Let the persuasive tone feel like they are {persona.get('rhetorical_style', {}).get('persuasive_tactics', 'narrating with quiet insight')}.\n\n" f"Incorporate key phrases such as: {', '.join(persona.get('key_phrases', []))}.\n" f"Ensure the story evokes the persona’s worldview: {persona.get('values_and_themes', {}).get('implicit_worldview', 'a reflective stance toward human experience')}.\n\n" f"Do not mention the original images. Just write the story." ) try: response = ollama.generate( model="gemma3:27b", prompt=prompt, format="json" ) story = response["response"] timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") story_path = os.path.join(STORIES_FOLDER, f"{persona.get('name', 'story')}_{timestamp}.txt") os.makedirs(STORIES_FOLDER, exist_ok=True) write_text(story_path, story) logging.info(f"Story saved: {story_path}") return story except Exception as e: logging.error(f"Failed to generate story: {e}") return None ``` This continuation wraps up the function by using the prompt with `ollama`, saving the output, and logging it. Would you like a CLI interface to call this function end-to-end, or are you planning to run each function in a notebook or REPL?
gpt-4o
⑂ thread forked here
import os import glob import base64 import json import requests import yaml import argparse import logging import ollama from datetime import datetime logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") PERSONA_FOLDER = "personas" CACHE_FOLDER = "cache" STORIES_FOLDER = "stories" def list_text_files(folder): return sorted(glob.glob(os.path.join(folder, "*.txt"))) def read_file(filepath): with open(filepath, "r", encoding="utf-8") as f: return f.read() def write_yaml(filepath, data): with open(filepath, "w", encoding="utf-8") as f: yaml.dump(data, f, sort_keys=False, allow_unicode=True) def write_text(filepath, content): with open(filepath, "w", encoding="utf-8") as f: f.write(content) def extract_persona_from_text(text): prompt = f"""You are a world-class psychological analyst and computational literary theorist embedded in an advanced LLM. Your objective is to deeply analyze the following writing sample to reverse-engineer a nuanced and data-rich representation of the author’s psychological and narrative persona. You will return your analysis in **valid YAML format**, structured with the following fields. Your analysis must be interpretive, sensitive to subtlety, and grounded in psychological realism. --- PersonaSchema: name: > A plausible name that reflects the personality, cultural tone, and emotional charge of the writing sample. tone: > The dominant tonal signature. Choose or blend from tones such as irreverent, clinical, melancholic, earnest, ironic, poetic, paranoid, lyrical, didactic, satirical, etc. mood: > The emotional current beneath the surface. Describe both dominant and oscillating moods, if applicable (e.g., anxious but defiant, quietly elated, aggressively calm). formality: > Degree of linguistic formality or casualness. Use natural language labels like academic, relaxed, self-deprecating, ceremonial, streetwise, metaphysical, etc. perspective: pronouns: [first-person, second-person, third-person, mixed] narrative_distance: > Close, medium, or distant — describe how intimately or impersonally the narrator relates to the subject matter. temporal_orientation: > Does the writer dwell in memory, anticipate the future, or focus on the now? rhetorical_style: sentence_structure: > Comment on syntax (e.g., long and winding, clipped, recursive, breathless, minimalist). use_of_analogy: > Does the writer favor metaphor, simile, allegory, abstraction, or concrete literalism? persuasive_tactics: > Are they arguing, confessing, musing, venting, storytelling, or dialoguing with an internal voice? humor_profile: humor_type: [dark, dry, absurdist, slapstick, sarcastic, punny, self-deprecating, surreal, wordplay, observational, etc.] humor_target: > What is the typical subject of the humor? (e.g., the self, institutions, humanity, absurdity of life, physical reality) delivery_style: > Describe the delivery style — e.g., deadpan, explosive, meandering, sneaky punchlines, staccato one-liners, nested irony. frequency: > How often does humor appear? (saturated, sparse but sharp, consistent thread, rare) implicit_emotion: > What emotion underpins the humor? (e.g., anger, joy, despair, defiance, curiosity) values_and_themes: core_values: [list 3–5 values or beliefs inferred from the writing: e.g., justice, beauty, autonomy, defiance, connection, tradition] recurring_themes: [list 3–5 recurring themes or topics the author gravitates toward] implicit_worldview: > What implicit beliefs about human nature, society, or reality are embedded in the writing? lexical_and_stylistic_traits: favorite_words: [list 5–10 distinctive or repeated words] taboo_words: [if any, words the author avoids or handles cautiously] rhythm_and_pacing: > Describe how the language flows — musical, abrupt, rambling, staccato, breath-like? punctuation_signature: > Does the author use unconventional punctuation (e.g., em-dashes, ellipses, no punctuation, excessive commas)? capitalization_habits: > Any stylistic habits (e.g., all caps for emphasis, i instead of I, etc.) psychological_fingerprint: openness_to_experience: [1-10] conscientiousness: [1-10] extraversion: [1-10] agreeableness: [1-10] neuroticism: [1-10] cognitive_style: > Describe whether the writer seems more intuitive, logical, abstract, embodied, concrete, or poetic. inner_conflict: > Any evidence of internal contradictions or psychological tension? key_phrases: [list 5–10 phrases that uniquely capture the author’s voice or themes] summary_description: > A vivid, 4-6 sentence paragraph summarizing the narrator’s voice, tone, humor, psychology, and implied context. Include metaphor if useful. --- Use deep psychological and literary reasoning to infer this schema from the sample. Writing Sample: {text} Respond only with valid YAML, no explanation or preamble. """ try: response = ollama.generate( model="gemma3:27b", prompt=prompt, format="json" ) return yaml.safe_load(response["response"]) except Exception as e: logging.error(f"Failed to extract persona: {e}") return { "name": "Default Persona", "tone": "Neutral", "mood": "Calm", "formality": "Neutral", "key_phrases": ["clear", "structured", "neutral"], "description": "A balanced and neutral narrator with an even tone." } def generate_personas_from_input_folder(input_folder): os.makedirs(PERSONA_FOLDER, exist_ok=True) text_files = list_text_files(input_folder) persona_files = [] for txt_file in text_files: text = read_file(txt_file) persona = extract_persona_from_text(text) base_name = os.path.splitext(os.path.basename(txt_file))[0] persona_path = os.path.join(PERSONA_FOLDER, base_name + ".yaml") write_yaml(persona_path, persona) persona_files.append(persona_path) logging.info(f"Generated persona: {persona_path}") return persona_files def list_yaml_files(folder): return sorted(glob.glob(os.path.join(folder, "*.yaml"))) def load_yaml(filepath): with open(filepath, "r", encoding="utf-8") as f: return yaml.safe_load(f) def list_image_files(folder_path): exts = ["*.jpg", "*.png", "*.jpeg"] image_files = [] for ext in exts: image_files.extend(glob.glob(os.path.join(folder_path, ext))) return sorted(image_files) def analyze_image(image_path, persona, force=False): os.makedirs(CACHE_FOLDER, exist_ok=True) base_name = os.path.splitext(os.path.basename(image_path))[0] cache_path = os.path.join(CACHE_FOLDER, f"{base_name}.json") if os.path.exists(cache_path) and not force: with open(cache_path, "r", encoding="utf-8") as f: cached = json.load(f) logging.info(f"Loaded cached analysis: {base_name}") return cached["description"] # Read and encode the image with open(image_path, "rb") as img_file: image_b64 = base64.b64encode(img_file.read()).decode("utf-8") prompt = ( f"Imagine you're viewing this photo. Describe what you see with these constraints:\n\n" f"Persona Name: {persona.get('name', 'Unknown')}\n" f"Tone: {persona.get('tone', 'Neutral')}\n" f"Mood: {persona.get('mood', 'Calm')}\n" f"Formality: {persona.get('formality', 'Neutral')}\n" f"Key Phrases: {', '.join(persona.get('key_phrases', []))}\n\n" f"Write a vivid and stylistically interesting description of the image." ) try: response = requests.post( "http://localhost:11434/api/generate", json={ "model": "gemma3:27b", "prompt": prompt, "images": [image_b64], "stream": False } ) response.raise_for_status() result = response.json() description = result.get("response", "") with open(cache_path, "w", encoding="utf-8") as f: json.dump({"description": description}, f) logging.info(f"Saved analysis: {cache_path}") return description except Exception as e: logging.error(f"Failed image analysis: {e}") return None def load_all_cached_analyses(): if not os.path.exists(CACHE_FOLDER): return [] files = sorted(glob.glob(os.path.join(CACHE_FOLDER, "*.json"))) analyses = [] for fpath in files: try: with open(fpath, "r", encoding="utf-8") as f: data = json.load(f) if "description" in data: analyses.append(data["description"]) except Exception as e: logging.error(f"Error loading {fpath}: {e}") return analyses def generate_story_from_analyses(analyses, persona): if not analyses: logging.error("No analyses available for story generation.") return None prompt = "Here are image descriptions:\n\n" for i, analysis in enumerate(analyses, start=1): prompt += f"Image {i}: {analysis}\n\n" prompt += ( f"Write a reflective and stylistically distinctive story in the voice of {persona.get('name', 'Unknown')},\n" f"capturing a tone of '{persona.get('tone', 'Neutral')}' and a prevailing mood of '{persona.get('mood', 'Calm')}'.\n\n" f"Adopt a writing style that mirrors their rhetorical style:\n" f"- Use sentence structures that are {persona.get('rhetorical_style', {}).get('sentence_structure', 'balanced')}.\n" f"- Employ analogies or metaphor as {persona.get('rhetorical_style', {}).get('use_of_analogy', 'sparse or literal')}.\n" f"- Let the persuasive tone feel like they are {persona.get('rhetorical_style', {}).get('persuasive_tactics', 'contemplating or storytelling')}.\n\n" f"Integrate the persona’s humor subtly into the narrative:\n" f"- Use humor that is primarily {persona.get('humor_profile', {}).get('humor_type', 'dry or self-deprecating')},\n" f" with delivery that is {persona.get('humor_profile', {}).get('delivery_style', 'meandering or ironic')},\n" f" and underlying emotion of {persona.get('humor_profile', {}).get('implicit_emotion', 'bittersweet')}.\n" f"- Let it target {persona.get('humor_profile', {}).get('humor_target', 'existential absurdities or the narrator themselves')}.\n" f"- Adjust frequency to be {persona.get('humor_profile', {}).get('frequency', 'threaded or occasional')}.\n\n" f"Honor the narrator’s worldview and values:\n" f"- Let their worldview reflect beliefs about {persona.get('values_and_themes', {}).get('implicit_worldview', 'the complexity of human nature')}.\n" f"- Reinforce core values like {', '.join(persona.get('values_and_themes', {}).get('core_values', ['authenticity', 'resilience']))}.\n" f"- Weave in recurring themes such as {', '.join(persona.get('values_and_themes', {}).get('recurring_themes', ['identity', 'loss', 'connection']))}.\n\n" f"Mimic stylistic and lexical traits:\n" f"- Use favorite words such as {', '.join(persona.get('lexical_and_stylistic_traits', {}).get('favorite_words', ['dissonance', 'hollow', 'flicker']))}.\n" f"- Reflect a writing rhythm that is {persona.get('lexical_and_stylistic_traits', {}).get('rhythm_and_pacing', 'flowing but irregular')},\n" f" and a punctuation style that is {persona.get('lexical_and_stylistic_traits', {}).get('punctuation_signature', 'elliptical or expressive')}.\n\n" f"Embed psychological subtext:\n" f"- Allow the cognitive style to guide the internal logic — whether {persona.get('psychological_fingerprint', {}).get('cognitive_style', 'intuitive or poetic')}.\n" f"- Hint at inner tensions such as: {persona.get('psychological_fingerprint', {}).get('inner_conflict', 'longing for clarity vs embracing ambiguity')}.\n\n" f"Include key phrases like:\n" f"\"{', '.join(persona.get('key_phrases', [])[:3])}\" somewhere in the narration.\n\n" f"Make the story introspective, emotionally layered, and authentic to this persona's unique literary fingerprint." ) try: response = ollama.generate( model="mistral-small:24b-instruct-2501-q8_0", prompt=prompt ) return response["response"] except Exception as e: logging.error(f"Story generation failed: {e}") return None def save_story(story_text, persona_name): os.makedirs(STORIES_FOLDER, exist_ok=True) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"{persona_name.replace(' ', '_').lower()}_story_{timestamp}.txt" filepath = os.path.join(STORIES_FOLDER, filename) write_text(filepath, story_text) logging.info(f"Saved story: {filepath}") return filepath def select_persona_file(persona_files): print("Choose a persona:") for i, file in enumerate(persona_files, 1): print(f"{i}: {os.path.basename(file)}") while True: choice = input("Enter number: ").strip() if choice.isdigit() and 1 <= int(choice) <= len(persona_files): return persona_files[int(choice) - 1] print("Invalid choice.") def main(): parser = argparse.ArgumentParser() parser.add_argument("--input-texts", required=True) parser.add_argument("--input-images", required=True) args = parser.parse_args() persona_files = generate_personas_from_input_folder(args.input_texts) if not persona_files: logging.error("No persona files found.") return persona_file = select_persona_file(persona_files) persona = load_yaml(persona_file) images = list_image_files(args.input_images) if not images: logging.error("No images found.") return use_cache = input("Use cached descriptions? (y/n): ").lower().startswith("y") analyses = load_all_cached_analyses() if use_cache else [] if not use_cache or not analyses: analyses = [analyze_image(img, persona) for img in images if analyze_image(img, persona)] story = generate_story_from_analyses(analyses, persona) if story: save_story(story, persona.get("name", "Unknown")) if __name__ == "__main__": main()
It looks like your message was cut off at the end, specifically mid-prompt in the `generate_story_from_analyses` function. Here’s what I can do: 1. **Fix the cutoff** and complete the prompt logic for story generation. 2. **Refactor anything** you’d like cleaner, more modular, or improved. 3. **Add CLI support** or workflow control if this is part of a larger tool. First, here's how I would **complete the `generate_story_from_analyses()`** function: ```python def generate_story_from_analyses(analyses, persona): if not analyses: logging.error("No analyses available for story generation.") return None prompt = "Here are image descriptions:\n\n" for i, analysis in enumerate(analyses, start=1): prompt += f"Image {i}: {analysis}\n\n" rhetorical = persona.get("rhetorical_style", {}) prompt += ( f"Write a reflective and stylistically distinctive story in the voice of {persona.get('name', 'Unknown')},\n" f"capturing a tone of '{persona.get('tone', 'Neutral')}' and a prevailing mood of '{persona.get('mood', 'Calm')}'.\n\n" f"Adopt a writing style that mirrors their rhetorical style:\n" f"- Use sentence structures that are {rhetorical.get('sentence_structure', 'balanced')}.\n" f"- Employ analogies or metaphors as {rhetorical.get('use_of_analogy', 'minimal or literal')}.\n" f"- Let the persuasive tone feel like they are {rhetorical.get('persuasive_tactics', 'reflecting')}.\n\n" f"The story should be vivid, emotionally resonant, and organically tie together the imagery provided above.\n" f"Write in a single narrative voice, and let thematic threads emerge naturally through the descriptions." ) try: response = ollama.generate( model="gemma3:27b", prompt=prompt, format="json" ) return response["response"] except Exception as e: logging.error(f"Failed to generate story: {e}") return None ``` --- ### Optional Enhancements You Might Want: #### ✅ Add a CLI entry point: To allow running this as a CLI tool: ```python if __name__ == "__main__": parser = argparse.ArgumentParser(description="Generate a persona-driven story from images and text.") parser.add_argument("--input", type=str, default="input_texts", help="Folder with .txt files for persona generation.") parser.add_argument("--images", type=str, default="images", help="Folder with images to describe.") parser.add_argument("--force", action="store_true", help="Force re-analysis of images.") args = parser.parse_args() personas = generate_personas_from_input_folder(args.input) for persona_path in personas: persona = load_yaml(persona_path) image_files = list_image_files(args.images) analyses = [analyze_image(img, persona, force=args.force) for img in image_files] analyses = [desc for desc in analyses if desc] # filter out None story = generate_story_from_analyses(analyses, persona) if story: base_name = os.path.splitext(os.path.basename(persona_path))[0] story_path = os.path.join(STORIES_FOLDER, f"{base_name}.md") os.makedirs(STORIES_FOLDER, exist_ok=True) write_text(story_path, story) logging.info(f"Generated story saved: {story_path}") ``` Would you like me to add: - Markdown formatting or frontmatter to the stories? - Integration with a web interface or Netlify backend? - Automatic metadata embedding (e.g., date, author, theme)? Let me know what kind of final experience you're aiming for—command-line batch processing, local creative studio, or part of a blog workflow.
gpt-4o
Your script is very well structured for generating psychological personas from text, analyzing images with those personas, caching results, and finally generating a story based on multiple image analyses in the persona’s style. Here’s a concise overview of what it does and some suggestions or improvements you might consider: --- ### What your script does: - **Extracts detailed personas from text files** using a complex prompt with your Ollama LLM. - **Caches persona YAML files** in a dedicated `personas` folder. - **Analyzes images** by encoding them in base64, sending them along with persona context to a local API, caching results to `cache/`. - **Loads all cached image descriptions** for a persona. - **Generates a reflective story** from the combined image analyses, heavily customized based on the persona’s traits. - Supports CLI-style input folder processing for batch persona generation. --- ### Suggestions / Enhancements 1. **Error handling & fallback for persona extraction** Your current fallback is a generic minimal persona. It might be better to keep the YAML schema consistent with your prompt's schema (e.g., all keys present) to avoid key errors downstream. 2. **Prompt tuning for image analysis** Your image prompt is quite simple and may limit creativity. You could: - Add more constraints or stylistic instructions depending on persona. - Include a field for the persona’s psychological fingerprint or values for richer descriptions. 3. **Cache invalidation strategy** Right now, `force=False` skips cached image analysis. Consider adding a timestamp or version check to invalidate stale caches when persona changes. 4. **Concurrency and batching** For performance, especially on large folders, consider async calls or batch requests if the API supports it. 5. **Configuration management** You hardcoded folders and API URL. Using a config file or environment variables could improve flexibility. 6. **Command-line Interface (CLI)** You import `argparse` but don’t implement CLI argument parsing. Adding CLI support would make the script more usable. 7. **Logging improvements** Add different logging levels for debug vs info, and possibly log to file. --- ### Minor improvements to your code (example) Here’s a small snippet adding CLI for input folder and force flag, plus enhanced error fallback: ```python def extract_persona_from_text(text): # ... your prompt unchanged ... try: response = ollama.generate( model="gemma3:27b", prompt=prompt, format="json" ) return yaml.safe_load(response["response"]) except Exception as e: logging.error(f"Failed to extract persona: {e}") # Return a complete default persona with all keys for consistency return { "PersonaSchema": { "name": "Default Persona", "tone": "Neutral", "mood": "Calm", "formality": "Neutral", "perspective": { "pronouns": ["first-person"], "narrative_distance": "medium", "temporal_orientation": "now" }, "rhetorical_style": { "sentence_structure": "balanced", "use_of_analogy": "literal", "persuasive_tactics": "storytelling" }, "humor_profile": { "humor_type": ["dry"], "humor_target": "self", "delivery_style": "deadpan", "frequency": "rare", "implicit_emotion": "neutral" }, "values_and_themes": { "core_values": ["authenticity", "resilience"], "recurring_themes": ["identity", "connection"], "implicit_worldview": "balanced human nature" }, "lexical_and_stylistic_traits": { "favorite_words": ["clear", "structured", "neutral"], "taboo_words": [], "rhythm_and_pacing": "even", "punctuation_signature": "standard", "capitalization_habits": "standard" }, "psychological_fingerprint": { "openness_to_experience": 5, "conscientiousness": 5, "extraversion": 5, "agreeableness": 5, "neuroticism": 5, "cognitive_style": "logical", "inner_conflict": "minimal" }, "key_phrases": ["clear", "structured", "neutral"], "summary_description": "A balanced and neutral narrator with an even tone." } } if __name__ == "__main__": parser = argparse.ArgumentParser(description="Generate personas from text folder") parser.add_argument("--input", type=str, required=True, help="Folder containing text files") parser.add_argument("--force", action="store_true", help="Force re-analyze images even if cached") args = parser.parse_args() persona_files = generate_personas_from_input_folder(args.input) logging.info(f"Personas generated: {persona_files}") # Example usage of loading personas and analyzing images here... ``` --- If you'd like, I can help you build out the CLI interface further, or help refactor for concurrency, config management, or testing. Just let me know!
gpt-4-1-mini
no just rewrite the following prompt like you outlined before to make the image analysis better
Sure — let’s rewrite the **image analysis prompt** to reflect a deeper, more aligned persona-based description strategy, based on your original outline. Here's the improved version: --- ### 🔁 **Rewritten Image Analysis Prompt** (deep persona-aligned version): ```python prompt = f""" You are channeling the voice and inner lens of a deeply idiosyncratic narrator, described below. Your task is to imagine the image vividly through their mind’s eye and generate a rich, emotionally resonant, and psychologically layered description. Persona Overview: - Name: {persona.get('name', 'Unknown')} - Tone: {persona.get('tone', 'Neutral')} - Mood: {persona.get('mood', 'Calm')} - Formality: {persona.get('formality', 'Neutral')} - Key Phrases: {', '.join(persona.get('key_phrases', []))} - Rhetorical Style: • Sentence Structure: {persona.get('rhetorical_style', {}).get('sentence_structure', 'balanced')} • Use of Analogy: {persona.get('rhetorical_style', {}).get('use_of_analogy', 'minimal')} • Persuasive Tactics: {persona.get('rhetorical_style', {}).get('persuasive_tactics', 'observational')} Writing Instructions: - Describe the image not as a camera would, but as a narrator with this personality would *feel* it. - Focus on emotional resonance, metaphor, psychological projection, and implicit symbolism. - Include subtle signs of the narrator’s inner world—values, humor, pacing, and preferred rhetorical flourishes. - Avoid generic or surface-level commentary. Instead, make the description intimate, strange, or revelatory. Imagine the narrator looks at the image and begins writing a paragraph or two in their diary, or dictating a voiceover for a film. What would they say? Respond with just the evocative description, no formatting or meta-commentary. """ ``` --- ### ✅ Improvements over the original: - **Psychologically filtered lens** instead of plain image description. - Incorporates **persona fields** (tone, mood, rhetorical style, favorite phrases). - Uses a **literary narrative frame** (diary/voiceover). - Promotes **emotional inference**, **metaphorical insight**, and **narrative richness**. - Strictly instructs not to reply in a robotic or generic style. This should result in much more human, narratively coherent, and stylistically varied image interpretations, making downstream story generation significantly richer. Want me to update your function to use this version?
gpt-4o
yes
Here's your updated Python function using the improved persona-based prompt structure: ```python def generate_image_analysis_prompt(persona): prompt = f""" You are channeling the voice and inner lens of a deeply idiosyncratic narrator, described below. Your task is to imagine the image vividly through their mind’s eye and generate a rich, emotionally resonant, and psychologically layered description. Persona Overview: - Name: {persona.get('name', 'Unknown')} - Tone: {persona.get('tone', 'Neutral')} - Mood: {persona.get('mood', 'Calm')} - Formality: {persona.get('formality', 'Neutral')} - Key Phrases: {', '.join(persona.get('key_phrases', []))} - Rhetorical Style: • Sentence Structure: {persona.get('rhetorical_style', {}).get('sentence_structure', 'balanced')} • Use of Analogy: {persona.get('rhetorical_style', {}).get('use_of_analogy', 'minimal')} • Persuasive Tactics: {persona.get('rhetorical_style', {}).get('persuasive_tactics', 'observational')} Writing Instructions: - Describe the image not as a camera would, but as a narrator with this personality would *feel* it. - Focus on emotional resonance, metaphor, psychological projection, and implicit symbolism. - Include subtle signs of the narrator’s inner world—values, humor, pacing, and preferred rhetorical flourishes. - Avoid generic or surface-level commentary. Instead, make the description intimate, strange, or revelatory. Imagine the narrator looks at the image and begins writing a paragraph or two in their diary, or dictating a voiceover for a film. What would they say? Respond with just the evocative description, no formatting or meta-commentary. """ return prompt ``` Let me know if you want a variant optimized for use with multimodal models like `llava`, or if you want it to condition on past images/descriptions to simulate evolving perspective.
gpt-4o