Ollama Integration Guide
2024-12-2125 turns165,526 charsgpt-4o, o1⑂ 2 fork(s)
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