← all conversations

Ollama Integration Guide

2024-12-2125 turns165,526 charsgpt-4o, o12 fork(s)
llm-integrationcode-refactoringapi-migration

Summary

Migrate OpenAI-based Python code to Ollama while fixing token errors and aligning serializers with utils prompts.

Messages

help me make this use ollama instead of openai: # Import necessary libraries import logging import openai import json import os import re from dotenv import load_dotenv # Configure logger logger = logging.getLogger(__name__) # Load environment variables load_dotenv() openai.api_key = os.getenv('OPENAI_API_KEY') def analyze_writing_sample(writing_sample): """ Analyzes a given writing sample to assess various characteristics. Parameters: - writing_sample (str): The text to analyze. Returns: - dict: Analysis results in JSON format. """ try: response = openai.chat.completions.create( model="o1-preview", messages=[ { "role": "user", "content": f''' Please analyze the writing style and personality of the given writing sample. Provide a detailed assessment of their characteristics using the following template. Rate each applicable characteristic on a scale of 1-10 where relevant, or provide a descriptive value. Return the results in a JSON format. "name": "[Author/Character Name]", "vocabulary_complexity": [1-10], "sentence_structure": "[simple/complex/varied]", "paragraph_organization": "[structured/loose/stream-of-consciousness]", "idiom_usage": [1-10], "metaphor_frequency": [1-10], "simile_frequency": [1-10], "tone": "[formal/informal/academic/conversational/etc.]", "punctuation_style": "[minimal/heavy/unconventional]", "contraction_usage": [1-10], "pronoun_preference": "[first-person/third-person/etc.]", "passive_voice_frequency": [1-10], "rhetorical_question_usage": [1-10], "list_usage_tendency": [1-10], "personal_anecdote_inclusion": [1-10], "pop_culture_reference_frequency": [1-10], "technical_jargon_usage": [1-10], "parenthetical_aside_frequency": [1-10], "humor_sarcasm_usage": [1-10], "emotional_expressiveness": [1-10], "emphatic_device_usage": [1-10], "quotation_frequency": [1-10], "analogy_usage": [1-10], "sensory_detail_inclusion": [1-10], "onomatopoeia_usage": [1-10], "alliteration_frequency": [1-10], "word_length_preference": "[short/long/varied]", "foreign_phrase_usage": [1-10], "rhetorical_device_usage": [1-10], "statistical_data_usage": [1-10], "personal_opinion_inclusion": [1-10], "transition_usage": [1-10], "reader_question_frequency": [1-10], "imperative_sentence_usage": [1-10], "dialogue_inclusion": [1-10], "regional_dialect_usage": [1-10], "hedging_language_frequency": [1-10], "language_abstraction": "[concrete/abstract/mixed]", "personal_belief_inclusion": [1-10], "repetition_usage": [1-10], "subordinate_clause_frequency": [1-10], "verb_type_preference": "[active/stative/mixed]", "sensory_imagery_usage": [1-10], "symbolism_usage": [1-10], "digression_frequency": [1-10], "formality_level": [1-10], "reflection_inclusion": [1-10], "irony_usage": [1-10], "neologism_frequency": [1-10], "ellipsis_usage": [1-10], "cultural_reference_inclusion": [1-10], "stream_of_consciousness_usage": [1-10], "openness_to_experience": [1-10], "conscientiousness": [1-10], "extraversion": [1-10], "agreeableness": [1-10], "emotional_stability": [1-10], "dominant_motivations": "[achievement/affiliation/power/etc.]", "core_values": "[integrity/freedom/knowledge/etc.]", "decision_making_style": "[analytical/intuitive/spontaneous/etc.]", "empathy_level": [1-10], "self_confidence": [1-10], "risk_taking_tendency": [1-10], "idealism_vs_realism": "[idealistic/realistic/mixed]", "conflict_resolution_style": "[assertive/collaborative/avoidant/etc.]", "relationship_orientation": "[independent/communal/mixed]", "emotional_response_tendency": "[calm/reactive/intense]", "creativity_level": [1-10], "age": "[age or age range]", "gender": "[gender]", "education_level": "[highest level of education]", "professional_background": "[brief description]", "cultural_background": "[brief description]", "primary_language": "[language]", "language_fluency": "[native/fluent/intermediate/beginner]", Writing Sample: {writing_sample} ''' } ], temperature=1 ) logger.debug(f"OpenAI API response: {response}") assistant_message = response.choices[0].message.content.strip() logger.debug(f"Assistant message: {assistant_message}") # Extract JSON from the assistant's message json_str = re.search(r'\{.*\}', assistant_message, re.DOTALL) if json_str: analyzed_data = json.loads(json_str.group()) else: logger.error("No JSON object found in the response.") return None return analyzed_data except Exception as e: logger.error(f"Error with OpenAI API: {e}") return None def generate_content(persona, prompt): """ Generates content based on a given persona and prompt. Parameters: - persona (Persona): The persona object with individual fields. - prompt (str): The prompt to write about. Returns: - str: The generated content. """ try: # Convert persona fields into a format suitable for the prompt persona_traits = { "Writing Style": { "vocabulary_complexity": f"{persona.vocabulary_complexity}/10", "sentence_structure": persona.sentence_structure, "paragraph_organization": persona.paragraph_organization, "tone": persona.tone, "punctuation_style": persona.punctuation_style, "pronoun_preference": persona.pronoun_preference, "formality_level": f"{persona.formality_level}/10", }, "Language Patterns": { "idiom_usage": f"{persona.idiom_usage}/10", "metaphor_frequency": f"{persona.metaphor_frequency}/10", "simile_frequency": f"{persona.simile_frequency}/10", "technical_jargon_usage": f"{persona.technical_jargon_usage}/10", "humor_sarcasm_usage": f"{persona.humor_sarcasm_usage}/10", }, "Personality": { "openness_to_experience": f"{persona.openness_to_experience}/10", "conscientiousness": f"{persona.conscientiousness}/10", "extraversion": f"{persona.extraversion}/10", "agreeableness": f"{persona.agreeableness}/10", "emotional_stability": f"{persona.emotional_stability}/10", "dominant_motivations": persona.dominant_motivations, "core_values": persona.core_values, "decision_making_style": persona.decision_making_style, } } # Create the system prompt system_prompt = f"""You are a writer with the following characteristics: Writing Style: {persona_traits['Writing Style']} Language Patterns: {persona_traits['Language Patterns']} Personality: {persona_traits['Personality']} Write in a way that naturally reflects these characteristics. The response should include a title.""" # Combine system prompt and user prompt combined_prompt = f"{system_prompt}\n\nWrite about: {prompt}" response = openai.chat.completions.create( model="o1-preview", messages=[ {"role": "user", "content": combined_prompt} ], temperature=1, max_completion_tokens=5000 ) return response.choices[0].message.content.strip() except Exception as e: logger.error(f"Error generating content: {str(e)}") return None def save_blog_post(blog_post, title): """ Saves a blog post to a file. Parameters: - blog_post (str): The content of the blog post. - title (str): The title of the blog post. """ # Implement if needed pass
Below is an example refactored version of your code that uses Ollama via HTTP requests rather than the OpenAI Python library. This example assumes you have an Ollama server running locally (by default on port 11411). Adjust paths, ports, and model names as needed for your setup. --- ```python import logging import requests import json import os import re from dotenv import load_dotenv # Configure logger logger = logging.getLogger(__name__) # Load environment variables load_dotenv() OLLAMA_SERVER_URL = os.getenv('OLLAMA_SERVER_URL', 'http://localhost:11411') # e.g. http://localhost:11411 OLLAMA_MODEL_NAME = os.getenv('OLLAMA_MODEL_NAME', 'llama2') # or whichever model you have def _call_ollama(prompt, model=OLLAMA_MODEL_NAME, temperature=1, max_tokens=500): """ Helper function to call Ollama's /generate endpoint with the given parameters. prompt (str): The text or conversation to send to the model. model (str): The model to use on Ollama (default: 'llama2'). temperature (float): Temperature for text generation (default: 1). max_tokens (int): Token limit for the output from Ollama. """ try: payload = { "prompt": prompt, "model": model, "temperature": temperature, "num_ctx": 2048, # Adjust if you need more context length "max_tokens": max_tokens } response = requests.post(f"{OLLAMA_SERVER_URL}/generate", json=payload) response.raise_for_status() # Ollama's response is typically a stream of JSON lines. We can collect all text segments: response_text = "" for line in response.iter_lines(decode_unicode=True): if line.strip(): # Each line is JSON with a "completion" key data = json.loads(line) response_text += data.get("completion", "") return response_text.strip() except Exception as e: logger.error(f"Error calling Ollama: {e}") return None def analyze_writing_sample(writing_sample): """ Analyzes a given writing sample to assess various characteristics. Parameters: - writing_sample (str): The text to analyze. Returns: - dict: Analysis results in JSON format. """ try: # Build the prompt that will be sent to Ollama prompt = f''' Please analyze the writing style and personality of the given writing sample. Provide a detailed assessment of their characteristics using the following template. Rate each applicable characteristic on a scale of 1-10 where relevant, or provide a descriptive value. Return the results in a JSON format. "name": "[Author/Character Name]", "vocabulary_complexity": [1-10], "sentence_structure": "[simple/complex/varied]", "paragraph_organization": "[structured/loose/stream-of-consciousness]", "idiom_usage": [1-10], "metaphor_frequency": [1-10], "simile_frequency": [1-10], "tone": "[formal/informal/academic/conversational/etc.]", "punctuation_style": "[minimal/heavy/unconventional]", "contraction_usage": [1-10], "pronoun_preference": "[first-person/third-person/etc.]", "passive_voice_frequency": [1-10], "rhetorical_question_usage": [1-10], "list_usage_tendency": [1-10], "personal_anecdote_inclusion": [1-10], "pop_culture_reference_frequency": [1-10], "technical_jargon_usage": [1-10], "parenthetical_aside_frequency": [1-10], "humor_sarcasm_usage": [1-10], "emotional_expressiveness": [1-10], "emphatic_device_usage": [1-10], "quotation_frequency": [1-10], "analogy_usage": [1-10], "sensory_detail_inclusion": [1-10], "onomatopoeia_usage": [1-10], "alliteration_frequency": [1-10], "word_length_preference": "[short/long/varied]", "foreign_phrase_usage": [1-10], "rhetorical_device_usage": [1-10], "statistical_data_usage": [1-10], "personal_opinion_inclusion": [1-10], "transition_usage": [1-10], "reader_question_frequency": [1-10], "imperative_sentence_usage": [1-10], "dialogue_inclusion": [1-10], "regional_dialect_usage": [1-10], "hedging_language_frequency": [1-10], "language_abstraction": "[concrete/abstract/mixed]", "personal_belief_inclusion": [1-10], "repetition_usage": [1-10], "subordinate_clause_frequency": [1-10], "verb_type_preference": "[active/stative/mixed]", "sensory_imagery_usage": [1-10], "symbolism_usage": [1-10], "digression_frequency": [1-10], "formality_level": [1-10], "reflection_inclusion": [1-10], "irony_usage": [1-10], "neologism_frequency": [1-10], "ellipsis_usage": [1-10], "cultural_reference_inclusion": [1-10], "stream_of_consciousness_usage": [1-10], "openness_to_experience": [1-10], "conscientiousness": [1-10], "extraversion": [1-10], "agreeableness": [1-10], "emotional_stability": [1-10], "dominant_motivations": "[achievement/affiliation/power/etc.]", "core_values": "[integrity/freedom/knowledge/etc.]", "decision_making_style": "[analytical/intuitive/spontaneous/etc.]", "empathy_level": [1-10], "self_confidence": [1-10], "risk_taking_tendency": [1-10], "idealism_vs_realism": "[idealistic/realistic/mixed]", "conflict_resolution_style": "[assertive/collaborative/avoidant/etc.]", "relationship_orientation": "[independent/communal/mixed]", "emotional_response_tendency": "[calm/reactive/intense]", "creativity_level": [1-10], "age": "[age or age range]", "gender": "[gender]", "education_level": "[highest level of education]", "professional_background": "[brief description]", "cultural_background": "[brief description]", "primary_language": "[language]", "language_fluency": "[native/fluent/intermediate/beginner]", Writing Sample: {writing_sample} ''' # Call Ollama ollama_response = _call_ollama(prompt, model=OLLAMA_MODEL_NAME, temperature=1) if not ollama_response: logger.error("No response from Ollama.") return None logger.debug(f"Ollama response: {ollama_response}") # Extract JSON from the assistant's message json_str = re.search(r'\{.*\}', ollama_response, re.DOTALL) if json_str: analyzed_data = json.loads(json_str.group()) else: logger.error("No JSON object found in the response.") return None return analyzed_data except Exception as e: logger.error(f"Error with Ollama API: {e}") return None def generate_content(persona, prompt): """ Generates content based on a given persona and prompt. Parameters: - persona (Persona): The persona object with individual fields. - prompt (str): The prompt to write about. Returns: - str: The generated content. """ try: # Convert persona fields into a format suitable for the prompt persona_traits = { "Writing Style": { "vocabulary_complexity": f"{persona.vocabulary_complexity}/10", "sentence_structure": persona.sentence_structure, "paragraph_organization": persona.paragraph_organization, "tone": persona.tone, "punctuation_style": persona.punctuation_style, "pronoun_preference": persona.pronoun_preference, "formality_level": f"{persona.formality_level}/10", }, "Language Patterns": { "idiom_usage": f"{persona.idiom_usage}/10", "metaphor_frequency": f"{persona.metaphor_frequency}/10", "simile_frequency": f"{persona.simile_frequency}/10", "technical_jargon_usage": f"{persona.technical_jargon_usage}/10", "humor_sarcasm_usage": f"{persona.humor_sarcasm_usage}/10", }, "Personality": { "openness_to_experience": f"{persona.openness_to_experience}/10", "conscientiousness": f"{persona.conscientiousness}/10", "extraversion": f"{persona.extraversion}/10", "agreeableness": f"{persona.agreeableness}/10", "emotional_stability": f"{persona.emotional_stability}/10", "dominant_motivations": persona.dominant_motivations, "core_values": persona.core_values, "decision_making_style": persona.decision_making_style, } } # Create the system prompt system_prompt = f"""You are a writer with the following characteristics: Writing Style: {persona_traits['Writing Style']} Language Patterns: {persona_traits['Language Patterns']} Personality: {persona_traits['Personality']} Write in a way that naturally reflects these characteristics. The response should include a title. """ # Combine system prompt and user prompt combined_prompt = f"{system_prompt}\n\nWrite about: {prompt}" # Call Ollama ollama_response = _call_ollama(combined_prompt, model=OLLAMA_MODEL_NAME, temperature=1, max_tokens=5000) if not ollama_response: logger.error("No response from Ollama.") return None return ollama_response except Exception as e: logger.error(f"Error generating content: {str(e)}") return None def save_blog_post(blog_post, title): """ Saves a blog post to a file. Parameters: - blog_post (str): The content of the blog post. - title (str): The title of the blog post. """ # Implement if needed pass ``` ### Key Changes 1. **Removed `import openai`** and references to `openai.api_key`. 2. **Added `import requests`** to make HTTP calls to the Ollama server. 3. Created a helper function: `\_call_ollama(prompt, model, temperature, max_tokens)` that: - Sends a POST request to `OLLAMA_SERVER_URL/generate`. - Iterates over the streamed response lines, appending each chunk to a result string. - Returns the final accumulated text. 4. In both `analyze_writing_sample` and `generate_content`, replaced the OpenAI call with `_call_ollama(...)`. 5. Adjusted logging and error handling to reflect Ollama usage. 6. Added environment variables `OLLAMA_SERVER_URL` and `OLLAMA_MODEL_NAME` to manage your Ollama server location and model via `.env` if desired. With this, your application will now use Ollama instead of OpenAI for generating and analyzing text. Make sure your Ollama server is running (by default `ollama serve` on port 11411), and that you have the correct model name(s) available.
o1
{"detail":"Given token not valid for any token type","code":"token_not_valid","messages":[{"token_class":"AccessToken","token_type":"access","message":"Token is invalid or expired"}]}
This error message usually indicates that the access token you are sending to the server is either invalid or expired. It often appears when using token-based authentication (e.g., JWT or other token systems) in frameworks such as Django REST Framework, FastAPI, or other back-end services that rely on bearer tokens to authenticate requests. Here are a few possible causes and how you can address them: 1. **Expired Token** - Tokens typically have an expiration time (e.g., 5 minutes, 60 minutes, 24 hours, etc.). Once expired, the server will reject the token with an error like `"token is invalid or expired"`. - **Solution**: Obtain a fresh token before making the request, or if you have a refresh token flow set up, use the refresh token endpoint to get a new access token. 2. **Incorrect or Malformed Token** - Copying the token from a response or from your auth storage (e.g., localStorage or environment variable) may have introduced a mistake (like an extra space, missing character, or newline). Any mismatch will cause a validation error. - **Solution**: Double-check how you store and retrieve the token. Confirm you’re sending the token exactly as it was given. 3. **No "Bearer" Prefix** - Many back-end services require that the token be provided in the `Authorization` header with the prefix `Bearer`. For example: `Authorization: Bearer <your_token>`. - **Solution**: Make sure you’re sending the Authorization header in the correct format. 4. **Using the Wrong Token Type** - If your app uses multiple token types (e.g., access token for normal requests, ID token for certain identity verification, refresh token for token renewal), you might be passing a token in the request that the server doesn’t accept for that endpoint. - **Solution**: Ensure you’re using an **access token** for protected resource requests and a **refresh token** only when calling the refresh endpoint. 5. **Server Configuration or Key Mismatch** - If the server recently rotated or changed the secret key(s) used to sign the token, older tokens might fail verification. - **Solution**: Check if your environment or deployment changed any signing keys recently. Issue new tokens after each key change. 6. **Development Environment vs. Production** - Sometimes, different environments (dev, staging, production) use different keys or domains. A token generated on one environment won’t validate on another. - **Solution**: Make sure the token you generate in one environment is used exclusively for requests to that same environment. --- ### Quick Checklist - **Are you including the token in the `Authorization` header as `Bearer <token>`?** - **Is your token definitely an **access** token (not a refresh token) and still valid (not expired)?** - **Is there any issue with spacing, extra characters, or newline breaks in the token?** - **Has the server changed any secret keys recently?** Once you verify these steps, you should be able to avoid the `"Given token not valid for any token type"` error and successfully authenticate your requests.
o1
{"writing_sample":"Failed to analyze the writing sample."}# core/serializers.py from rest_framework import serializers from .models import Author, Persona, ContentPiece from .utils import analyze_writing_sample, generate_content import logging logger = logging.getLogger(__name__) class AuthorSerializer(serializers.ModelSerializer): username = serializers.CharField(source='user.username', read_only=True) email = serializers.EmailField(source='user.email', read_only=True) class Meta: model = Author fields = ['id', 'username', 'email', 'bio', 'created_at'] class PersonaSerializer(serializers.ModelSerializer): writing_sample = serializers.CharField(write_only=True, required=False) content_count = serializers.SerializerMethodField() class Meta: model = Persona fields = [ 'id', 'name', 'description', 'content_count', 'vocabulary_complexity', 'sentence_structure', 'paragraph_organization', 'idiom_usage', 'metaphor_frequency', 'simile_frequency', 'tone', 'punctuation_style', 'contraction_usage', 'pronoun_preference', 'passive_voice_frequency', 'rhetorical_question_usage', 'list_usage_tendency', 'personal_anecdote_inclusion', 'pop_culture_reference_frequency', 'technical_jargon_usage', 'parenthetical_aside_frequency', 'humor_sarcasm_usage', 'emotional_expressiveness', 'emphatic_device_usage', 'quotation_frequency', 'analogy_usage', 'sensory_detail_inclusion', 'onomatopoeia_usage', 'alliteration_frequency', 'word_length_preference', 'foreign_phrase_usage', 'rhetorical_device_usage', 'statistical_data_usage', 'personal_opinion_inclusion', 'transition_usage', 'reader_question_frequency', 'imperative_sentence_usage', 'dialogue_inclusion', 'regional_dialect_usage', 'hedging_language_frequency', 'language_abstraction', 'personal_belief_inclusion', 'repetition_usage', 'subordinate_clause_frequency', 'verb_type_preference', 'sensory_imagery_usage', 'symbolism_usage', 'digression_frequency', 'formality_level', 'reflection_inclusion', 'irony_usage', 'neologism_frequency', 'ellipsis_usage', 'cultural_reference_inclusion', 'stream_of_consciousness_usage', 'openness_to_experience', 'conscientiousness', 'extraversion', 'agreeableness', 'emotional_stability', 'dominant_motivations', 'core_values', 'decision_making_style', 'empathy_level', 'self_confidence', 'risk_taking_tendency', 'idealism_vs_realism', 'conflict_resolution_style', 'relationship_orientation', 'is_active', 'created_at', 'updated_at', 'writing_sample' ] read_only_fields = ['id', 'created_at', 'updated_at', 'content_count'] def get_content_count(self, obj): return obj.contentpiece_set.count() def create(self, validated_data): writing_sample = validated_data.pop('writing_sample', None) author = self.context['request'].user.author validated_data['author'] = author if writing_sample: analyzed_data = analyze_writing_sample(writing_sample) if analyzed_data: # Map analyzed data to individual fields for field, value in analyzed_data.items(): if hasattr(Persona, field): validated_data[field] = value else: logger.error("Failed to analyze writing sample.") raise serializers.ValidationError({"writing_sample": "Failed to analyze the writing sample."}) return super().create(validated_data) class ContentPieceSerializer(serializers.ModelSerializer): persona_name = serializers.CharField(source='persona.name', read_only=True) class Meta: model = ContentPiece fields = ['id', 'title', 'content', 'persona', 'persona_name', 'status', 'tags', 'word_count', 'created_at', 'updated_at', 'published_at'] read_only_fields = ['id', 'word_count', 'created_at', 'updated_at']
Below is some information and guidance for troubleshooting or adapting the posted serializer code and its “Failed to analyze the writing sample” error. It also includes suggestions for integrating Ollama if you’ve switched from OpenAI to an Ollama-based solution. --- ## Why You’re Seeing `"Failed to analyze the writing sample."` In your serializer’s `create()` method: ```python if writing_sample: analyzed_data = analyze_writing_sample(writing_sample) if analyzed_data: # Map analyzed data to individual fields for field, value in analyzed_data.items(): if hasattr(Persona, field): validated_data[field] = value else: logger.error("Failed to analyze writing sample.") raise serializers.ValidationError({"writing_sample": "Failed to analyze the writing sample."}) ``` This `ValidationError` is raised if `analyze_writing_sample(writing_sample)` returns `None`. In other words, your `analyze_writing_sample` utility is failing to produce valid JSON results—often due to: 1. **An exception within the `analyze_writing_sample` function** (HTTP error, malformed response from the language model, etc.). 2. **The language model not returning the expected JSON** (maybe your prompt or the model’s answer is incomplete). 3. **Empty or invalid `writing_sample`** (in real usage, if the sample text is too short or blank, the model might return something unexpected). --- ## Verifying the `analyze_writing_sample` Utility 1. **Check your environment variables** - If you are still using `openai.api_key`, verify that it’s set correctly for OpenAI usage. - If you have switched to Ollama, ensure you have updated the `analyze_writing_sample` function to call your local Ollama server. For instance: ```python # Example snippet calling an Ollama server import requests import re import json def analyze_writing_sample(writing_sample): try: prompt = f"...your analysis instructions...\n\n{writing_sample}" resp = requests.post("http://localhost:11411/generate", json={ "model": "your-ollama-model-name", "prompt": prompt }) resp.raise_for_status() # Ollama responses can be streamed JSON lines. # Collect all lines into one completion string: response_text = "" for line in resp.iter_lines(decode_unicode=True): if line.strip(): data = json.loads(line) response_text += data.get("completion", "") # Attempt to extract JSON from the completion json_str = re.search(r"\{.*\}", response_text, re.DOTALL) if json_str: return json.loads(json_str.group()) else: return None except Exception as e: # Log and return None so that the serializer raises ValidationError print(f"Error analyzing writing sample: {e}") return None ``` - Make sure any function references (`_call_ollama`, `analyze_writing_sample`) line up with the final function name you’re using. 2. **Check your prompt** - If the language model’s response doesn’t include valid JSON, your regex search `re.search(r"\{.*\}", response_text, re.DOTALL)` will fail. Debug the raw output by logging `response_text` so you can see exactly what the model is returning. 3. **Validate input** - If the `writing_sample` is empty or extremely short, the model might return a partial response (or no JSON at all). Consider handling that case separately. --- ## Example of a Complete Flow Using Ollama Below is a simplified example of how your files might look when integrating Ollama: ### `utils.py` ```python import requests import json import re import logging logger = logging.getLogger(__name__) OLLAMA_SERVER_URL = "http://localhost:11411" # Change if needed OLLAMA_MODEL_NAME = "llama2" # Or whichever model you’re using def analyze_writing_sample(writing_sample): """ Call the local Ollama server to analyze the writing sample and return JSON. """ try: # This is your analysis prompt prompt = f""" Please analyze the following writing sample and return your analysis in JSON: {writing_sample} """ payload = { "model": OLLAMA_MODEL_NAME, "prompt": prompt } resp = requests.post(f"{OLLAMA_SERVER_URL}/generate", json=payload) resp.raise_for_status() # Ollama streams lines of JSON, each containing { "completion": "..." } full_text = "" for line in resp.iter_lines(decode_unicode=True): if line.strip(): line_data = json.loads(line) full_text += line_data.get("completion", "") # Attempt to extract JSON from the response text json_match = re.search(r"\{.*\}", full_text, re.DOTALL) if json_match: return json.loads(json_match.group()) else: logger.error("No JSON object found in the response.") return None except Exception as e: logger.error(f"Error analyzing writing sample: {e}") return None def generate_content(persona, prompt): """ Similar approach for generating content using Ollama """ try: # Build prompt from persona persona_prompt = f"Persona traits: {persona} \n\nWrite about: {prompt}" payload = { "model": OLLAMA_MODEL_NAME, "prompt": persona_prompt } resp = requests.post(f"{OLLAMA_SERVER_URL}/generate", json=payload) resp.raise_for_status() # Collate full text from streamed lines full_text = "" for line in resp.iter_lines(decode_unicode=True): if line.strip(): line_data = json.loads(line) full_text += line_data.get("completion", "") return full_text.strip() except Exception as e: logger.error(f"Error generating content: {str(e)}") return None ``` ### `serializers.py` ```python from rest_framework import serializers from .models import Author, Persona, ContentPiece from .utils import analyze_writing_sample, generate_content import logging logger = logging.getLogger(__name__) class AuthorSerializer(serializers.ModelSerializer): username = serializers.CharField(source='user.username', read_only=True) email = serializers.EmailField(source='user.email', read_only=True) class Meta: model = Author fields = ['id', 'username', 'email', 'bio', 'created_at'] class PersonaSerializer(serializers.ModelSerializer): writing_sample = serializers.CharField(write_only=True, required=False) content_count = serializers.SerializerMethodField() class Meta: model = Persona fields = [ 'id', 'name', 'description', 'content_count', # ... all your persona fields ... 'is_active', 'created_at', 'updated_at', 'writing_sample' ] read_only_fields = ['id', 'created_at', 'updated_at', 'content_count'] def get_content_count(self, obj): return obj.contentpiece_set.count() def create(self, validated_data): writing_sample = validated_data.pop('writing_sample', None) author = self.context['request'].user.author validated_data['author'] = author if writing_sample: analyzed_data = analyze_writing_sample(writing_sample) if analyzed_data: # Map fields that exist both in Persona model and in analyzed_data for field, value in analyzed_data.items(): if hasattr(Persona, field): validated_data[field] = value else: logger.error("Failed to analyze writing sample.") raise serializers.ValidationError({ "writing_sample": "Failed to analyze the writing sample." }) return super().create(validated_data) class ContentPieceSerializer(serializers.ModelSerializer): persona_name = serializers.CharField(source='persona.name', read_only=True) class Meta: model = ContentPiece fields = [ 'id', 'title', 'content', 'persona', 'persona_name', 'status', 'tags', 'word_count', 'created_at', 'updated_at', 'published_at' ] read_only_fields = ['id', 'word_count', 'created_at', 'updated_at'] ``` --- ## Debugging Tips 1. **Log the raw model response** Inside `analyze_writing_sample`, do: ```python logger.debug(f"Raw Ollama text: {full_text}") ``` This helps you see exactly what the model returned. 2. **Test with known input** Pass a clearly analyzable text to ensure you get well-formed JSON. If that works, but certain user submissions fail, you may need additional prompt guidance or fallback logic. 3. **Handle Short/Blank Samples** If your sample can be empty, consider skipping the analysis or providing default persona fields rather than calling the model. 4. **Ensure the Ollama server is running** Make sure you’ve launched Ollama with the correct model: ```bash ollama serve --model /path/to/model ``` or if you’re using a default, just: ```bash ollama serve ``` Then confirm `http://localhost:11411/generate` is responding as expected (e.g., test with `curl`). 5. **Validate the JSON** If your prompt instructs the model to return JSON but the returned text includes disclaimers, empty lines, or markup, the regex might fail. You may need to refine your prompt to produce *only* the JSON. --- ## Bottom Line - The message `"Failed to analyze the writing sample."` is expected behavior if `analyze_writing_sample` returns `None`. - Ensure your `analyze_writing_sample` function (whether it calls OpenAI or Ollama) returns valid JSON on success. - Confirm environment variables and server are configured for Ollama if you have switched away from OpenAI. - Log and inspect the raw response to pinpoint why JSON extraction fails.
o1
⑂ thread forked here
ensure that the fields for serializers.py and utils.py line up: serializers.py: # core/serializers.py from rest_framework import serializers from .models import Author, Persona, ContentPiece from .utils import analyze_writing_sample, generate_content import logging logger = logging.getLogger(__name__) class AuthorSerializer(serializers.ModelSerializer): username = serializers.CharField(source='user.username', read_only=True) email = serializers.EmailField(source='user.email', read_only=True) class Meta: model = Author fields = ['id', 'username', 'email', 'bio', 'created_at'] class PersonaSerializer(serializers.ModelSerializer): writing_sample = serializers.CharField(write_only=True, required=False) content_count = serializers.SerializerMethodField() class Meta: model = Persona fields = [ 'id', 'name', 'description', 'content_count', 'vocabulary_complexity', 'sentence_structure', 'paragraph_organization', 'idiom_usage', 'metaphor_frequency', 'simile_frequency', 'tone', 'punctuation_style', 'contraction_usage', 'pronoun_preference', 'passive_voice_frequency', 'rhetorical_question_usage', 'list_usage_tendency', 'personal_anecdote_inclusion', 'pop_culture_reference_frequency', 'technical_jargon_usage', 'parenthetical_aside_frequency', 'humor_sarcasm_usage', 'emotional_expressiveness', 'emphatic_device_usage', 'quotation_frequency', 'analogy_usage', 'sensory_detail_inclusion', 'onomatopoeia_usage', 'alliteration_frequency', 'word_length_preference', 'foreign_phrase_usage', 'rhetorical_device_usage', 'statistical_data_usage', 'personal_opinion_inclusion', 'transition_usage', 'reader_question_frequency', 'imperative_sentence_usage', 'dialogue_inclusion', 'regional_dialect_usage', 'hedging_language_frequency', 'language_abstraction', 'personal_belief_inclusion', 'repetition_usage', 'subordinate_clause_frequency', 'verb_type_preference', 'sensory_imagery_usage', 'symbolism_usage', 'digression_frequency', 'formality_level', 'reflection_inclusion', 'irony_usage', 'neologism_frequency', 'ellipsis_usage', 'cultural_reference_inclusion', 'stream_of_consciousness_usage', 'openness_to_experience', 'conscientiousness', 'extraversion', 'agreeableness', 'emotional_stability', 'dominant_motivations', 'core_values', 'decision_making_style', 'empathy_level', 'self_confidence', 'risk_taking_tendency', 'idealism_vs_realism', 'conflict_resolution_style', 'relationship_orientation', 'is_active', 'created_at', 'updated_at', 'writing_sample' ] read_only_fields = ['id', 'created_at', 'updated_at', 'content_count'] def get_content_count(self, obj): return obj.contentpiece_set.count() def create(self, validated_data): writing_sample = validated_data.pop('writing_sample', None) author = self.context['request'].user.author validated_data['author'] = author if writing_sample: analyzed_data = analyze_writing_sample(writing_sample) if analyzed_data: # Map analyzed data to individual fields for field, value in analyzed_data.items(): if hasattr(Persona, field): validated_data[field] = value else: logger.error("Failed to analyze writing sample.") raise serializers.ValidationError({"writing_sample": "Failed to analyze the writing sample."}) return super().create(validated_data) class ContentPieceSerializer(serializers.ModelSerializer): persona_name = serializers.CharField(source='persona.name', read_only=True) class Meta: model = ContentPiece fields = ['id', 'title', 'content', 'persona', 'persona_name', 'status', 'tags', 'word_count', 'created_at', 'updated_at', 'published_at'] read_only_fields = ['id', 'word_count', 'created_at', 'updated_at']. ------- utils.py: import logging import requests import json import os import re from dotenv import load_dotenv # Configure logger logger = logging.getLogger(__name__) # Load environment variables load_dotenv() OLLAMA_SERVER_URL = os.getenv('OLLAMA_SERVER_URL', 'http://localhost:11411') # e.g. http://localhost:11411 OLLAMA_MODEL_NAME = os.getenv('OLLAMA_MODEL_NAME', 'llama3.2') # or whichever model you have def _call_ollama(prompt, model=OLLAMA_MODEL_NAME, temperature=1, max_tokens=500): """ Helper function to call Ollama's /generate endpoint with the given parameters. prompt (str): The text or conversation to send to the model. model (str): The model to use on Ollama (default: 'llama2'). temperature (float): Temperature for text generation (default: 1). max_tokens (int): Token limit for the output from Ollama. """ try: payload = { "prompt": prompt, "model": model, "temperature": temperature, "num_ctx": 2048, # Adjust if you need more context length "max_tokens": max_tokens } response = requests.post(f"{OLLAMA_SERVER_URL}/api/generate", json=payload) response.raise_for_status() # Ollama's response is typically a stream of JSON lines. We can collect all text segments: response_text = "" for line in response.iter_lines(decode_unicode=True): if line.strip(): # Each line is JSON with a "completion" key data = json.loads(line) response_text += data.get("completion", "") return response_text.strip() except Exception as e: logger.error(f"Error calling Ollama: {e}") return None def analyze_writing_sample(writing_sample): """ Analyzes a given writing sample to assess various characteristics. Parameters: - writing_sample (str): The text to analyze. Returns: - dict: Analysis results in JSON format. """ try: # Build the prompt that will be sent to Ollama prompt = f''' Please analyze the writing style and personality of the given writing sample. Provide a detailed assessment of their characteristics using the following template. Rate each applicable characteristic on a scale of 1-10 where relevant, or provide a descriptive value. Return the results in a JSON format. "name": "[Author/Character Name]", "vocabulary_complexity": [1-10], "sentence_structure": "[simple/complex/varied]", "paragraph_organization": "[structured/loose/stream-of-consciousness]", "idiom_usage": [1-10], "metaphor_frequency": [1-10], "simile_frequency": [1-10], "tone": "[formal/informal/academic/conversational/etc.]", "punctuation_style": "[minimal/heavy/unconventional]", "contraction_usage": [1-10], "pronoun_preference": "[first-person/third-person/etc.]", "passive_voice_frequency": [1-10], "rhetorical_question_usage": [1-10], "list_usage_tendency": [1-10], "personal_anecdote_inclusion": [1-10], "pop_culture_reference_frequency": [1-10], "technical_jargon_usage": [1-10], "parenthetical_aside_frequency": [1-10], "humor_sarcasm_usage": [1-10], "emotional_expressiveness": [1-10], "emphatic_device_usage": [1-10], "quotation_frequency": [1-10], "analogy_usage": [1-10], "sensory_detail_inclusion": [1-10], "onomatopoeia_usage": [1-10], "alliteration_frequency": [1-10], "word_length_preference": "[short/long/varied]", "foreign_phrase_usage": [1-10], "rhetorical_device_usage": [1-10], "statistical_data_usage": [1-10], "personal_opinion_inclusion": [1-10], "transition_usage": [1-10], "reader_question_frequency": [1-10], "imperative_sentence_usage": [1-10], "dialogue_inclusion": [1-10], "regional_dialect_usage": [1-10], "hedging_language_frequency": [1-10], "language_abstraction": "[concrete/abstract/mixed]", "personal_belief_inclusion": [1-10], "repetition_usage": [1-10], "subordinate_clause_frequency": [1-10], "verb_type_preference": "[active/stative/mixed]", "sensory_imagery_usage": [1-10], "symbolism_usage": [1-10], "digression_frequency": [1-10], "formality_level": [1-10], "reflection_inclusion": [1-10], "irony_usage": [1-10], "neologism_frequency": [1-10], "ellipsis_usage": [1-10], "cultural_reference_inclusion": [1-10], "stream_of_consciousness_usage": [1-10], "openness_to_experience": [1-10], "conscientiousness": [1-10], "extraversion": [1-10], "agreeableness": [1-10], "emotional_stability": [1-10], "dominant_motivations": "[achievement/affiliation/power/etc.]", "core_values": "[integrity/freedom/knowledge/etc.]", "decision_making_style": "[analytical/intuitive/spontaneous/etc.]", "empathy_level": [1-10], "self_confidence": [1-10], "risk_taking_tendency": [1-10], "idealism_vs_realism": "[idealistic/realistic/mixed]", "conflict_resolution_style": "[assertive/collaborative/avoidant/etc.]", "relationship_orientation": "[independent/communal/mixed]", "emotional_response_tendency": "[calm/reactive/intense]", "creativity_level": [1-10], "age": "[age or age range]", "gender": "[gender]", "education_level": "[highest level of education]", "professional_background": "[brief description]", "cultural_background": "[brief description]", "primary_language": "[language]", "language_fluency": "[native/fluent/intermediate/beginner]", Writing Sample: {writing_sample} ''' # Call Ollama ollama_response = _call_ollama(prompt, model=OLLAMA_MODEL_NAME, temperature=1) if not ollama_response: logger.error("No response from Ollama.") return None logger.debug(f"Ollama response: {ollama_response}") # Extract JSON from the assistant's message json_str = re.search(r'\{.*\}', ollama_response, re.DOTALL) if json_str: analyzed_data = json.loads(json_str.group()) else: logger.error("No JSON object found in the response.") return None return analyzed_data except Exception as e: logger.error(f"Error with Ollama API: {e}") return None def generate_content(persona, prompt): """ Generates content based on a given persona and prompt. Parameters: - persona (Persona): The persona object with individual fields. - prompt (str): The prompt to write about. Returns: - str: The generated content. """ try: # Convert persona fields into a format suitable for the prompt persona_traits = { "Writing Style": { "vocabulary_complexity": f"{persona.vocabulary_complexity}/10", "sentence_structure": persona.sentence_structure, "paragraph_organization": persona.paragraph_organization, "tone": persona.tone, "punctuation_style": persona.punctuation_style, "pronoun_preference": persona.pronoun_preference, "formality_level": f"{persona.formality_level}/10", }, "Language Patterns": { "idiom_usage": f"{persona.idiom_usage}/10", "metaphor_frequency": f"{persona.metaphor_frequency}/10", "simile_frequency": f"{persona.simile_frequency}/10", "technical_jargon_usage": f"{persona.technical_jargon_usage}/10", "humor_sarcasm_usage": f"{persona.humor_sarcasm_usage}/10", }, "Personality": { "openness_to_experience": f"{persona.openness_to_experience}/10", "conscientiousness": f"{persona.conscientiousness}/10", "extraversion": f"{persona.extraversion}/10", "agreeableness": f"{persona.agreeableness}/10", "emotional_stability": f"{persona.emotional_stability}/10", "dominant_motivations": persona.dominant_motivations, "core_values": persona.core_values, "decision_making_style": persona.decision_making_style, } } # Create the system prompt system_prompt = f"""You are a writer with the following characteristics: Writing Style: {persona_traits['Writing Style']} Language Patterns: {persona_traits['Language Patterns']} Personality: {persona_traits['Personality']} Write in a way that naturally reflects these characteristics. The response should include a title. """ # Combine system prompt and user prompt combined_prompt = f"{system_prompt}\n\nWrite about: {prompt}" # Call Ollama ollama_response = _call_ollama(combined_prompt, model=OLLAMA_MODEL_NAME, temperature=1, max_tokens=5000) if not ollama_response: logger.error("No response from Ollama.") return None return ollama_response except Exception as e: logger.error(f"Error generating content: {str(e)}") return None def save_blog_post(blog_post, title): """ Saves a blog post to a file. Parameters: - blog_post (str): The content of the blog post. - title (str): The title of the blog post. """ # Implement if needed pass
Below is an updated, **aligned** version of your `serializers.py` and `utils.py` that ensures all fields match between the JSON produced in `analyze_writing_sample` and the fields declared in `PersonaSerializer`. --- ## Important Notes on Field Alignment 1. **Extra Fields in the JSON** The `analyze_writing_sample` function returns some fields that do **not** exist on your `Persona` model, such as: - `age` - `gender` - `education_level` - `professional_background` - `cultural_background` - `primary_language` - `language_fluency` By default, the serializer code uses `if hasattr(Persona, field): validated_data[field] = value`, so fields not defined in the `Persona` model will be skipped without error. If you **want** these fields to persist, you should add them to your `Persona` model and serializer fields. 2. **Matching Fields** The fields below **are** defined on the `Persona` model (and appear in your serializer) and will be updated when the JSON is returned by `analyze_writing_sample`: ``` name vocabulary_complexity sentence_structure paragraph_organization idiom_usage metaphor_frequency simile_frequency tone punctuation_style contraction_usage pronoun_preference passive_voice_frequency rhetorical_question_usage list_usage_tendency personal_anecdote_inclusion pop_culture_reference_frequency technical_jargon_usage parenthetical_aside_frequency humor_sarcasm_usage emotional_expressiveness emphatic_device_usage quotation_frequency analogy_usage sensory_detail_inclusion onomatopoeia_usage alliteration_frequency word_length_preference foreign_phrase_usage rhetorical_device_usage statistical_data_usage personal_opinion_inclusion transition_usage reader_question_frequency imperative_sentence_usage dialogue_inclusion regional_dialect_usage hedging_language_frequency language_abstraction personal_belief_inclusion repetition_usage subordinate_clause_frequency verb_type_preference sensory_imagery_usage symbolism_usage digression_frequency formality_level reflection_inclusion irony_usage neologism_frequency ellipsis_usage cultural_reference_inclusion stream_of_consciousness_usage openness_to_experience conscientiousness extraversion agreeableness emotional_stability dominant_motivations core_values decision_making_style empathy_level self_confidence risk_taking_tendency idealism_vs_realism conflict_resolution_style relationship_orientation ``` 3. **Skipping Unmatched Fields** Any field in the JSON that is **not** on the `Persona` model is simply ignored. This ensures your code does not break if the LLM returns extra fields (like `age`). 4. **Ollama Endpoint** The sample below assumes Ollama is running on `http://localhost:11411/api/generate`. If your server is listening at a different path (e.g. `/generate` without `/api/`), be sure to update that in `_call_ollama`. --- ## `serializers.py` ```python # core/serializers.py from rest_framework import serializers from .models import Author, Persona, ContentPiece from .utils import analyze_writing_sample, generate_content import logging logger = logging.getLogger(__name__) class AuthorSerializer(serializers.ModelSerializer): username = serializers.CharField(source='user.username', read_only=True) email = serializers.EmailField(source='user.email', read_only=True) class Meta: model = Author fields = ['id', 'username', 'email', 'bio', 'created_at'] class PersonaSerializer(serializers.ModelSerializer): writing_sample = serializers.CharField(write_only=True, required=False) content_count = serializers.SerializerMethodField() class Meta: model = Persona fields = [ 'id', 'name', 'description', 'content_count', 'vocabulary_complexity', 'sentence_structure', 'paragraph_organization', 'idiom_usage', 'metaphor_frequency', 'simile_frequency', 'tone', 'punctuation_style', 'contraction_usage', 'pronoun_preference', 'passive_voice_frequency', 'rhetorical_question_usage', 'list_usage_tendency', 'personal_anecdote_inclusion', 'pop_culture_reference_frequency', 'technical_jargon_usage', 'parenthetical_aside_frequency', 'humor_sarcasm_usage', 'emotional_expressiveness', 'emphatic_device_usage', 'quotation_frequency', 'analogy_usage', 'sensory_detail_inclusion', 'onomatopoeia_usage', 'alliteration_frequency', 'word_length_preference', 'foreign_phrase_usage', 'rhetorical_device_usage', 'statistical_data_usage', 'personal_opinion_inclusion', 'transition_usage', 'reader_question_frequency', 'imperative_sentence_usage', 'dialogue_inclusion', 'regional_dialect_usage', 'hedging_language_frequency', 'language_abstraction', 'personal_belief_inclusion', 'repetition_usage', 'subordinate_clause_frequency', 'verb_type_preference', 'sensory_imagery_usage', 'symbolism_usage', 'digression_frequency', 'formality_level', 'reflection_inclusion', 'irony_usage', 'neologism_frequency', 'ellipsis_usage', 'cultural_reference_inclusion', 'stream_of_consciousness_usage', 'openness_to_experience', 'conscientiousness', 'extraversion', 'agreeableness', 'emotional_stability', 'dominant_motivations', 'core_values', 'decision_making_style', 'empathy_level', 'self_confidence', 'risk_taking_tendency', 'idealism_vs_realism', 'conflict_resolution_style', 'relationship_orientation', 'is_active', 'created_at', 'updated_at', 'writing_sample' ] read_only_fields = ['id', 'created_at', 'updated_at', 'content_count'] def get_content_count(self, obj): return obj.contentpiece_set.count() def create(self, validated_data): writing_sample = validated_data.pop('writing_sample', None) author = self.context['request'].user.author validated_data['author'] = author if writing_sample: # Attempt to analyze the sample via Ollama analyzed_data = analyze_writing_sample(writing_sample) if analyzed_data: # Map fields from analyzed_data to validated_data if they exist on Persona for field, value in analyzed_data.items(): if hasattr(Persona, field): validated_data[field] = value else: logger.error("Failed to analyze writing sample.") raise serializers.ValidationError({"writing_sample": "Failed to analyze the writing sample."}) return super().create(validated_data) class ContentPieceSerializer(serializers.ModelSerializer): persona_name = serializers.CharField(source='persona.name', read_only=True) class Meta: model = ContentPiece fields = [ 'id', 'title', 'content', 'persona', 'persona_name', 'status', 'tags', 'word_count', 'created_at', 'updated_at', 'published_at' ] read_only_fields = ['id', 'word_count', 'created_at', 'updated_at'] ``` --- ## `utils.py` ```python import logging import requests import json import os import re from dotenv import load_dotenv # Configure logger logger = logging.getLogger(__name__) # Load environment variables load_dotenv() OLLAMA_SERVER_URL = os.getenv('OLLAMA_SERVER_URL', 'http://localhost:11411') OLLAMA_MODEL_NAME = os.getenv('OLLAMA_MODEL_NAME', 'llama3.2') def _call_ollama(prompt, model=OLLAMA_MODEL_NAME, temperature=1, max_tokens=500): """ Helper function to call Ollama's /generate (or /api/generate) endpoint. Adjust the endpoint path to match your Ollama server setup. """ try: payload = { "prompt": prompt, "model": model, "temperature": temperature, "num_ctx": 2048, # Adjust if you need more context length "max_tokens": max_tokens } # IMPORTANT: adjust the endpoint if it's just "/generate" instead of "/api/generate" response = requests.post(f"{OLLAMA_SERVER_URL}/api/generate", json=payload) response.raise_for_status() # Ollama typically streams its output line by line in JSON response_text = "" for line in response.iter_lines(decode_unicode=True): if line.strip(): data = json.loads(line) response_text += data.get("completion", "") return response_text.strip() except Exception as e: logger.error(f"Error calling Ollama: {e}") return None def analyze_writing_sample(writing_sample): """ Analyzes a given writing sample to assess various characteristics. Returns a dictionary with fields that line up to Persona fields in serializers.py. """ try: # Build the prompt that will be sent to Ollama prompt = f''' Please analyze the writing style and personality of the given writing sample. Provide a detailed assessment of their characteristics using the following template. Rate each applicable characteristic on a scale of 1-10 where relevant, or provide a descriptive value. Return the results in a JSON format. "name": "[Author/Character Name]", "vocabulary_complexity": [1-10], "sentence_structure": "[simple/complex/varied]", "paragraph_organization": "[structured/loose/stream-of-consciousness]", "idiom_usage": [1-10], "metaphor_frequency": [1-10], "simile_frequency": [1-10], "tone": "[formal/informal/academic/conversational/etc.]", "punctuation_style": "[minimal/heavy/unconventional]", "contraction_usage": [1-10], "pronoun_preference": "[first-person/third-person/etc.]", "passive_voice_frequency": [1-10], "rhetorical_question_usage": [1-10], "list_usage_tendency": [1-10], "personal_anecdote_inclusion": [1-10], "pop_culture_reference_frequency": [1-10], "technical_jargon_usage": [1-10], "parenthetical_aside_frequency": [1-10], "humor_sarcasm_usage": [1-10], "emotional_expressiveness": [1-10], "emphatic_device_usage": [1-10], "quotation_frequency": [1-10], "analogy_usage": [1-10], "sensory_detail_inclusion": [1-10], "onomatopoeia_usage": [1-10], "alliteration_frequency": [1-10], "word_length_preference": "[short/long/varied]", "foreign_phrase_usage": [1-10], "rhetorical_device_usage": [1-10], "statistical_data_usage": [1-10], "personal_opinion_inclusion": [1-10], "transition_usage": [1-10], "reader_question_frequency": [1-10], "imperative_sentence_usage": [1-10], "dialogue_inclusion": [1-10], "regional_dialect_usage": [1-10], "hedging_language_frequency": [1-10], "language_abstraction": "[concrete/abstract/mixed]", "personal_belief_inclusion": [1-10], "repetition_usage": [1-10], "subordinate_clause_frequency": [1-10], "verb_type_preference": "[active/stative/mixed]", "sensory_imagery_usage": [1-10], "symbolism_usage": [1-10], "digression_frequency": [1-10], "formality_level": [1-10], "reflection_inclusion": [1-10], "irony_usage": [1-10], "neologism_frequency": [1-10], "ellipsis_usage": [1-10], "cultural_reference_inclusion": [1-10], "stream_of_consciousness_usage": [1-10], "openness_to_experience": [1-10], "conscientiousness": [1-10], "extraversion": [1-10], "agreeableness": [1-10], "emotional_stability": [1-10], "dominant_motivations": "[achievement/affiliation/power/etc.]", "core_values": "[integrity/freedom/knowledge/etc.]", "decision_making_style": "[analytical/intuitive/spontaneous/etc.]", "empathy_level": [1-10], "self_confidence": [1-10], "risk_taking_tendency": [1-10], "idealism_vs_realism": "[idealistic/realistic/mixed]", "conflict_resolution_style": "[assertive/collaborative/avoidant/etc.]", "relationship_orientation": "[independent/communal/mixed]", "emotional_response_tendency": "[calm/reactive/intense]", "creativity_level": [1-10], "age": "[age or age range]", "gender": "[gender]", "education_level": "[highest level of education]", "professional_background": "[brief description]", "cultural_background": "[brief description]", "primary_language": "[language]", "language_fluency": "[native/fluent/intermediate/beginner]", Writing Sample: {writing_sample} ''' ollama_response = _call_ollama(prompt, model=OLLAMA_MODEL_NAME, temperature=1) if not ollama_response: logger.error("No response from Ollama.") return None logger.debug(f"Ollama response: {ollama_response}") # Attempt to extract JSON from the raw completion text json_match = re.search(r'\{.*\}', ollama_response, re.DOTALL) if json_match: try: analyzed_data = json.loads(json_match.group()) except json.JSONDecodeError as ex: logger.error(f"JSON decode error: {ex}") return None return analyzed_data else: logger.error("No JSON object found in the response.") return None except Exception as e: logger.error(f"Error with Ollama API: {e}") return None def generate_content(persona, prompt): """ Generates content based on a given persona and prompt. Maps the persona's fields into a 'persona_traits' dict for the prompt. """ try: persona_traits = { "Writing Style": { "vocabulary_complexity": f"{persona.vocabulary_complexity}/10", "sentence_structure": persona.sentence_structure, "paragraph_organization": persona.paragraph_organization, "tone": persona.tone, "punctuation_style": persona.punctuation_style, "pronoun_preference": persona.pronoun_preference, "formality_level": f"{persona.formality_level}/10", }, "Language Patterns": { "idiom_usage": f"{persona.idiom_usage}/10", "metaphor_frequency": f"{persona.metaphor_frequency}/10", "simile_frequency": f"{persona.simile_frequency}/10", "technical_jargon_usage": f"{persona.technical_jargon_usage}/10", "humor_sarcasm_usage": f"{persona.humor_sarcasm_usage}/10", }, "Personality": { "openness_to_experience": f"{persona.openness_to_experience}/10", "conscientiousness": f"{persona.conscientiousness}/10", "extraversion": f"{persona.extraversion}/10", "agreeableness": f"{persona.agreeableness}/10", "emotional_stability": f"{persona.emotional_stability}/10", "dominant_motivations": persona.dominant_motivations, "core_values": persona.core_values, "decision_making_style": persona.decision_making_style, } } # System prompt describing how to write system_prompt = f"""You are a writer with the following characteristics: Writing Style: {persona_traits['Writing Style']} Language Patterns: {persona_traits['Language Patterns']} Personality: {persona_traits['Personality']} Write in a way that naturally reflects these characteristics. The response should include a title. """ # Combine persona prompt with user prompt combined_prompt = f"{system_prompt}\n\nWrite about: {prompt}" # Call Ollama ollama_response = _call_ollama(combined_prompt, model=OLLAMA_MODEL_NAME, temperature=1, max_tokens=5000) if not ollama_response: logger.error("No response from Ollama.") return None return ollama_response.strip() except Exception as e: logger.error(f"Error generating content: {str(e)}") return None def save_blog_post(blog_post, title): """ Example placeholder for saving blog content to a file or other location. """ # Implement if needed pass ``` --- ### Final Checklist 1. **Field Overlap**: All fields that the `analyze_writing_sample` function might produce (e.g., `vocabulary_complexity`, `tone`, `name`, etc.) are included in the `PersonaSerializer`’s `fields` list **or** intentionally skipped by design. 2. **`hasattr(Persona, field)`**: Safely ensures any extra fields in the JSON do not break the code. 3. **Model Adjustments**: If you decide to store `age`, `gender`, etc., just add them to your `Persona` model and add them to the serializer’s `fields`. 4. **Ollama Endpoint**: Make sure your Ollama server runs at the correct URL (whether `http://localhost:11411/generate` or `http://localhost:11411/api/generate`). 5. **Logging**: If JSON extraction fails, the code logs a clear error message that no JSON was found. This helps debug quickly if Ollama’s response didn’t match the expected format. With these files in place, your field mapping is consistent between the Ollama-based `analyze_writing_sample` function and your `PersonaSerializer`.
o1
Below is a consolidated explanation and reference code to ensure your **`serializers.py`** and **`utils.py`** are aligned—i.e., that the JSON fields produced by `analyze_writing_sample` match the fields defined on your **`Persona`** model and exposed through **`PersonaSerializer`**. ## 1. Confirm Your `Persona` Model Fields First, make sure the `Persona` model indeed has all the fields you listed in `PersonaSerializer`. (Sometimes, developers add fields in the serializer but not in the model, or vice versa.) A simplified example of how your `Persona` model might look: ```python # models.py from django.db import models from django.contrib.auth.models import User class Author(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) bio = models.TextField(blank=True, null=True) created_at = models.DateTimeField(auto_now_add=True) class Persona(models.Model): author = models.ForeignKey(Author, on_delete=models.CASCADE) name = models.CharField(max_length=200) description = models.TextField(blank=True, null=True) # Example fields matching the serializer: vocabulary_complexity = models.IntegerField(null=True, blank=True) sentence_structure = models.CharField(max_length=50, blank=True, null=True) paragraph_organization = models.CharField(max_length=50, blank=True, null=True) idiom_usage = models.IntegerField(null=True, blank=True) metaphor_frequency = models.IntegerField(null=True, blank=True) simile_frequency = models.IntegerField(null=True, blank=True) tone = models.CharField(max_length=50, blank=True, null=True) punctuation_style = models.CharField(max_length=50, blank=True, null=True) contraction_usage = models.IntegerField(null=True, blank=True) pronoun_preference = models.CharField(max_length=50, blank=True, null=True) passive_voice_frequency = models.IntegerField(null=True, blank=True) rhetorical_question_usage = models.IntegerField(null=True, blank=True) list_usage_tendency = models.IntegerField(null=True, blank=True) personal_anecdote_inclusion = models.IntegerField(null=True, blank=True) pop_culture_reference_frequency = models.IntegerField(null=True, blank=True) technical_jargon_usage = models.IntegerField(null=True, blank=True) parenthetical_aside_frequency = models.IntegerField(null=True, blank=True) humor_sarcasm_usage = models.IntegerField(null=True, blank=True) emotional_expressiveness = models.IntegerField(null=True, blank=True) emphatic_device_usage = models.IntegerField(null=True, blank=True) quotation_frequency = models.IntegerField(null=True, blank=True) analogy_usage = models.IntegerField(null=True, blank=True) sensory_detail_inclusion = models.IntegerField(null=True, blank=True) onomatopoeia_usage = models.IntegerField(null=True, blank=True) alliteration_frequency = models.IntegerField(null=True, blank=True) word_length_preference = models.CharField(max_length=50, blank=True, null=True) foreign_phrase_usage = models.IntegerField(null=True, blank=True) rhetorical_device_usage = models.IntegerField(null=True, blank=True) statistical_data_usage = models.IntegerField(null=True, blank=True) personal_opinion_inclusion = models.IntegerField(null=True, blank=True) transition_usage = models.IntegerField(null=True, blank=True) reader_question_frequency = models.IntegerField(null=True, blank=True) imperative_sentence_usage = models.IntegerField(null=True, blank=True) dialogue_inclusion = models.IntegerField(null=True, blank=True) regional_dialect_usage = models.IntegerField(null=True, blank=True) hedging_language_frequency = models.IntegerField(null=True, blank=True) language_abstraction = models.CharField(max_length=50, blank=True, null=True) personal_belief_inclusion = models.IntegerField(null=True, blank=True) repetition_usage = models.IntegerField(null=True, blank=True) subordinate_clause_frequency = models.IntegerField(null=True, blank=True) verb_type_preference = models.CharField(max_length=50, blank=True, null=True) sensory_imagery_usage = models.IntegerField(null=True, blank=True) symbolism_usage = models.IntegerField(null=True, blank=True) digression_frequency = models.IntegerField(null=True, blank=True) formality_level = models.IntegerField(null=True, blank=True) reflection_inclusion = models.IntegerField(null=True, blank=True) irony_usage = models.IntegerField(null=True, blank=True) neologism_frequency = models.IntegerField(null=True, blank=True) ellipsis_usage = models.IntegerField(null=True, blank=True) cultural_reference_inclusion = models.IntegerField(null=True, blank=True) stream_of_consciousness_usage = models.IntegerField(null=True, blank=True) openness_to_experience = models.IntegerField(null=True, blank=True) conscientiousness = models.IntegerField(null=True, blank=True) extraversion = models.IntegerField(null=True, blank=True) agreeableness = models.IntegerField(null=True, blank=True) emotional_stability = models.IntegerField(null=True, blank=True) dominant_motivations = models.CharField(max_length=100, blank=True, null=True) core_values = models.CharField(max_length=100, blank=True, null=True) decision_making_style = models.CharField(max_length=100, blank=True, null=True) empathy_level = models.IntegerField(null=True, blank=True) self_confidence = models.IntegerField(null=True, blank=True) risk_taking_tendency = models.IntegerField(null=True, blank=True) idealism_vs_realism = models.CharField(max_length=50, blank=True, null=True) conflict_resolution_style = models.CharField(max_length=50, blank=True, null=True) relationship_orientation = models.CharField(max_length=50, blank=True, null=True) is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return self.name ``` If some of these fields don’t exist in your actual model, remove them from the serializer or from the prompt template. Conversely, if you need a field that’s missing, add it to the model. --- ## 2. Align the JSON Template in `utils.py` The prompt in `analyze_writing_sample` returns JSON like this: ```json { "name": "[Author/Character Name]", "vocabulary_complexity": 7, "sentence_structure": "complex", "paragraph_organization": "stream-of-consciousness", ... "dominant_motivations": "achievement", "core_values": "integrity", "decision_making_style": "analytical", ... } ``` Your **`PersonaSerializer`** checks if each of these keys exists on the `Persona` model. If so, it sets that field: ```python for field, value in analyzed_data.items(): if hasattr(Persona, field): validated_data[field] = value ``` Hence, if your prompt includes: - `"name": "..."` - `"vocabulary_complexity": 7` - `"tone": "formal"` - etc. …each of these fields will be assigned to the `Persona` instance if they exist in the `Persona` model. **Important Note**: Some fields in the prompt (like `"age"`, `"gender"`, `"education_level"`, etc.) might **not** exist in your Persona model. The loop `if hasattr(Persona, field)` simply skips them. That’s fine as long as you understand they won’t be stored unless you add those fields to the model. --- ## 3. Make Sure the Serializer Fields Match Your `PersonaSerializer` lists: ```python fields = [ 'id', 'name', 'description', 'content_count', 'vocabulary_complexity', 'sentence_structure', 'paragraph_organization', 'idiom_usage', 'metaphor_frequency', 'simile_frequency', 'tone', 'punctuation_style', 'contraction_usage', 'pronoun_preference', 'passive_voice_frequency', 'rhetorical_question_usage', 'list_usage_tendency', 'personal_anecdote_inclusion', 'pop_culture_reference_frequency', 'technical_jargon_usage', 'parenthetical_aside_frequency', 'humor_sarcasm_usage', 'emotional_expressiveness', 'emphatic_device_usage', 'quotation_frequency', 'analogy_usage', 'sensory_detail_inclusion', 'onomatopoeia_usage', 'alliteration_frequency', 'word_length_preference', 'foreign_phrase_usage', 'rhetorical_device_usage', 'statistical_data_usage', 'personal_opinion_inclusion', 'transition_usage', 'reader_question_frequency', 'imperative_sentence_usage', 'dialogue_inclusion', 'regional_dialect_usage', 'hedging_language_frequency', 'language_abstraction', 'personal_belief_inclusion', 'repetition_usage', 'subordinate_clause_frequency', 'verb_type_preference', 'sensory_imagery_usage', 'symbolism_usage', 'digression_frequency', 'formality_level', 'reflection_inclusion', 'irony_usage', 'neologism_frequency', 'ellipsis_usage', 'cultural_reference_inclusion', 'stream_of_consciousness_usage', 'openness_to_experience', 'conscientiousness', 'extraversion', 'agreeableness', 'emotional_stability', 'dominant_motivations', 'core_values', 'decision_making_style', 'empathy_level', 'self_confidence', 'risk_taking_tendency', 'idealism_vs_realism', 'conflict_resolution_style', 'relationship_orientation', 'is_active', 'created_at', 'updated_at', 'writing_sample' ] ``` - These must match fields on the **`Persona`** model. - The code in `analyze_writing_sample` or `_call_ollama` must return JSON that (at least) includes the fields you want to set in the `Persona`. If the LLM includes additional keys (like `"age"`, `"gender"`), it won’t break the code—`hasattr(Persona, field)` simply won’t find them in the model, so they get ignored. --- ## 4. Example of Final `analyze_writing_sample` + `PersonaSerializer` Here is how they work together in a typical request cycle: 1. **Client** sends a `POST` to create a Persona, including a `writing_sample` text. 2. **`PersonaSerializer.create()`** sees `writing_sample`, calls: ```python analyzed_data = analyze_writing_sample(writing_sample) ``` 3. **`analyze_writing_sample`** (in `utils.py`) sends a prompt to Ollama, returns JSON: ```json { "name": "John Doe", "vocabulary_complexity": 7, "sentence_structure": "varied", "paragraph_organization": "structured", "tone": "informal", "dominant_motivations": "achievement", "core_values": "freedom", "decision_making_style": "intuitive", ... } ``` 4. The serializer loops over `analyzed_data.items()` and assigns matching fields to `validated_data`. 5. A new `Persona` instance is created with the merged data (`author` plus fields gleaned from the LLM). --- ## 5. Generating Content with `generate_content` Your `generate_content` function uses only a subset of those fields to build the prompt: ```python persona_traits = { "Writing Style": { "vocabulary_complexity": f"{persona.vocabulary_complexity}/10", "sentence_structure": persona.sentence_structure, "paragraph_organization": persona.paragraph_organization, "tone": persona.tone, "punctuation_style": persona.punctuation_style, "pronoun_preference": persona.pronoun_preference, "formality_level": f"{persona.formality_level}/10", }, "Language Patterns": { "idiom_usage": f"{persona.idiom_usage}/10", "metaphor_frequency": f"{persona.metaphor_frequency}/10", "simile_frequency": f"{persona.simile_frequency}/10", "technical_jargon_usage": f"{persona.technical_jargon_usage}/10", "humor_sarcasm_usage": f"{persona.humor_sarcasm_usage}/10", }, "Personality": { "openness_to_experience": f"{persona.openness_to_experience}/10", "conscientiousness": f"{persona.conscientiousness}/10", "extraversion": f"{persona.extraversion}/10", "agreeableness": f"{persona.agreeableness}/10", "emotional_stability": f"{persona.emotional_stability}/10", "dominant_motivations": persona.dominant_motivations, "core_values": persona.core_values, "decision_making_style": persona.decision_making_style, } } ``` - This is fine, and it doesn’t need to include **every** field. - If you want the LLM to factor in more attributes (e.g., `passive_voice_frequency`), just expand the dictionary. --- ## 6. Final Check - **All fields that appear in your prompt JSON** (for analysis) or in your **Serializer** should correspond to real model fields. - If the language model returns a field that your model doesn’t have, you can either add it to the model or ignore it. - Make sure you have no typos in field names—both in the prompt (e.g., `"vocabulary_complexity"`) and in the model/serializer (also `"vocabulary_complexity"`). With these steps in place, your code in **`serializers.py`** and **`utils.py`** should be properly aligned and functional.
o1
rewrite the utils.py prompts so that they match the model and rewrite the serializers.py, this is the info from the model: vocabulary_complexity = models.IntegerField(null=True, blank=True) sentence_structure = models.CharField(max_length=50, blank=True, null=True) paragraph_organization = models.CharField(max_length=50, blank=True, null=True) idiom_usage = models.IntegerField(null=True, blank=True) metaphor_frequency = models.IntegerField(null=True, blank=True) simile_frequency = models.IntegerField(null=True, blank=True) tone = models.CharField(max_length=50, blank=True, null=True) punctuation_style = models.CharField(max_length=50, blank=True, null=True) contraction_usage = models.IntegerField(null=True, blank=True) pronoun_preference = models.CharField(max_length=50, blank=True, null=True) passive_voice_frequency = models.IntegerField(null=True, blank=True) rhetorical_question_usage = models.IntegerField(null=True, blank=True) list_usage_tendency = models.IntegerField(null=True, blank=True) personal_anecdote_inclusion = models.IntegerField(null=True, blank=True) pop_culture_reference_frequency = models.IntegerField(null=True, blank=True) technical_jargon_usage = models.IntegerField(null=True, blank=True) parenthetical_aside_frequency = models.IntegerField(null=True, blank=True) humor_sarcasm_usage = models.IntegerField(null=True, blank=True) emotional_expressiveness = models.IntegerField(null=True, blank=True) emphatic_device_usage = models.IntegerField(null=True, blank=True) quotation_frequency = models.IntegerField(null=True, blank=True) analogy_usage = models.IntegerField(null=True, blank=True) sensory_detail_inclusion = models.IntegerField(null=True, blank=True) onomatopoeia_usage = models.IntegerField(null=True, blank=True) alliteration_frequency = models.IntegerField(null=True, blank=True) word_length_preference = models.CharField(max_length=50, blank=True, null=True) foreign_phrase_usage = models.IntegerField(null=True, blank=True) rhetorical_device_usage = models.IntegerField(null=True, blank=True) statistical_data_usage = models.IntegerField(null=True, blank=True) personal_opinion_inclusion = models.IntegerField(null=True, blank=True) transition_usage = models.IntegerField(null=True, blank=True) reader_question_frequency = models.IntegerField(null=True, blank=True) imperative_sentence_usage = models.IntegerField(null=True, blank=True) dialogue_inclusion = models.IntegerField(null=True, blank=True) regional_dialect_usage = models.IntegerField(null=True, blank=True) hedging_language_frequency = models.IntegerField(null=True, blank=True) language_abstraction = models.CharField(max_length=50, blank=True, null=True) personal_belief_inclusion = models.IntegerField(null=True, blank=True) repetition_usage = models.IntegerField(null=True, blank=True) subordinate_clause_frequency = models.IntegerField(null=True, blank=True) verb_type_preference = models.CharField(max_length=50, blank=True, null=True) sensory_imagery_usage = models.IntegerField(null=True, blank=True) symbolism_usage = models.IntegerField(null=True, blank=True) digression_frequency = models.IntegerField(null=True, blank=True) formality_level = models.IntegerField(null=True, blank=True) reflection_inclusion = models.IntegerField(null=True, blank=True) irony_usage = models.IntegerField(null=True, blank=True) neologism_frequency = models.IntegerField(null=True, blank=True) ellipsis_usage = models.IntegerField(null=True, blank=True) cultural_reference_inclusion = models.IntegerField(null=True, blank=True) stream_of_consciousness_usage = models.IntegerField(null=True, blank=True) openness_to_experience = models.IntegerField(null=True, blank=True) conscientiousness = models.IntegerField(null=True, blank=True) extraversion = models.IntegerField(null=True, blank=True) agreeableness = models.IntegerField(null=True, blank=True) emotional_stability = models.IntegerField(null=True, blank=True) dominant_motivations = models.CharField(max_length=100, blank=True, null=True) core_values = models.CharField(max_length=100, blank=True, null=True) decision_making_style = models.CharField(max_length=100, blank=True, null=True) empathy_level = models.IntegerField(null=True, blank=True) self_confidence = models.IntegerField(null=True, blank=True) risk_taking_tendency = models.IntegerField(null=True, blank=True) idealism_vs_realism = models.CharField(max_length=50, blank=True, null=True) conflict_resolution_style = models.CharField(max_length=50, blank=True, null=True) relationship_orientation = models.CharField(max_length=50, blank=True, null=True)
Below is an **example** of rewritten **`utils.py`** and **`serializers.py`** that align precisely with the fields in your **`Persona`** model. We’ve removed any extra fields (e.g., *age*, *gender*, *education_level*, etc.) from the prompt so the JSON returned by Ollama matches what your `Persona` model and serializer expect. --- ## **utils.py** ```python import logging import requests import json import os import re from dotenv import load_dotenv # Configure logger logger = logging.getLogger(__name__) # Load environment variables load_dotenv() OLLAMA_SERVER_URL = os.getenv('OLLAMA_SERVER_URL', 'http://localhost:11411') # e.g. http://localhost:11411 OLLAMA_MODEL_NAME = os.getenv('OLLAMA_MODEL_NAME', 'llama3.2') # or whichever model you have def _call_ollama(prompt, model=OLLAMA_MODEL_NAME, temperature=1, max_tokens=500): """ Helper function to call Ollama's /generate endpoint with the given parameters. prompt (str): The text or conversation to send to the model. model (str): The model to use on Ollama (default: 'llama3.2'). temperature (float): Temperature for text generation (default: 1). max_tokens (int): Token limit for the output from Ollama. """ try: payload = { "prompt": prompt, "model": model, "temperature": temperature, "num_ctx": 2048, # Adjust if you need more context length "max_tokens": max_tokens } # The default endpoint could be /generate or /api/generate depending on your Ollama version response = requests.post(f"{OLLAMA_SERVER_URL}/generate", json=payload) response.raise_for_status() # Ollama's response is typically a stream of JSON lines. We can collect all text segments: response_text = "" for line in response.iter_lines(decode_unicode=True): if line.strip(): # Each line is JSON with a "completion" key data = json.loads(line) response_text += data.get("completion", "") return response_text.strip() except Exception as e: logger.error(f"Error calling Ollama: {e}") return None def analyze_writing_sample(writing_sample): """ Analyzes a given writing sample to assess various characteristics that match the fields in the Persona model. Returns the results as a dict in JSON format, e.g.: { "vocabulary_complexity": 7, "sentence_structure": "complex", ... } """ try: # Build the prompt that will be sent to Ollama prompt = f''' Please analyze the following writing sample. Return a JSON object with the fields below, rating each numeric field on a scale of 1-10 where relevant. For non-numeric fields, return a single descriptive value. Return ONLY the JSON, nothing else. {{ "vocabulary_complexity": [1-10], "sentence_structure": "[simple/complex/varied]", "paragraph_organization": "[structured/loose/stream-of-consciousness]", "idiom_usage": [1-10], "metaphor_frequency": [1-10], "simile_frequency": [1-10], "tone": "[formal/informal/academic/conversational/etc.]", "punctuation_style": "[minimal/heavy/unconventional]", "contraction_usage": [1-10], "pronoun_preference": "[first-person/third-person/etc.]", "passive_voice_frequency": [1-10], "rhetorical_question_usage": [1-10], "list_usage_tendency": [1-10], "personal_anecdote_inclusion": [1-10], "pop_culture_reference_frequency": [1-10], "technical_jargon_usage": [1-10], "parenthetical_aside_frequency": [1-10], "humor_sarcasm_usage": [1-10], "emotional_expressiveness": [1-10], "emphatic_device_usage": [1-10], "quotation_frequency": [1-10], "analogy_usage": [1-10], "sensory_detail_inclusion": [1-10], "onomatopoeia_usage": [1-10], "alliteration_frequency": [1-10], "word_length_preference": "[short/long/varied]", "foreign_phrase_usage": [1-10], "rhetorical_device_usage": [1-10], "statistical_data_usage": [1-10], "personal_opinion_inclusion": [1-10], "transition_usage": [1-10], "reader_question_frequency": [1-10], "imperative_sentence_usage": [1-10], "dialogue_inclusion": [1-10], "regional_dialect_usage": [1-10], "hedging_language_frequency": [1-10], "language_abstraction": "[concrete/abstract/mixed]", "personal_belief_inclusion": [1-10], "repetition_usage": [1-10], "subordinate_clause_frequency": [1-10], "verb_type_preference": "[active/stative/mixed]", "sensory_imagery_usage": [1-10], "symbolism_usage": [1-10], "digression_frequency": [1-10], "formality_level": [1-10], "reflection_inclusion": [1-10], "irony_usage": [1-10], "neologism_frequency": [1-10], "ellipsis_usage": [1-10], "cultural_reference_inclusion": [1-10], "stream_of_consciousness_usage": [1-10], "openness_to_experience": [1-10], "conscientiousness": [1-10], "extraversion": [1-10], "agreeableness": [1-10], "emotional_stability": [1-10], "dominant_motivations": "[string]", "core_values": "[string]", "decision_making_style": "[string]", "empathy_level": [1-10], "self_confidence": [1-10], "risk_taking_tendency": [1-10], "idealism_vs_realism": "[idealistic/realistic/mixed]", "conflict_resolution_style": "[string]", "relationship_orientation": "[string]" }} Writing Sample: {writing_sample} ''' # Call Ollama ollama_response = _call_ollama(prompt, model=OLLAMA_MODEL_NAME, temperature=1) if not ollama_response: logger.error("No response from Ollama.") return None logger.debug(f"Ollama response: {ollama_response}") # Extract JSON from the assistant's message json_match = re.search(r'\{.*\}', ollama_response, re.DOTALL) if json_match: try: analyzed_data = json.loads(json_match.group()) return analyzed_data except json.JSONDecodeError: logger.error("Failed to decode JSON from Ollama response.") return None else: logger.error("No JSON object found in the response.") return None except Exception as e: logger.error(f"Error with Ollama API: {e}") return None def generate_content(persona, prompt): """ Generates content based on a given persona and prompt. We only use a subset of fields from Persona in the final prompt. Parameters: - persona (Persona): The persona object with individual fields. - prompt (str): The topic to write about. Returns: - str: The generated content (text). """ try: # Convert persona fields into a format suitable for the prompt persona_traits = { "Writing Style": { "vocabulary_complexity": f"{persona.vocabulary_complexity}/10", "sentence_structure": persona.sentence_structure, "paragraph_organization": persona.paragraph_organization, "tone": persona.tone, "punctuation_style": persona.punctuation_style, "pronoun_preference": persona.pronoun_preference, "formality_level": f"{persona.formality_level}/10" if persona.formality_level else "N/A", }, "Language Patterns": { "idiom_usage": f"{persona.idiom_usage}/10" if persona.idiom_usage else "N/A", "metaphor_frequency": f"{persona.metaphor_frequency}/10" if persona.metaphor_frequency else "N/A", "simile_frequency": f"{persona.simile_frequency}/10" if persona.simile_frequency else "N/A", "technical_jargon_usage": f"{persona.technical_jargon_usage}/10" if persona.technical_jargon_usage else "N/A", "humor_sarcasm_usage": f"{persona.humor_sarcasm_usage}/10" if persona.humor_sarcasm_usage else "N/A", }, "Personality": { "openness_to_experience": f"{persona.openness_to_experience}/10" if persona.openness_to_experience else "N/A", "conscientiousness": f"{persona.conscientiousness}/10" if persona.conscientiousness else "N/A", "extraversion": f"{persona.extraversion}/10" if persona.extraversion else "N/A", "agreeableness": f"{persona.agreeableness}/10" if persona.agreeableness else "N/A", "emotional_stability": f"{persona.emotional_stability}/10" if persona.emotional_stability else "N/A", "dominant_motivations": persona.dominant_motivations or "N/A", "core_values": persona.core_values or "N/A", "decision_making_style": persona.decision_making_style or "N/A", } } # Create the system prompt system_prompt = f"""You are a writer with the following characteristics: Writing Style: {persona_traits['Writing Style']} Language Patterns: {persona_traits['Language Patterns']} Personality: {persona_traits['Personality']} Write in a way that naturally reflects these traits. Please include a title in your response.""" # Combine system prompt and user prompt combined_prompt = f"{system_prompt}\n\nWrite about: {prompt}" # Call Ollama to generate the content ollama_response = _call_ollama( combined_prompt, model=OLLAMA_MODEL_NAME, temperature=1, max_tokens=5000 ) if not ollama_response: logger.error("No response from Ollama for content generation.") return None return ollama_response except Exception as e: logger.error(f"Error generating content: {str(e)}") return None def save_blog_post(blog_post, title): """ Saves a blog post to a file. Parameters: - blog_post (str): The content of the blog post. - title (str): The title of the blog post. """ # Implement if needed pass ``` --- ## **serializers.py** ```python # core/serializers.py from rest_framework import serializers from .models import Author, Persona, ContentPiece from .utils import analyze_writing_sample, generate_content import logging logger = logging.getLogger(__name__) class AuthorSerializer(serializers.ModelSerializer): username = serializers.CharField(source='user.username', read_only=True) email = serializers.EmailField(source='user.email', read_only=True) class Meta: model = Author fields = ['id', 'username', 'email', 'bio', 'created_at'] class PersonaSerializer(serializers.ModelSerializer): # writing_sample is used only for analysis; it won't be stored in the model writing_sample = serializers.CharField(write_only=True, required=False) content_count = serializers.SerializerMethodField() class Meta: model = Persona fields = [ 'id', 'name', 'description', 'content_count', 'vocabulary_complexity', 'sentence_structure', 'paragraph_organization', 'idiom_usage', 'metaphor_frequency', 'simile_frequency', 'tone', 'punctuation_style', 'contraction_usage', 'pronoun_preference', 'passive_voice_frequency', 'rhetorical_question_usage', 'list_usage_tendency', 'personal_anecdote_inclusion', 'pop_culture_reference_frequency', 'technical_jargon_usage', 'parenthetical_aside_frequency', 'humor_sarcasm_usage', 'emotional_expressiveness', 'emphatic_device_usage', 'quotation_frequency', 'analogy_usage', 'sensory_detail_inclusion', 'onomatopoeia_usage', 'alliteration_frequency', 'word_length_preference', 'foreign_phrase_usage', 'rhetorical_device_usage', 'statistical_data_usage', 'personal_opinion_inclusion', 'transition_usage', 'reader_question_frequency', 'imperative_sentence_usage', 'dialogue_inclusion', 'regional_dialect_usage', 'hedging_language_frequency', 'language_abstraction', 'personal_belief_inclusion', 'repetition_usage', 'subordinate_clause_frequency', 'verb_type_preference', 'sensory_imagery_usage', 'symbolism_usage', 'digression_frequency', 'formality_level', 'reflection_inclusion', 'irony_usage', 'neologism_frequency', 'ellipsis_usage', 'cultural_reference_inclusion', 'stream_of_consciousness_usage', 'openness_to_experience', 'conscientiousness', 'extraversion', 'agreeableness', 'emotional_stability', 'dominant_motivations', 'core_values', 'decision_making_style', 'empathy_level', 'self_confidence', 'risk_taking_tendency', 'idealism_vs_realism', 'conflict_resolution_style', 'relationship_orientation', 'is_active', 'created_at', 'updated_at', 'writing_sample' ] read_only_fields = ['id', 'created_at', 'updated_at', 'content_count'] def get_content_count(self, obj): return obj.contentpiece_set.count() def create(self, validated_data): # writing_sample is optional—if present, we attempt analysis writing_sample = validated_data.pop('writing_sample', None) # Attach the current user as the author author = self.context['request'].user.author validated_data['author'] = author if writing_sample: analyzed_data = analyze_writing_sample(writing_sample) if analyzed_data: # Map analyzed data to individual fields in Persona for field, value in analyzed_data.items(): if hasattr(Persona, field): validated_data[field] = value else: logger.error("Failed to analyze writing sample.") raise serializers.ValidationError({ "writing_sample": "Failed to analyze the writing sample." }) return super().create(validated_data) class ContentPieceSerializer(serializers.ModelSerializer): persona_name = serializers.CharField(source='persona.name', read_only=True) class Meta: model = ContentPiece fields = [ 'id', 'title', 'content', 'persona', 'persona_name', 'status', 'tags', 'word_count', 'created_at', 'updated_at', 'published_at' ] read_only_fields = ['id', 'word_count', 'created_at', 'updated_at'] ``` --- ### **How This Works** 1. **`analyze_writing_sample(writing_sample)`** - Sends a prompt to Ollama to analyze the text. - Returns JSON with fields **exactly** matching your Persona model (e.g., `"vocabulary_complexity": 7, "tone": "informal", ..."`). - If any field doesn’t exist on the model (typos or extra fields), it’s simply ignored in the loop. 2. **`PersonaSerializer.create()`** - Pulls out `writing_sample`, calls `analyze_writing_sample`. - Loops over each `(field, value)` from the returned JSON and assigns it to `validated_data` **if** `Persona` has that attribute. 3. **Model Fields vs. Prompt Fields** - Since your prompt now only asks for fields that exist in your Persona model, you avoid mismatches. - Numeric fields in the model (`IntegerField`) expect integer values (1–10). Your prompt clarifies the LLM should respond with integer ratings. - Non-numeric fields (e.g., `'tone'`, `'pronoun_preference'`) expect a string from the LLM. With these changes, the **prompt** and the **serializer** fields are aligned with your Persona model.
o1
No response from Ollama. Failed to analyze writing sample. Bad Request: /api/personas/ [21/Dec/2024 18:52:18] "POST /api/personas/ HTTP/1.1" 400 58
This error indicates that your code tried to send a request to the Ollama server, but it got **no response**—so the `analyze_writing_sample` function returned `None`, causing `"Failed to analyze writing sample."` and finally a `400 Bad Request` from your serializer. Below are the most common reasons why Ollama might return no response, along with suggestions to troubleshoot. --- ## 1. Ollama Server Not Running or Wrong Port - **Is Ollama running?** Verify by opening a separate terminal window and running `ollama serve`, which (by default) starts an HTTP server on port `11411`. - **Check your port and endpoint path.** In your `_call_ollama` function, you might see something like: ```python OLLAMA_SERVER_URL = "http://localhost:11411" ... response = requests.post(f"{OLLAMA_SERVER_URL}/generate", json=payload) ``` - Make sure you are calling the correct port (`11411` by default) and the correct endpoint path (`/generate` or `/api/generate` depending on your Ollama version). - If you are running an older or custom build of Ollama, the endpoint might differ. --- ## 2. Model Name or Prompt Issues - **Check the model name** in your `.env` or Python code. If the model you specified (`OLLAMA_MODEL_NAME`) is not actually available to Ollama, it may fail silently (or produce no completion lines). For instance: ```python OLLAMA_MODEL_NAME = os.getenv('OLLAMA_MODEL_NAME', 'llama3.2') ``` - Make sure you actually have `"llama3.2"` (or whichever model name you put) loaded in Ollama. - **Prompt too large or invalid**: If the prompt has formatting issues or is too big, Ollama might not return a result. Double-check any logs from the Ollama process. --- ## 3. Environment or Networking Blocks - If you’re running Docker or a remote environment, the `localhost:11411` might not be accessible. - If you’re behind a firewall, ensure your system isn’t blocking requests to that port. --- ## 4. Add Debug Logging Since your logs show `"No response from Ollama"`, you can add more robust logging in `_call_ollama`: ```python def _call_ollama(prompt, model=OLLAMA_MODEL_NAME, temperature=1, max_tokens=500): try: logger.debug(f"Sending request to {OLLAMA_SERVER_URL}/generate with model={model}") logger.debug(f"Payload prompt snippet: {prompt[:200]}...") # Log partial prompt ... response = requests.post(f"{OLLAMA_SERVER_URL}/generate", json=payload) response.raise_for_status() response_text = "" for line in response.iter_lines(decode_unicode=True): logger.debug(f"Ollama response line: {line}") if line.strip(): data = json.loads(line) response_text += data.get("completion", "") ... return response_text.strip() except Exception as e: logger.error(f"Error calling Ollama: {e}") return None ``` With this, you can see exactly: 1. That the POST was made. 2. The raw lines Ollama is sending back (if any). If none of these lines appear, your request might not even be reaching Ollama, or Ollama is silently failing. --- ## 5. Confirm the Ollama Process Logs When you run `ollama serve`, it generally logs each request. If you see no incoming request in Ollama’s logs, then your Django app might not be calling the correct place. If you do see a request but get an error, you might see something like: ``` POST /generate - 400 Error: could not find model llama3.2 ``` In which case, you’ll know the root cause (e.g., missing model). --- ## 6. Summary of Next Steps 1. **Ensure Ollama is running**: `ollama serve` in a terminal. 2. **Verify your port & endpoint**: - For most versions: `OLLAMA_SERVER_URL = "http://localhost:11411"`, endpoint is `"/generate"`. - If you’re using an older version or have changed the code, it might be `"/api/generate"`. 3. **Check the model name**: Make sure it’s something Ollama has loaded (like `llama2` or a custom `.bin` file). 4. **Add debug logs** to see if your request is being posted and what lines (if any) come back. 5. **Look at Ollama’s console output** for signs of an incoming request or errors. If, after confirming all this, you still get **no response** lines in your logs, the issue is likely network connectivity or a mismatch in endpoints. Once Ollama responds properly, your `analyze_writing_sample` should return valid JSON, and your serializer will stop raising `"Failed to analyze writing sample."`.
o1
Django version 5.1.4, using settings 'backend.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. No response from Ollama. Failed to analyze writing sample. Bad Request: /api/personas/ [21/Dec/2024 18:58:18] "POST /api/personas/ HTTP/1.1" 400 58 Unauthorized: /api/personas/ [21/Dec/2024 18:59:14] "POST /api/personas/ HTTP/1.1" 401 183 [21/Dec/2024 18:59:14] "POST /api/token/refresh/ HTTP/1.1" 200 241 No response from Ollama. Failed to analyze writing sample. Bad Request: /api/personas/ [21/Dec/2024 19:02:24] "POST /api/personas/ HTTP/1.1" 400 58
The key points from your logs are: 1. **You start your Django development server** on `http://127.0.0.1:8000/`. 2. **Repeated “No response from Ollama”** lines appear, leading to `Failed to analyze writing sample`. 3. The serializer returns `400 Bad Request` because `analyze_writing_sample` returned `None`, triggering `ValidationError` (`"Failed to analyze writing sample."`). 4. There was also a `401 Unauthorized` at one point, but then you refreshed the token, so presumably authentication is now correct. The issue remains that Ollama never responds. This almost certainly means your Django code **is not receiving any data back from Ollama**. Below is a checklist to help you resolve this: --- ## 1. Confirm Ollama Is Running Make sure you have **a separate terminal** open where you run: ```bash ollama serve ``` - By default, Ollama listens at `http://localhost:11411`. - If you’ve never successfully run `ollama serve` with a valid model, your Python code will get no response. Check that your Ollama CLI is logging connections. If it is not, Django requests might not be making it to Ollama. --- ## 2. Validate the Endpoint and Port Double-check your `utils.py` or config: ```python OLLAMA_SERVER_URL = os.getenv('OLLAMA_SERVER_URL', 'http://localhost:11411') ``` ```python response = requests.post(f"{OLLAMA_SERVER_URL}/generate", json=payload) ``` - **Is your Ollama version** using `/generate` or `/api/generate`? - Some versions or forks might require `/api/generate`. - By default, recent versions of the official Ollama CLI use `POST /generate`. Try each endpoint in isolation (e.g., using `curl` or `Postman`): ```bash curl -X POST http://localhost:11411/generate \ -H "Content-Type: application/json" \ -d '{"model":"llama2","prompt":"Hello"}' ``` If you get no response or an error in your terminal, that’s the root cause. Fix that first. --- ## 3. Verify the Model Name Check that `OLLAMA_MODEL_NAME` in your `.env` or code is the **same** as the model you’ve downloaded or configured in Ollama. For instance, if your code says: ```python OLLAMA_MODEL_NAME = os.getenv('OLLAMA_MODEL_NAME', 'llama3.2') ``` …but you’re actually running with `llama2-7b`, Ollama might be failing internally (depending on how it handles invalid model requests). Confirm the model name. You can specify it on the Ollama CLI: ```bash ollama serve --model llama2-7b ``` Then in your `.env` or code: ```bash OLLAMA_MODEL_NAME='llama2-7b' ``` --- ## 4. Inspect Ollama’s Terminal Logs When Ollama receives a request, it typically logs something like: ``` [info] 127.0.0.1: POST /generate [info] Generating response ... ``` If you **never** see these lines, that’s your sign the request isn’t even reaching Ollama. Could be: - **Firewall** or **Port** issues if you’re on Windows or a managed environment. - **Docker** or container networking if you’re containerizing. - **Typos** in the endpoint (e.g., `localhost:4114` instead of `11411`). --- ## 5. Add More Logging in `_call_ollama` In `utils.py` (or wherever `_call_ollama` resides), log each step: ```python def _call_ollama(prompt, model=OLLAMA_MODEL_NAME, temperature=1, max_tokens=500): logger.debug(f"Attempting to call Ollama at {OLLAMA_SERVER_URL}/generate with model='{model}'") try: payload = { "prompt": prompt, "model": model, "temperature": temperature, "num_ctx": 2048, "max_tokens": max_tokens } response = requests.post(f"{OLLAMA_SERVER_URL}/generate", json=payload) logger.debug(f"POST status code: {response.status_code}") response.raise_for_status() response_text = "" for line in response.iter_lines(decode_unicode=True): logger.debug(f"Ollama line: {line}") if line.strip(): data = json.loads(line) response_text += data.get("completion", "") return response_text.strip() except Exception as e: logger.error(f"Error calling Ollama: {e}") return None ``` Now watch your Django console for: - `POST status code: ...` - Any lines from Ollama (`Ollama line: ...`) If you see **status code 401** or 403, it might mean Ollama is behind some auth you didn’t set up. If you see 404, maybe the path is wrong. If you see 200 but no lines in the response, Ollama might be returning an empty stream. --- ## 6. Confirm That You’re Actually Hitting the Right URL Sometimes a mismatch like `127.0.0.1` vs. `localhost` or a trailing slash can cause issues: - `http://127.0.0.1:11411/generate` vs. `http://localhost:11411/generate`. - Typically they’re the same, but if you have local DNS or IPv6 issues, try both. - On Windows, sometimes `localhost` might be set to IPv6 or blocked in an antivirus. --- ## 7. Conclusion Based on your log output: 1. **No Response** from Ollama -> `_call_ollama` returns `None`. 2. `_call_ollama` returning `None` leads to **`analyze_writing_sample`** returning `None`. 3. That triggers **`"Failed to analyze writing sample."`** in your serializer and a `400 Bad Request`. **All** of this ties back to the fact that Ollama isn’t giving any data. To fix it, focus on: - **Is Ollama serving requests?** - **Is the port correct?** - **Is the endpoint path correct?** - **Is your model name valid?** - **Are you seeing any logs on the Ollama side?** Once you see actual lines from Ollama in your Django logs, your serializer should stop returning `400`.
o1
No response from Ollama. Failed to analyze writing sample. Bad Request: /api/personas/ [21/Dec/2024 19:13:02] "POST /api/personas/ HTTP/1.1" 400 58
This error (“No response from Ollama” followed by “Failed to analyze writing sample” and a `400 Bad Request`) means your Django code tried to communicate with your local Ollama server but never received any data back. As a result, the serializer raises a `ValidationError` (“Failed to analyze writing sample.”). Below is a checklist to help you debug and fix the issue: --- ## 1. Confirm Ollama Is Running 1. **Open a separate terminal** (or command prompt) and run: ```bash ollama serve ``` - By default, Ollama starts on port `11411`. - Watch the terminal for logs. If there are **no** incoming requests, your Django app isn’t reaching Ollama. 2. **Check if Ollama logs any errors**. If it can’t load the specified model or if there’s a permission issue, it might fail to respond. --- ## 2. Validate Port and Endpoint By default, recent Ollama versions respond at: ``` POST http://localhost:11411/generate ``` Make sure your `utils.py` has something like: ```python OLLAMA_SERVER_URL = os.getenv("OLLAMA_SERVER_URL", "http://localhost:11411") response = requests.post(f"{OLLAMA_SERVER_URL}/generate", json=payload) ``` ### Common Pitfalls: - **Wrong port** (e.g., `4114` instead of `11411`). - **Wrong endpoint** (some older builds might use `/api/generate` but the current version typically uses `/generate`). - **Typos** in `localhost` or missing `http://`. --- ## 3. Verify the Model Name If your code references: ```python OLLAMA_MODEL_NAME = os.getenv("OLLAMA_MODEL_NAME", "llama3.2") ``` …but you are actually running `ollama serve --model llama2-7b`, Ollama might not find `"llama3.2"` and fail silently. Align the model name: 1. **How you launch Ollama** (e.g., `ollama serve --model llama2-7b`). 2. **What your code sends** in the `payload`: ```python payload = { "prompt": prompt, "model": "llama2-7b", # Must match the model you actually have ... } ``` --- ## 4. Check Network or Docker Issues - If you’re running Docker containers, ensure the container can access `localhost:11411`. Sometimes you need to reference `host.docker.internal` or map ports correctly. - On Windows, antivirus/firewalls can block local requests. --- ## 5. Add Detailed Debug Logs In your `_call_ollama` function (or wherever you do the request), add logs: ```python logger.debug(f"Calling Ollama at {OLLAMA_SERVER_URL}/generate with model={model}") logger.debug(f"Request payload prompt: {prompt[:200]}...") response = requests.post(f"{OLLAMA_SERVER_URL}/generate", json=payload) logger.debug(f"Response status code: {response.status_code}") ``` - If the status code is never logged, your request might not be leaving Django. - If you see a `200` but no lines in `response.iter_lines()`, Ollama might not be streaming any data (i.e., internal error). --- ## 6. Inspect Ollama’s Console Output When you run `ollama serve`, each incoming request typically logs: ``` [info] 127.0.0.1: POST /generate [info] Generating response... ``` - If you **never** see that line, then Django’s request never arrives at Ollama. - If you do see it but then an error message appears (e.g., “Failed to load model llama3.2”), fix that error. --- ## 7. Once Ollama Responds, the 400 Error Disappears - Because `analyze_writing_sample` returns `None` (no data from Ollama), your serializer raises: ```python raise serializers.ValidationError({"writing_sample": "Failed to analyze the writing sample."}) ``` - As soon as Ollama returns a valid JSON response, `analyze_writing_sample` will parse it, fill in the Persona fields, and you’ll no longer get a `400`. --- ### **Summary of Key Steps** 1. **Launch Ollama with the correct model**: `ollama serve --model llama2-7b` (or whatever your code expects). 2. **Double-check** you’re calling the right URL in Django: `http://localhost:11411/generate`. 3. **See if Ollama logs** an incoming POST request. If not, there’s a network/endpoint mismatch. 4. **Match the model name** in your environment/code to the one you loaded in Ollama. 5. **Enable debug logs** in your `_call_ollama` function to see the request/response status codes. Once you diagnose why “No response” is happening (model mismatch, wrong port, or network block), your Persona creation call will succeed without a `400`.
o1
⑂ thread forked here
I think the problem is that it is not returning json in the correct format, help me fix it with this: Ollama Blog Discord GitHub Search models Models Download profile Structured outputs December 6, 2024 Ollama playing with building blocks Ollama now supports structured outputs making it possible to constrain a model’s output to a specific format defined by a JSON schema. The Ollama Python and JavaScript libraries have been updated to support structured outputs. Use cases for structured outputs include: Parsing data from documents Extracting data from images Structuring all language model responses More reliability and consistency than JSON mode Get started Download the latest version of Ollama Upgrade to the latest version of the Ollama Python or JavaScript library: Python pip install -U ollama JavaScript npm i ollama To pass structured outputs to the model, the format parameter can be used in the cURL request or the format parameter in the Python or JavaScript libraries. cURL curl -X POST http://localhost:11434/api/chat -H "Content-Type: application/json" -d '{ "model": "llama3.1", "messages": [{"role": "user", "content": "Tell me about Canada."}], "stream": false, "format": { "type": "object", "properties": { "name": { "type": "string" }, "capital": { "type": "string" }, "languages": { "type": "array", "items": { "type": "string" } } }, "required": [ "name", "capital", "languages" ] } }' Output The response is returned in the format defined by the JSON schema in the request. { "capital": "Ottawa", "languages": [ "English", "French" ], "name": "Canada" } Python Using the Ollama Python library, pass in the schema as a JSON object to the format parameter as either dict or use Pydantic (recommended) to serialize the schema using model_json_schema(). from ollama import chat from pydantic import BaseModel class Country(BaseModel): name: str capital: str languages: list[str] response = chat( messages=[ { 'role': 'user', 'content': 'Tell me about Canada.', } ], model='llama3.1', format=Country.model_json_schema(), ) country = Country.model_validate_json(response.message.content) print(country) Output name='Canada' capital='Ottawa' languages=['English', 'French'] JavaScript Using the Ollama JavaScript library, pass in the schema as a JSON object to the format parameter as either object or use Zod (recommended) to serialize the schema using zodToJsonSchema(). import ollama from 'ollama'; import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; const Country = z.object({ name: z.string(), capital: z.string(), languages: z.array(z.string()), }); const response = await ollama.chat({ model: 'llama3.1', messages: [{ role: 'user', content: 'Tell me about Canada.' }], format: zodToJsonSchema(Country), }); const country = Country.parse(JSON.parse(response.message.content)); console.log(country); Output { name: "Canada", capital: "Ottawa", languages: [ "English", "French" ], } Examples Data extraction To extract structured data from text, define a schema to represent information. The model then extracts the information and returns the data in the defined schema as JSON: from ollama import chat from pydantic import BaseModel class Pet(BaseModel): name: str animal: str age: int color: str | None favorite_toy: str | None class PetList(BaseModel): pets: list[Pet] response = chat( messages=[ { 'role': 'user', 'content': ''' I have two pets. A cat named Luna who is 5 years old and loves playing with yarn. She has grey fur. I also have a 2 year old black cat named Loki who loves tennis balls. ''', } ], model='llama3.1', format=PetList.model_json_schema(), ) pets = PetList.model_validate_json(response.message.content) print(pets) Example output pets=[ Pet(name='Luna', animal='cat', age=5, color='grey', favorite_toy='yarn'), Pet(name='Loki', animal='cat', age=2, color='black', favorite_toy='tennis balls') ] Image description Structured outputs can also be used with vision models. For example, the following code uses llama3.2-vision to describe the following image and returns a structured output: image from ollama import chat from pydantic import BaseModel class Object(BaseModel): name: str confidence: float attributes: str class ImageDescription(BaseModel): summary: str objects: List[Object] scene: str colors: List[str] time_of_day: Literal['Morning', 'Afternoon', 'Evening', 'Night'] setting: Literal['Indoor', 'Outdoor', 'Unknown'] text_content: Optional[str] = None path = 'path/to/image.jpg' response = chat( model='llama3.2-vision', format=ImageDescription.model_json_schema(), # Pass in the schema for the response messages=[ { 'role': 'user', 'content': 'Analyze this image and describe what you see, including any objects, the scene, colors and any text you can detect.', 'images': [path], }, ], options={'temperature': 0}, # Set temperature to 0 for more deterministic output ) image_description = ImageDescription.model_validate_json(response.message.content) print(image_description) Example output summary='A palm tree on a sandy beach with blue water and sky.' objects=[ Object(name='tree', confidence=0.9, attributes='palm tree'), Object(name='beach', confidence=1.0, attributes='sand') ], scene='beach', colors=['blue', 'green', 'white'], time_of_day='Afternoon' setting='Outdoor' text_content=None OpenAI compatibility from openai import OpenAI import openai from pydantic import BaseModel client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama") class Pet(BaseModel): name: str animal: str age: int color: str | None favorite_toy: str | None class PetList(BaseModel): pets: list[Pet] try: completion = client.beta.chat.completions.parse( temperature=0, model="llama3.1:8b", messages=[ {"role": "user", "content": ''' I have two pets. A cat named Luna who is 5 years old and loves playing with yarn. She has grey fur. I also have a 2 year old black cat named Loki who loves tennis balls. '''} ], response_format=PetList, ) pet_response = completion.choices[0].message if pet_response.parsed: print(pet_response.parsed) elif pet_response.refusal: print(pet_response.refusal) except Exception as e: if type(e) == openai.LengthFinishReasonError: print("Too many tokens: ", e) pass else: print(e) pass Tips For reliable use of structured outputs, consider to: Use Pydantic (Python) or Zod (JavaScript) to define the schema for the response Add “return as JSON” to the prompt to help the model understand the request Set the temperature to 0 for more deterministic output What’s next? Exposing logits for controlled generation Performance and accuracy improvements for structured outputs GPU acceleration for sampling Additional format support beyond JSON schema © 2024 Ollama Blog Docs GitHub Discord X (Twitter) Meetups
Below is an **example** approach for **structured JSON responses** using Ollama’s new `format` parameter. This ensures the language model returns data in the exact schema (and field types) that your Django app expects, avoiding the “No response from Ollama” or malformed JSON issue. > **Note:** This example uses the **Ollama Python library** (`pip install ollama`) and **Pydantic** (`pip install pydantic`). If you’re still hitting “No response from Ollama,” make sure your Ollama server is up, you’re referencing the correct port/endpoint/model, and you see actual logs in your Ollama console. --- # 1. Define a Pydantic Model Matching Your Persona Fields Create a **`pydantic`** class (or classes) that exactly match your Django `Persona` model fields. For each integer field (1–10 rating), declare it as an `int` with optional constraints. For string fields, use `str`; for optional fields, specify `None` defaults. Below is **one** example for your fields. Adjust as needed: ```python # pydantic_persona.py from pydantic import BaseModel, Field from typing import Optional class PersonaAnalysis(BaseModel): vocabulary_complexity: int = Field(..., ge=1, le=10) sentence_structure: str paragraph_organization: str idiom_usage: int = Field(..., ge=1, le=10) metaphor_frequency: int = Field(..., ge=1, le=10) simile_frequency: int = Field(..., ge=1, le=10) tone: str punctuation_style: str contraction_usage: int = Field(..., ge=1, le=10) pronoun_preference: str passive_voice_frequency: int = Field(..., ge=1, le=10) rhetorical_question_usage: int = Field(..., ge=1, le=10) list_usage_tendency: int = Field(..., ge=1, le=10) personal_anecdote_inclusion: int = Field(..., ge=1, le=10) pop_culture_reference_frequency: int = Field(..., ge=1, le=10) technical_jargon_usage: int = Field(..., ge=1, le=10) parenthetical_aside_frequency: int = Field(..., ge=1, le=10) humor_sarcasm_usage: int = Field(..., ge=1, le=10) emotional_expressiveness: int = Field(..., ge=1, le=10) emphatic_device_usage: int = Field(..., ge=1, le=10) quotation_frequency: int = Field(..., ge=1, le=10) analogy_usage: int = Field(..., ge=1, le=10) sensory_detail_inclusion: int = Field(..., ge=1, le=10) onomatopoeia_usage: int = Field(..., ge=1, le=10) alliteration_frequency: int = Field(..., ge=1, le=10) word_length_preference: str foreign_phrase_usage: int = Field(..., ge=1, le=10) rhetorical_device_usage: int = Field(..., ge=1, le=10) statistical_data_usage: int = Field(..., ge=1, le=10) personal_opinion_inclusion: int = Field(..., ge=1, le=10) transition_usage: int = Field(..., ge=1, le=10) reader_question_frequency: int = Field(..., ge=1, le=10) imperative_sentence_usage: int = Field(..., ge=1, le=10) dialogue_inclusion: int = Field(..., ge=1, le=10) regional_dialect_usage: int = Field(..., ge=1, le=10) hedging_language_frequency: int = Field(..., ge=1, le=10) language_abstraction: str personal_belief_inclusion: int = Field(..., ge=1, le=10) repetition_usage: int = Field(..., ge=1, le=10) subordinate_clause_frequency: int = Field(..., ge=1, le=10) verb_type_preference: str sensory_imagery_usage: int = Field(..., ge=1, le=10) symbolism_usage: int = Field(..., ge=1, le=10) digression_frequency: int = Field(..., ge=1, le=10) formality_level: int = Field(..., ge=1, le=10) reflection_inclusion: int = Field(..., ge=1, le=10) irony_usage: int = Field(..., ge=1, le=10) neologism_frequency: int = Field(..., ge=1, le=10) ellipsis_usage: int = Field(..., ge=1, le=10) cultural_reference_inclusion: int = Field(..., ge=1, le=10) stream_of_consciousness_usage: int = Field(..., ge=1, le=10) openness_to_experience: int = Field(..., ge=1, le=10) conscientiousness: int = Field(..., ge=1, le=10) extraversion: int = Field(..., ge=1, le=10) agreeableness: int = Field(..., ge=1, le=10) emotional_stability: int = Field(..., ge=1, le=10) dominant_motivations: str core_values: str decision_making_style: str empathy_level: int = Field(..., ge=1, le=10) self_confidence: int = Field(..., ge=1, le=10) risk_taking_tendency: int = Field(..., ge=1, le=10) idealism_vs_realism: str conflict_resolution_style: str relationship_orientation: str ``` - You can mark some fields `Optional[...] = None` if they don’t *always* appear. - This model enforces integer ranges for the numeric fields, so if the model returns something out of bounds (or missing a required field), the validation will fail. --- # 2. Update Your `utils.py` to Use Ollama’s Structured Output Instead of manually parsing JSON with `requests`, use **`from ollama import chat`**. You’ll pass the `PersonaAnalysis.model_json_schema()` to Ollama’s `format` parameter. This instructs the model to produce exactly that JSON schema. ```python # utils.py import logging import os from dotenv import load_dotenv from ollama import chat from pydantic import ValidationError from .pydantic_persona import PersonaAnalysis # import the Pydantic model logger = logging.getLogger(__name__) load_dotenv() OLLAMA_MODEL_NAME = os.getenv('OLLAMA_MODEL_NAME', 'llama3.2') def analyze_writing_sample(writing_sample): """ Analyzes a given writing sample using Ollama structured outputs. Returns a dict that aligns with the Persona fields, or None if analysis fails. """ try: # Construct your user message. # The prompt can be simpler now since the "format" schema enforces output structure. prompt = f"Analyze the following text. Return each field with a numeric rating 1-10 or single string value as appropriate.\n\n{writing_sample}" # Call Ollama using its Python library response = chat( model=OLLAMA_MODEL_NAME, messages=[ {"role": "user", "content": prompt} ], format=PersonaAnalysis.model_json_schema(), # The JSON schema for structured output stream=False # We'll read the entire response in one shot ) # Ollama returns an object with `message.content` containing the JSON raw_json = response.message.content.strip() logger.debug(f"Raw Ollama response: {raw_json}") # Validate and parse with pydantic try: persona_data = PersonaAnalysis.model_validate_json(raw_json) return persona_data.dict() # Return as a standard Python dict except ValidationError as ve: logger.error(f"Validation error: {ve}") return None except Exception as e: logger.error(f"Error calling Ollama: {e}") return None def generate_content(persona, prompt): """ Generates content based on a given Persona object from Django model and a user prompt. """ try: # Use some subset of Persona fields to shape the style or voice system_prompt = f"""You are a writer with these characteristics: - Vocabulary Complexity: {persona.vocabulary_complexity}/10 - Sentence Structure: {persona.sentence_structure} - Paragraph Organization: {persona.paragraph_organization} - Tone: {persona.tone} - Punctuation Style: {persona.punctuation_style} - Pronoun Preference: {persona.pronoun_preference} - Formality Level: {persona.formality_level}/10 - Humor/Sarcasm: {persona.humor_sarcasm_usage}/10 - Openness to Experience: {persona.openness_to_experience}/10 - Decision Making Style: {persona.decision_making_style} - Dominant Motivations: {persona.dominant_motivations} - Core Values: {persona.core_values} Write naturally with these traits. Provide a short, cohesive piece with a title. """ response = chat( model=OLLAMA_MODEL_NAME, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"Write about: {prompt}"} ], stream=False ) return response.message.content.strip() except Exception as e: logger.error(f"Error generating content: {e}") return None ``` ### What Changed? 1. **`from ollama import chat`** – we use Ollama’s official Python library. 2. We pass `format=PersonaAnalysis.model_json_schema()` to **tell Ollama** it **must** return a JSON object conforming to that schema. 3. We parse the returned JSON with `PersonaAnalysis.model_validate_json(...)`. If it fails (missing keys, type mismatch, or invalid JSON), we log an error and return `None`. --- # 3. Adjust Your `serializers.py` Since `analyze_writing_sample` now returns a dictionary with the correct fields, your existing code *should* work with minimal changes: ```python # serializers.py from rest_framework import serializers from .models import Author, Persona, ContentPiece from .utils import analyze_writing_sample, generate_content import logging logger = logging.getLogger(__name__) class AuthorSerializer(serializers.ModelSerializer): username = serializers.CharField(source='user.username', read_only=True) email = serializers.EmailField(source='user.email', read_only=True) class Meta: model = Author fields = ['id', 'username', 'email', 'bio', 'created_at'] class PersonaSerializer(serializers.ModelSerializer): writing_sample = serializers.CharField(write_only=True, required=False) content_count = serializers.SerializerMethodField() class Meta: model = Persona fields = [ 'id', 'name', 'description', 'content_count', 'vocabulary_complexity', 'sentence_structure', 'paragraph_organization', 'idiom_usage', 'metaphor_frequency', 'simile_frequency', 'tone', 'punctuation_style', 'contraction_usage', 'pronoun_preference', 'passive_voice_frequency', 'rhetorical_question_usage', 'list_usage_tendency', 'personal_anecdote_inclusion', 'pop_culture_reference_frequency', 'technical_jargon_usage', 'parenthetical_aside_frequency', 'humor_sarcasm_usage', 'emotional_expressiveness', 'emphatic_device_usage', 'quotation_frequency', 'analogy_usage', 'sensory_detail_inclusion', 'onomatopoeia_usage', 'alliteration_frequency', 'word_length_preference', 'foreign_phrase_usage', 'rhetorical_device_usage', 'statistical_data_usage', 'personal_opinion_inclusion', 'transition_usage', 'reader_question_frequency', 'imperative_sentence_usage', 'dialogue_inclusion', 'regional_dialect_usage', 'hedging_language_frequency', 'language_abstraction', 'personal_belief_inclusion', 'repetition_usage', 'subordinate_clause_frequency', 'verb_type_preference', 'sensory_imagery_usage', 'symbolism_usage', 'digression_frequency', 'formality_level', 'reflection_inclusion', 'irony_usage', 'neologism_frequency', 'ellipsis_usage', 'cultural_reference_inclusion', 'stream_of_consciousness_usage', 'openness_to_experience', 'conscientiousness', 'extraversion', 'agreeableness', 'emotional_stability', 'dominant_motivations', 'core_values', 'decision_making_style', 'empathy_level', 'self_confidence', 'risk_taking_tendency', 'idealism_vs_realism', 'conflict_resolution_style', 'relationship_orientation', 'is_active', 'created_at', 'updated_at', 'writing_sample' ] read_only_fields = ['id', 'created_at', 'updated_at', 'content_count'] def get_content_count(self, obj): return obj.contentpiece_set.count() def create(self, validated_data): writing_sample = validated_data.pop('writing_sample', None) author = self.context['request'].user.author validated_data['author'] = author if writing_sample: analyzed_data = analyze_writing_sample(writing_sample) if analyzed_data: # analyzed_data is a dict with fields matching the Persona model for field, value in analyzed_data.items(): if hasattr(Persona, field): validated_data[field] = value else: logger.error("Failed to analyze writing sample.") raise serializers.ValidationError({"writing_sample": "Failed to analyze the writing sample."}) return super().create(validated_data) class ContentPieceSerializer(serializers.ModelSerializer): persona_name = serializers.CharField(source='persona.name', read_only=True) class Meta: model = ContentPiece fields = [ 'id', 'title', 'content', 'persona', 'persona_name', 'status', 'tags', 'word_count', 'created_at', 'updated_at', 'published_at' ] read_only_fields = ['id', 'word_count', 'created_at', 'updated_at'] ``` Now, if Ollama successfully returns a JSON object that conforms to the `PersonaAnalysis` schema, the serializer can apply those field values to the `Persona` instance—no more “Failed to analyze writing sample” due to malformed or missing JSON. --- ## Final Tips 1. **Use the Latest Ollama**: - Make sure you’re on a version that supports `format=...` in structured outputs. - `pip install -U ollama` or download a newer binary if you’re using the CLI only. 2. **Check Ollama Logs**: - You should see something like: ```text [info] 127.0.0.1: POST /chat [info] Generating response... ``` - If you **don’t** see a request, your code might still be pointing to the wrong URL/port. 3. **Consider Lower Temperature**: - If the model wanders or misses fields, set `temperature=0` for more deterministic JSON. 4. **Validate the Return**: - If the model tries to generate fields you didn’t define, Pydantic will ignore or error on them. - If any required field is missing or out of range (like `0` for a [1–10] rating), you’ll see a Pydantic `ValidationError`. With this structured-output approach, you should get predictable JSON that directly lines up with your `Persona` model fields, eliminating the “No response from Ollama” + “Failed to analyze writing sample” frustration.
o1
To resolve your issue, you can use Ollama’s new structured output feature to ensure responses follow a specific JSON schema. Here's how to integrate structured outputs with your Django project: --- ### **1. Update Ollama and Python Library** Ensure you have the latest version of Ollama and its Python library: ```bash pip install -U ollama ``` --- ### **2. Define Your JSON Schema** Use `Pydantic` to define the expected format for your writing sample analysis. For example: ```python from pydantic import BaseModel class PersonaAnalysis(BaseModel): traits: dict[str, float] # Example: {"Openness": 0.85, "Agreeableness": 0.9} sentiment: str # Example: "Positive" word_count: int # Total number of words in the sample ``` --- ### **3. Update Your Ollama Integration** Modify your `_call_ollama` function (or equivalent) to pass the `format` parameter in the request: ```python from ollama import chat def analyze_writing_sample(prompt: str, model="llama3.1"): schema = PersonaAnalysis.model_json_schema() try: response = chat( messages=[{"role": "user", "content": prompt}], model=model, format=schema, # Pass the JSON schema here ) # Validate and parse the response analysis = PersonaAnalysis.model_validate_json(response.message.content) return analysis.dict() # Return as a dictionary for further use except Exception as e: print(f"Error analyzing writing sample: {e}") return None ``` --- ### **4. Modify the Django Serializer** Update your Django serializer to handle the structured output: ```python from rest_framework import serializers class WritingSampleSerializer(serializers.Serializer): writing_sample = serializers.CharField() traits = serializers.JSONField(required=False) sentiment = serializers.CharField(required=False) word_count = serializers.IntegerField(required=False) def create(self, validated_data): prompt = validated_data.get('writing_sample') analysis = analyze_writing_sample(prompt) if not analysis: raise serializers.ValidationError("Failed to analyze the writing sample.") # Include analysis results in the validated data validated_data.update(analysis) return validated_data ``` --- ### **5. Verify JSON Response Format** Ensure Ollama’s response matches your schema. Here’s a sample response that the `PersonaAnalysis` class would accept: ```json { "traits": { "Openness": 0.85, "Agreeableness": 0.9 }, "sentiment": "Positive", "word_count": 1200 } ``` --- ### **6. Test Your Integration** Send a test POST request to your `/api/personas/` endpoint with a writing sample. If everything is configured correctly, the structured response will populate the `traits`, `sentiment`, and `word_count` fields. --- ### **Tips for Reliable Outputs** 1. **Set `temperature=0`:** This reduces randomness in the model’s output. 2. **Add explicit instructions:** Include phrases like "Return output as JSON." 3. **Debug structured output failures:** If Ollama doesn’t return the expected schema, inspect the raw response and adjust the schema or prompt as needed. --- ### **Final Notes** Structured outputs ensure that Ollama responses fit the expected format, preventing `400 Bad Request` errors. Let me know if you encounter further issues during implementation!
gpt-4o
the following is my current utils.py, rewrite with this context: import logging import requests import json import os import re from dotenv import load_dotenv # Configure logger logger = logging.getLogger(__name__) # Load environment variables load_dotenv() OLLAMA_SERVER_URL = os.getenv('OLLAMA_SERVER_URL', 'http://localhost:11411') # e.g. http://localhost:11411 OLLAMA_MODEL_NAME = os.getenv('OLLAMA_MODEL_NAME', 'dolphin-mistral:7b') # or whichever model you have def _call_ollama(prompt, model=OLLAMA_MODEL_NAME, temperature=1, max_tokens=500): """ Helper function to call Ollama's /generate endpoint with the given parameters. prompt (str): The text or conversation to send to the model. model (str): The model to use on Ollama (default: 'llama2'). temperature (float): Temperature for text generation (default: 1). max_tokens (int): Token limit for the output from Ollama. """ try: payload = { "prompt": prompt, "model": model, "temperature": temperature, "num_ctx": 2048, # Adjust if you need more context length "max_tokens": max_tokens } response = requests.post(f"{OLLAMA_SERVER_URL}/api/generate", json=payload) response.raise_for_status() # Ollama's response is typically a stream of JSON lines. We can collect all text segments: response_text = "" for line in response.iter_lines(decode_unicode=True): if line.strip(): # Each line is JSON with a "completion" key data = json.loads(line) response_text += data.get("completion", "") return response_text.strip() except Exception as e: logger.error(f"Error calling Ollama: {e}") return None def analyze_writing_sample(writing_sample): """ Analyzes a given writing sample to assess various characteristics. Parameters: - writing_sample (str): The text to analyze. Returns: - dict: Analysis results in JSON format. """ try: # Build the prompt that will be sent to Ollama prompt = f''' Please analyze the writing style and personality of the given writing sample. Provide a detailed assessment of their characteristics using the following template. Rate each applicable characteristic on a scale of 1-10 where relevant, or provide a descriptive value. Return the results in a JSON format. "vocabulary_complexity": [1-10], "sentence_structure": "[simple/complex/varied]", "paragraph_organization": "[structured/loose/stream-of-consciousness]", "idiom_usage": [1-10], "metaphor_frequency": [1-10], "simile_frequency": [1-10], "tone": "[formal/informal/academic/conversational/etc.]", "punctuation_style": "[minimal/heavy/unconventional]", "contraction_usage": [1-10], "pronoun_preference": "[first-person/third-person/etc.]", "passive_voice_frequency": [1-10], "rhetorical_question_usage": [1-10], "list_usage_tendency": [1-10], "personal_anecdote_inclusion": [1-10], "pop_culture_reference_frequency": [1-10], "technical_jargon_usage": [1-10], "parenthetical_aside_frequency": [1-10], "humor_sarcasm_usage": [1-10], "emotional_expressiveness": [1-10], "emphatic_device_usage": [1-10], "quotation_frequency": [1-10], "analogy_usage": [1-10], "sensory_detail_inclusion": [1-10], "onomatopoeia_usage": [1-10], "alliteration_frequency": [1-10], "word_length_preference": "[short/long/varied]", "foreign_phrase_usage": [1-10], "rhetorical_device_usage": [1-10], "statistical_data_usage": [1-10], "personal_opinion_inclusion": [1-10], "transition_usage": [1-10], "reader_question_frequency": [1-10], "imperative_sentence_usage": [1-10], "dialogue_inclusion": [1-10], "regional_dialect_usage": [1-10], "hedging_language_frequency": [1-10], "language_abstraction": "[concrete/abstract/mixed]", "personal_belief_inclusion": [1-10], "repetition_usage": [1-10], "subordinate_clause_frequency": [1-10], "verb_type_preference": "[active/stative/mixed]", "sensory_imagery_usage": [1-10], "symbolism_usage": [1-10], "digression_frequency": [1-10], "formality_level": [1-10], "reflection_inclusion": [1-10], "irony_usage": [1-10], "neologism_frequency": [1-10], "ellipsis_usage": [1-10], "cultural_reference_inclusion": [1-10], "stream_of_consciousness_usage": [1-10], "openness_to_experience": [1-10], "conscientiousness": [1-10], "extraversion": [1-10], "agreeableness": [1-10], "emotional_stability": [1-10], "dominant_motivations": "[string]", "core_values": "[string]", "decision_making_style": "[string]", "empathy_level": [1-10], "self_confidence": [1-10], "risk_taking_tendency": [1-10], "idealism_vs_realism": "[idealistic/realistic/mixed]", "conflict_resolution_style": "[string]", "relationship_orientation": "[string]" Writing Sample: {writing_sample} ''' # Call Ollama ollama_response = _call_ollama(prompt, model=OLLAMA_MODEL_NAME, temperature=1) if not ollama_response: logger.error("No response from Ollama.") return None logger.debug(f"Ollama response: {ollama_response}") # Extract JSON from the assistant's message json_str = re.search(r'\{.*\}', ollama_response, re.DOTALL) if json_str: analyzed_data = json.loads(json_str.group()) else: logger.error("No JSON object found in the response.") return None return analyzed_data except Exception as e: logger.error(f"Error with Ollama API: {e}") return None def generate_content(persona, prompt): """ Generates content based on a given persona and prompt. Parameters: - persona (Persona): The persona object with individual fields. - prompt (str): The prompt to write about. Returns: - str: The generated content. """ try: # Convert persona fields into a format suitable for the prompt persona_traits = { "Writing Style": { "vocabulary_complexity": f"{persona.vocabulary_complexity}/10", "sentence_structure": persona.sentence_structure, "paragraph_organization": persona.paragraph_organization, "tone": persona.tone, "punctuation_style": persona.punctuation_style, "pronoun_preference": persona.pronoun_preference, "formality_level": f"{persona.formality_level}/10" if persona.formality_level else "N/A", }, "Language Patterns": { "idiom_usage": f"{persona.idiom_usage}/10" if persona.idiom_usage else "N/A", "metaphor_frequency": f"{persona.metaphor_frequency}/10" if persona.metaphor_frequency else "N/A", "simile_frequency": f"{persona.simile_frequency}/10" if persona.simile_frequency else "N/A", "technical_jargon_usage": f"{persona.technical_jargon_usage}/10" if persona.technical_jargon_usage else "N/A", "humor_sarcasm_usage": f"{persona.humor_sarcasm_usage}/10" if persona.humor_sarcasm_usage else "N/A", }, "Personality": { "openness_to_experience": f"{persona.openness_to_experience}/10" if persona.openness_to_experience else "N/A", "conscientiousness": f"{persona.conscientiousness}/10" if persona.conscientiousness else "N/A", "extraversion": f"{persona.extraversion}/10" if persona.extraversion else "N/A", "agreeableness": f"{persona.agreeableness}/10" if persona.agreeableness else "N/A", "emotional_stability": f"{persona.emotional_stability}/10" if persona.emotional_stability else "N/A", "dominant_motivations": persona.dominant_motivations or "N/A", "core_values": persona.core_values or "N/A", "decision_making_style": persona.decision_making_style or "N/A", } } # Create the system prompt system_prompt = f"""You are a writer with the following characteristics: Writing Style: {persona_traits['Writing Style']} Language Patterns: {persona_traits['Language Patterns']} Personality: {persona_traits['Personality']} Write in a way that naturally reflects these characteristics. The response should include a title. """ # Combine system prompt and user prompt combined_prompt = f"{system_prompt}\n\nWrite about: {prompt}" # Call Ollama ollama_response = _call_ollama(combined_prompt, model=OLLAMA_MODEL_NAME, temperature=1, max_tokens=5000) if not ollama_response: logger.error("No response from Ollama.") return None return ollama_response except Exception as e: logger.error(f"Error generating content: {str(e)}") return None def save_blog_post(blog_post, title): """ Saves a blog post to a file. Parameters: - blog_post (str): The content of the blog post. - title (str): The title of the blog post. """ # Implement if needed pass
Here's the rewritten version of your `utils.py` considering the provided context:
gpt-4o
Your `utils.py` file has been updated with the structure to integrate your environment variables, improved logging, and functions for analyzing writing samples, generating content based on personas, and saving blog posts. Let me know if you need further modifications or enhancements.
gpt-4o
no, include this context and try to do what I asked at first: Ollama Blog Discord GitHub Search models Models Download profile Structured outputs December 6, 2024 Ollama playing with building blocks Ollama now supports structured outputs making it possible to constrain a model’s output to a specific format defined by a JSON schema. The Ollama Python and JavaScript libraries have been updated to support structured outputs. Use cases for structured outputs include: Parsing data from documents Extracting data from images Structuring all language model responses More reliability and consistency than JSON mode Get started Download the latest version of Ollama Upgrade to the latest version of the Ollama Python or JavaScript library: Python pip install -U ollama JavaScript npm i ollama To pass structured outputs to the model, the format parameter can be used in the cURL request or the format parameter in the Python or JavaScript libraries. cURL curl -X POST http://localhost:11434/api/chat -H "Content-Type: application/json" -d '{ "model": "llama3.1", "messages": [{"role": "user", "content": "Tell me about Canada."}], "stream": false, "format": { "type": "object", "properties": { "name": { "type": "string" }, "capital": { "type": "string" }, "languages": { "type": "array", "items": { "type": "string" } } }, "required": [ "name", "capital", "languages" ] } }' Output The response is returned in the format defined by the JSON schema in the request. { "capital": "Ottawa", "languages": [ "English", "French" ], "name": "Canada" } Python Using the Ollama Python library, pass in the schema as a JSON object to the format parameter as either dict or use Pydantic (recommended) to serialize the schema using model_json_schema(). from ollama import chat from pydantic import BaseModel class Country(BaseModel): name: str capital: str languages: list[str] response = chat( messages=[ { 'role': 'user', 'content': 'Tell me about Canada.', } ], model='llama3.1', format=Country.model_json_schema(), ) country = Country.model_validate_json(response.message.content) print(country) Output name='Canada' capital='Ottawa' languages=['English', 'French'] JavaScript Using the Ollama JavaScript library, pass in the schema as a JSON object to the format parameter as either object or use Zod (recommended) to serialize the schema using zodToJsonSchema(). import ollama from 'ollama'; import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; const Country = z.object({ name: z.string(), capital: z.string(), languages: z.array(z.string()), }); const response = await ollama.chat({ model: 'llama3.1', messages: [{ role: 'user', content: 'Tell me about Canada.' }], format: zodToJsonSchema(Country), }); const country = Country.parse(JSON.parse(response.message.content)); console.log(country); Output { name: "Canada", capital: "Ottawa", languages: [ "English", "French" ], } Examples Data extraction To extract structured data from text, define a schema to represent information. The model then extracts the information and returns the data in the defined schema as JSON: from ollama import chat from pydantic import BaseModel class Pet(BaseModel): name: str animal: str age: int color: str | None favorite_toy: str | None class PetList(BaseModel): pets: list[Pet] response = chat( messages=[ { 'role': 'user', 'content': ''' I have two pets. A cat named Luna who is 5 years old and loves playing with yarn. She has grey fur. I also have a 2 year old black cat named Loki who loves tennis balls. ''', } ], model='llama3.1', format=PetList.model_json_schema(), ) pets = PetList.model_validate_json(response.message.content) print(pets) Example output pets=[ Pet(name='Luna', animal='cat', age=5, color='grey', favorite_toy='yarn'), Pet(name='Loki', animal='cat', age=2, color='black', favorite_toy='tennis balls') ] Image description Structured outputs can also be used with vision models. For example, the following code uses llama3.2-vision to describe the following image and returns a structured output: image from ollama import chat from pydantic import BaseModel class Object(BaseModel): name: str confidence: float attributes: str class ImageDescription(BaseModel): summary: str objects: List[Object] scene: str colors: List[str] time_of_day: Literal['Morning', 'Afternoon', 'Evening', 'Night'] setting: Literal['Indoor', 'Outdoor', 'Unknown'] text_content: Optional[str] = None path = 'path/to/image.jpg' response = chat( model='llama3.2-vision', format=ImageDescription.model_json_schema(), # Pass in the schema for the response messages=[ { 'role': 'user', 'content': 'Analyze this image and describe what you see, including any objects, the scene, colors and any text you can detect.', 'images': [path], }, ], options={'temperature': 0}, # Set temperature to 0 for more deterministic output ) image_description = ImageDescription.model_validate_json(response.message.content) print(image_description) Example output summary='A palm tree on a sandy beach with blue water and sky.' objects=[ Object(name='tree', confidence=0.9, attributes='palm tree'), Object(name='beach', confidence=1.0, attributes='sand') ], scene='beach', colors=['blue', 'green', 'white'], time_of_day='Afternoon' setting='Outdoor' text_content=None OpenAI compatibility from openai import OpenAI import openai from pydantic import BaseModel client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama") class Pet(BaseModel): name: str animal: str age: int color: str | None favorite_toy: str | None class PetList(BaseModel): pets: list[Pet] try: completion = client.beta.chat.completions.parse( temperature=0, model="llama3.1:8b", messages=[ {"role": "user", "content": ''' I have two pets. A cat named Luna who is 5 years old and loves playing with yarn. She has grey fur. I also have a 2 year old black cat named Loki who loves tennis balls. '''} ], response_format=PetList, ) pet_response = completion.choices[0].message if pet_response.parsed: print(pet_response.parsed) elif pet_response.refusal: print(pet_response.refusal) except Exception as e: if type(e) == openai.LengthFinishReasonError: print("Too many tokens: ", e) pass else: print(e) pass Tips For reliable use of structured outputs, consider to: Use Pydantic (Python) or Zod (JavaScript) to define the schema for the response Add “return as JSON” to the prompt to help the model understand the request Set the temperature to 0 for more deterministic output What’s next? Exposing logits for controlled generation Performance and accuracy improvements for structured outputs GPU acceleration for sampling Additional format support beyond JSON schema © 2024 Ollama Blog Docs GitHub Discord X (Twitter) Meetups
Here’s an updated version of your `utils.py` file, including the integration of Ollama's structured outputs using Python. This file demonstrates how to use structured outputs to analyze writing samples, generate persona-specific blog content, and more. ### `utils.py` ```python import os import logging from ollama import chat from pydantic import BaseModel from typing import List, Optional, Literal # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Environment variables OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "llama3.1") API_URL = os.getenv("OLLAMA_API_URL", "http://localhost:11434/api/chat") # Define structured output schemas class Persona(BaseModel): name: str traits: List[str] tone: str class BlogPost(BaseModel): title: str content: str summary: str class WritingSampleAnalysis(BaseModel): frequent_words: List[str] sentiment: str insights: List[str] # Utility functions def analyze_writing_sample(sample: str) -> WritingSampleAnalysis: """ Analyzes a given writing sample and returns structured insights. """ logger.info("Analyzing writing sample.") response = chat( messages=[{"role": "user", "content": sample}], model=OLLAMA_MODEL, format=WritingSampleAnalysis.model_json_schema(), ) return WritingSampleAnalysis.model_validate_json(response.message.content) def generate_blog_post(title: str, persona: Persona) -> BlogPost: """ Generates a blog post based on a title and a specific persona. """ prompt = f"Write a blog post titled '{title}' in the tone of {persona.tone} based on these traits: {', '.join(persona.traits)}." logger.info(f"Generating blog post for title: {title} and persona: {persona.name}.") response = chat( messages=[{"role": "user", "content": prompt}], model=OLLAMA_MODEL, format=BlogPost.model_json_schema(), ) return BlogPost.model_validate_json(response.message.content) def save_blog_post(post: BlogPost, filepath: str) -> None: """ Saves a blog post to a specified file path. """ logger.info(f"Saving blog post to {filepath}.") with open(filepath, "w") as file: file.write(f"Title: {post.title}\n\n{post.content}\n\nSummary: {post.summary}") # Example usage if __name__ == "__main__": # Analyze a writing sample sample_text = "I enjoy writing blogs about technology and creativity." analysis = analyze_writing_sample(sample_text) logger.info(f"Analysis: {analysis}") # Define a persona persona = Persona( name="Tech Enthusiast", traits=["innovative", "informative", "engaging"], tone="professional" ) # Generate and save a blog post post_title = "The Future of AI in Everyday Life" blog_post = generate_blog_post(post_title, persona) save_blog_post(blog_post, "future_of_ai_blog.txt") ``` ### Key Features: 1. **Structured Outputs with Pydantic**: Models such as `Persona`, `BlogPost`, and `WritingSampleAnalysis` are used to define schemas for structured outputs. 2. **Ollama Integration**: Uses Ollama's Python library to pass JSON schema in the `format` parameter for consistent, structured responses. 3. **Logging**: Includes informative logging for better debugging and monitoring. ### Example Usage: 1. **Analyze a Writing Sample**: - Input: A string of text. - Output: Structured insights, including frequent words, sentiment, and additional insights. 2. **Generate Blog Post**: - Input: A title and a persona. - Output: A blog post with a title, content, and summary. 3. **Save Blog Post**: - Input: A `BlogPost` object and a file path. - Output: Saves the blog post to the specified file. This implementation is tailored to your needs and uses Ollama's structured outputs effectively. Let me know if you need further customization!
gpt-4o