Integrating Anthropic API
2024-10-254 turns61,956 charso1-preview
Summary
modify the ghostwriter repo to use anthropic's api instead of xai's
Messages
Starting from the repo at https://github.com/kliewerdaniel/GhostWriter and modify it to use Anthropic's API instead of XAI's api, be sure to use the most up to date model and write out all the updates to this code: import logging
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status, generics
from .serializers import PersonaSerializer, BlogPostSerializer
from .models import Persona, BlogPost
from .utils import generate_content
logger = logging.getLogger(__name__)
class AnalyzeWritingSampleView(APIView):
def post(self, request, *args, **kwargs):
logger.debug(f"Request data: {request.data}")
serializer = PersonaSerializer(data=request.data)
if serializer.is_valid():
persona = serializer.save()
return Response(PersonaSerializer(persona).data, status=status.HTTP_201_CREATED)
else:
logger.error(f"Serializer validation failed: {serializer.errors}")
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class GenerateContentView(APIView):
def post(self, request):
persona_id = request.data.get('persona_id')
prompt = request.data.get('prompt')
if not persona_id:
logger.warning('persona_id is required.')
return Response({'error': 'persona_id is required.'}, status=status.HTTP_400_BAD_REQUEST)
if not prompt:
logger.warning('prompt is required.')
return Response({'error': 'prompt is required.'}, status=status.HTTP_400_BAD_REQUEST)
try:
persona = Persona.objects.get(id=persona_id)
except Persona.DoesNotExist:
logger.warning(f"Persona with ID {persona_id} not found.")
return Response({'error': f'Persona with ID {persona_id} not found'}, status=status.HTTP_404_NOT_FOUND)
# Call generate_content to create the blog post content
blog_post_content = generate_content(persona.data, prompt)
if not blog_post_content:
logger.error('Failed to generate blog post content.')
return Response({'error': 'Failed to generate blog post content.'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# Ensure content is split into title and body
lines = blog_post_content.strip().split('\n')
title = lines[0] if lines else 'Untitled'
content = '\n'.join(lines[1:]) if len(lines) > 1 else ''
# Create the BlogPost object and save it to the database
blog_post = BlogPost.objects.create(
persona=persona,
title=title,
content=content,
)
# Log for debugging purposes
logger.debug(f"Blog post created with title: {title}")
# Return the serialized blog post data
return Response(BlogPostSerializer(blog_post).data, status=status.HTTP_201_CREATED)
class PersonaListView(generics.ListAPIView):
queryset = Persona.objects.all()
serializer_class = PersonaSerializer
class PersonaDetailView(APIView):
def get(self, request, persona_id):
try:
persona = Persona.objects.get(id=persona_id)
except Persona.DoesNotExist:
logger.warning(f"Persona with ID {persona_id} not found.")
return Response({'error': 'Persona not found'}, status=status.HTTP_404_NOT_FOUND)
serializer = PersonaSerializer(persona)
return Response(serializer.data, status=status.HTTP_200_OK)
class BlogPostView(generics.ListAPIView):
queryset = BlogPost.objects.all().order_by('-created_at')
serializer_class = BlogPostSerializer
import logging
import requests
import json
import re
from decouple import config
logger = logging.getLogger(__name__)
XAI_API_KEY = config('XAI_API_KEY')
XAI_API_BASE = "https://api.x.ai/v1"
def analyze_writing_sample(writing_sample):
endpoint = f"{XAI_API_BASE}/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {XAI_API_KEY}"
}
payload = {
"messages": [
{
"role": "system",
"content": "You are an assistant that analyzes writing samples."
},
{
"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]",
"background": "[A brief paragraph describing the author's context, major influences, and any other relevant information not captured above]"
Writing Sample:
{writing_sample}
'''
}
],
"model": "grok-beta",
"stream": False,
"temperature": 0
}
try:
response = requests.post(endpoint, headers=headers, json=payload)
response.raise_for_status() # Raises HTTPError for bad responses
# Log the API response for debugging
logger.debug(f"OpenAI API response: {response.text}")
assistant_message = response.json()['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 requests.exceptions.RequestException as e:
logger.error(f"HTTP Request failed: {e}")
return None
except json.JSONDecodeError as e:
logger.error(f"JSON decoding failed: {e}")
return None
except Exception as e:
logger.error(f"Unexpected error: {e}")
return None
def generate_content(persona_data, prompt):
endpoint = f"{XAI_API_BASE}/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {XAI_API_KEY}"
}
# Format the persona data into a readable string
characteristics = '\n'.join([
f"{key.replace('_', ' ').capitalize()}: {value}"
for key, value in persona_data.items()
if value is not None and key not in ['id', 'name']
])
decoding_prompt = f'''
You are to write a response in the style of {persona_data.get('name', 'Unknown Author')}, a writer with the following characteristics:
{characteristics}
Now, please write a response in this style about the following topic:
"{prompt}"
Begin with a compelling title that reflects the content of the post.
'''
payload = {
"messages": [
{"role": "system", "content": "You are an assistant that generates blog posts."},
{"role": "user", "content": decoding_prompt}
],
"model": "grok-beta",
"stream": False,
"temperature": 0
}
try:
response = requests.post(endpoint, headers=headers, json=payload)
response.raise_for_status()
assistant_message = response.json()['choices'][0]['message']['content'].strip()
logger.debug(f"Assistant message: {assistant_message}")
return assistant_message
except requests.exceptions.RequestException as e:
logger.error(f"HTTP Request failed: {e}")
return ''
except json.JSONDecodeError as e:
logger.error(f"JSON decoding failed: {e}")
return ''
except Exception as e:
logger.error(f"Unexpected error: {e}")
return ''
def save_blog_post(blog_post, title):
# Implement if needed
pass
from django.urls import path
from .views import (
AnalyzeWritingSampleView,
GenerateContentView,
PersonaListView,
PersonaDetailView,
BlogPostView
)
urlpatterns = [
path('analyze/', AnalyzeWritingSampleView.as_view(), name='analyze-writing-sample'),
path('generate-content/', GenerateContentView.as_view(), name='generate-content'),
path('personas/', PersonaListView.as_view(), name='persona-list'),
path('personas/<int:persona_id>/', PersonaDetailView.as_view(), name='persona-detail'),
path('blog-posts/', BlogPostView.as_view(), name='blogpost-list'),
]
from rest_framework import serializers
from .models import Persona, BlogPost
from .utils import analyze_writing_sample
import logging
logger = logging.getLogger(__name__)
class PersonaSerializer(serializers.ModelSerializer):
writing_sample = serializers.CharField(write_only=True)
class Meta:
model = Persona
fields = ['id', 'name', 'writing_sample', 'data']
read_only_fields = ['id', 'data']
def create(self, validated_data):
writing_sample = validated_data.pop('writing_sample')
logger.debug(f"Writing sample received: {writing_sample[:100]}...") # Log first 100 characters
# Analyze the writing sample
analyzed_data = analyze_writing_sample(writing_sample)
logger.debug(f"Analyzed data: {analyzed_data}")
if not analyzed_data:
logger.error("analyze_writing_sample returned empty or invalid data.")
raise serializers.ValidationError({"writing_sample": "Failed to analyze the writing sample."})
# Store the analyzed data
validated_data['data'] = analyzed_data
# Save persona
return Persona.objects.create(**validated_data)
class BlogPostSerializer(serializers.ModelSerializer):
persona = serializers.StringRelatedField() # Displays persona name
class Meta:
model = BlogPost
fields = ['id', 'persona', 'title', 'content', 'created_at']
from django.db import models
class Persona(models.Model):
name = models.CharField(max_length=100, blank=True, null=True)
data = models.JSONField(blank=True, null=True) # Stores analyzed writing sample data
def __str__(self):
return self.name
class BlogPost(models.Model):
id = models.BigAutoField(primary_key=True)
persona = models.ForeignKey(Persona, on_delete=models.CASCADE, related_name='blog_posts')
title = models.CharField(max_length=200, blank=True, null=True)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title or f"BlogPost {self.id}"
"""
Django settings for backend project.
Generated by 'django-admin startproject' using Django 5.1.2.
For more information on this file, see
https://docs.djangoproject.com/en/5.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.1/ref/settings/
"""
# Ensure you have python-decouple installed:
# pip install python-decouple
import decouple
from decouple import config
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-afq-u-)(^1od2wb_(4oke^ky00%p-0$+o%e6bfw0nucj30$0$r'
XAI_API_KEY = config('XAI_API_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'core',
'corsheaders',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'backend.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'backend.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
CORS_ALLOWED_ORIGINS = [
'http://localhost:3000',
'http://localhost:3001',
]
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('core.urls')), # Prefix API URLs with /api/
]
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import UploadSample from './components/UploadSample';
import PersonaList from './components/PersonaList';
import GenerateContent from './components/GenerateContent';
import BlogPosts from './components/BlogPosts';
import NavBar from './components/NavBar';
const App: React.FC = () => {
return (
<Router>
<NavBar />
<div style={{ padding: '20px' }}>
<Routes>
<Route path="/" element={<UploadSample />} />
<Route path="/personas" element={<PersonaList />} />
<Route path="/generate" element={<GenerateContent />} />
<Route path="/blog-posts" element={<BlogPosts />} />
</Routes>
</div>
</Router>
);
};
export default App;
import axios from 'axios';
const instance = axios.create({
baseURL: 'http://localhost:8000/api/', // Adjust the baseURL if needed
});
export default instance;
// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client'; // Updated for React 18
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
import React, { useEffect, useState } from 'react';
import axios from '../axiosConfig'; // Adjust the path if necessary
import { CircularProgress, Typography, Box, Card, CardContent } from '@mui/material';
interface BlogPost {
id: number;
persona: string;
title: string;
content: string;
created_at: string;
}
const BlogPosts: React.FC = () => {
const [blogPosts, setBlogPosts] = useState<BlogPost[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchBlogPosts = async () => {
try {
const response = await axios.get('blog-posts/');
setBlogPosts(response.data);
} catch (err) {
console.error('Error fetching blog posts:', err);
setError('Failed to load blog posts.');
} finally {
setLoading(false);
}
};
fetchBlogPosts();
}, []);
if (loading) {
return (
<Box display="flex" justifyContent="center" alignItems="center" height="100vh">
<CircularProgress />
</Box>
);
}
if (error) {
return (
<Box display="flex" justifyContent="center" alignItems="center" height="100vh">
<Typography variant="h6" color="error">
{error}
</Typography>
</Box>
);
}
return (
<Box p={4}>
<Typography variant="h4" gutterBottom>
Output
</Typography>
{blogPosts.length === 0 ? (
<Typography variant="body1">No blog posts found.</Typography>
) : (
blogPosts.map((post) => (
<Card key={post.id} variant="outlined" sx={{ mb: 2 }}>
<CardContent>
<Typography variant="h5" gutterBottom>
{post.title || 'Untitled'}
</Typography>
<Typography variant="body2" paragraph>
{post.content}
</Typography>
<Typography variant="caption" color="text.secondary">
By: {post.persona} on {new Date(post.created_at).toLocaleString()}
</Typography>
</CardContent>
</Card>
))
)}
</Box>
);
};
export default BlogPosts;
import React, { useState } from 'react';
import axios from '../axiosConfig'; // Adjust the path if necessary
import { useSearchParams } from 'react-router-dom';
import { Box, Button, TextField, Typography, Alert, CircularProgress, Card, CardContent } from '@mui/material';
interface BlogPost {
id: number;
persona: string;
title: string;
content: string;
created_at: string;
}
const GenerateContent: React.FC = () => {
const [searchParams] = useSearchParams();
const personaIdParam = searchParams.get('personaId');
const personaId = personaIdParam ? Number(personaIdParam) : null;
const [prompt, setPrompt] = useState<string>('');
const [content, setContent] = useState<BlogPost | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const handleGenerate = async () => {
if (!prompt) {
setError('Please enter a prompt.');
return;
}
if (!personaId) {
setError('Invalid Persona ID.');
return;
}
setLoading(true);
setError(null);
try {
const response = await axios.post('generate-content/', {
persona_id: personaId,
prompt: prompt,
});
setContent(response.data);
setError(null);
setPrompt('');
} catch (err: any) {
console.error('Error generating content:', err);
if (err.response && err.response.data) {
setError(JSON.stringify(err.response.data));
} else {
setError('Failed to generate content.');
}
} finally {
setLoading(false);
}
};
return (
<Box p={4} maxWidth="600px" mx="auto">
<Typography variant="h4" gutterBottom>
Generate Content
</Typography>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
<TextField
label="Prompt"
variant="outlined"
fullWidth
multiline
rows={4}
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Enter a topic or prompt..."
required
sx={{ mb: 3 }}
/>
<Button
onClick={handleGenerate}
variant="contained"
color="primary"
disabled={loading}
fullWidth
>
{loading ? <CircularProgress size={24} /> : 'Generate Content'}
</Button>
{content && (
<Card variant="outlined" sx={{ mt: 4 }}>
<CardContent>
<Typography variant="h5" gutterBottom>
{content.title || 'Untitled'}
</Typography>
<Typography variant="body1">
{content.content}
</Typography>
</CardContent>
</Card>
)}
</Box>
);
};
export default GenerateContent;
import { AppBar, Toolbar, Tabs, Tab, Box } from '@mui/material';
import { Link, useLocation, LinkProps } from 'react-router-dom';
import { styled } from '@mui/system';
import { TabProps } from '@mui/material/Tab'; // Import TabProps
const NavBar: React.FC = () => {
const location = useLocation();
const currentPath = location.pathname;
const StyledTab = styled(Tab)<TabProps & LinkProps>(({ theme }) => ({ // Extend with LinkProps
color: '#ffffff',
fontWeight: 'bold',
textTransform: 'none',
'&.Mui-selected': {
color: '#ffffff',
backgroundColor: '#333333',
borderRadius: theme.spacing(0.5),
},
'&:hover': {
color: '#aaaaaa',
},
}));
const StyledAppBar = styled(AppBar)({
backgroundColor: '#000000',
boxShadow: 'none',
});
return (
<StyledAppBar position="static">
<Toolbar>
<Box sx={{ flexGrow: 1 }}>
<Tabs value={currentPath} TabIndicatorProps={{ style: { backgroundColor: '#ffffff' } }}>
<StyledTab label="Upload Sample" value="/" component={Link} to="/" />
<StyledTab label="Personas" value="/personas" component={Link} to="/personas" />
<StyledTab label="Blog Posts" value="/blog-posts" component={Link} to="/blog-posts" />
</Tabs>
</Box>
</Toolbar>
</StyledAppBar>
);
};
export default NavBar;
/* General Container Styling */
.persona-list-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f9f9f9;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
/* Title Styling */
.title {
text-align: center;
font-size: 2rem;
margin-bottom: 20px;
color: #333;
}
/* Loading and Error Messages */
.loading,
.error,
.no-personas {
text-align: center;
font-size: 1.2rem;
color: #666;
}
/* Persona Card Container */
.persona-cards {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: center;
}
/* Persona Card Styling */
.persona-card {
background-color: #fff;
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
width: calc(33.33% - 20px);
min-width: 200px;
text-align: center;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.persona-card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
/* Persona Name Styling */
.persona-name {
font-size: 1.5rem;
margin-bottom: 15px;
color: #444;
}
/* Button Styling */
.generate-button {
background-color: #007bff;
color: #fff;
border: none;
padding: 10px 15px;
font-size: 1rem;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.2s;
}
.generate-button:hover {
background-color: #0056b3;
}
import React, { useEffect, useState } from 'react';
import axios from '../axiosConfig'; // Adjust the path if necessary
import { useNavigate } from 'react-router-dom';
import './PersonaList.css'; // Import the CSS file for styling
interface Persona {
id: number;
name: string;
data: Record<string, any>;
}
const PersonaList: React.FC = () => {
const [personas, setPersonas] = useState<Persona[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const navigate = useNavigate();
useEffect(() => {
const fetchPersonas = async () => {
try {
const response = await axios.get('personas/');
setPersonas(response.data);
} catch (err) {
console.error('Error fetching personas:', err);
setError('Failed to load personas.');
} finally {
setLoading(false);
}
};
fetchPersonas();
}, []);
const handleSelectPersona = (personaId: number) => {
navigate(`/generate?personaId=${personaId}`);
};
if (loading) return <div className="loading">Loading...</div>;
if (error) return <div className="error">{error}</div>;
return (
<div className="persona-list-container">
<h2 className="title">Saved Personas</h2>
{personas.length === 0 ? (
<p className="no-personas">No personas found.</p>
) : (
<div className="persona-cards">
{personas.map((persona) => (
<div key={persona.id} className="persona-card">
<h3 className="persona-name">{persona.name}</h3>
<button
className="generate-button"
onClick={() => handleSelectPersona(persona.id)}
>
Generate Content
</button>
</div>
))}
</div>
)}
</div>
);
};
export default PersonaList;
import React, { useState } from 'react';
import axios from '../axiosConfig'; // Adjust the path if necessary
import { Box, Button, TextField, Typography, Alert, Stack } from '@mui/material';
const UploadSample: React.FC = () => {
const [name, setName] = useState('');
const [writingSample, setWritingSample] = useState('');
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
const payload = {
name: name.trim(),
writing_sample: writingSample.trim(),
};
try {
console.log('Payload being sent:', payload);
const response = await axios.post('analyze/', payload);
console.log('Response received:', response.data);
setSuccess(`Persona "${response.data.name}" created successfully!`);
setError(null);
setName('');
setWritingSample('');
} catch (error: any) {
console.error('Error uploading writing sample:', error);
console.log('Error response:', error.response);
if (error.response && error.response.data) {
setError(JSON.stringify(error.response.data));
} else {
setError('An error occurred while uploading the writing sample.');
}
setSuccess(null);
}
};
return (
<Box p={4} maxWidth="600px" mx="auto">
<Typography variant="h4" gutterBottom>
Upload Writing Sample
</Typography>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
{success && (
<Alert severity="success" sx={{ mb: 2 }}>
{success}
</Alert>
)}
<form onSubmit={handleSubmit}>
<Stack spacing={3}>
<TextField
label="Persona Name"
variant="outlined"
fullWidth
value={name}
onChange={(e) => setName(e.target.value)}
required
inputProps={{ maxLength: 100 }}
/>
<TextField
label="Writing Sample"
variant="outlined"
fullWidth
multiline
rows={6}
value={writingSample}
onChange={(e) => setWritingSample(e.target.value)}
required
/>
<Button type="submit" variant="contained" color="primary" size="large">
Submit
</Button>
</Stack>
</form>
</Box>
);
};
export default UploadSample;
Sure! Below are the updated code snippets where we've replaced XAI's API with Anthropic's API. We've also ensured that the code uses the most up-to-date model (`claude-2`). Here are the changes:
---
### **Updated `utils.py`**
```python
import logging
import requests
import json
import re
from decouple import config
logger = logging.getLogger(__name__)
ANTHROPIC_API_KEY = config('ANTHROPIC_API_KEY')
ANTHROPIC_API_BASE = "https://api.anthropic.com"
def analyze_writing_sample(writing_sample):
endpoint = f"{ANTHROPIC_API_BASE}/v1/complete"
headers = {
"Content-Type": "application/json",
"X-API-Key": ANTHROPIC_API_KEY
}
# Build the prompt with the required instructions and writing sample
instructions = 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]",
"background": "[A brief paragraph describing the author's context, major influences, and any other relevant information not captured above]"
Writing Sample:
{writing_sample}
'''
# Prepare the prompt for Anthropic API
prompt = f"Human: {instructions.strip()}\n\nAssistant:"
payload = {
"prompt": prompt,
"model": "claude-2",
"max_tokens_to_sample": 1000,
"temperature": 0,
"stop_sequences": ["\n\nHuman:"]
}
try:
response = requests.post(endpoint, headers=headers, json=payload)
response.raise_for_status() # Raises HTTPError for bad responses
# Log the API response for debugging
logger.debug(f"Anthropic API response: {response.text}")
assistant_message = response.json()['completion'].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 requests.exceptions.RequestException as e:
logger.error(f"HTTP Request failed: {e}")
return None
except json.JSONDecodeError as e:
logger.error(f"JSON decoding failed: {e}")
return None
except Exception as e:
logger.error(f"Unexpected error: {e}")
return None
def generate_content(persona_data, prompt_text):
endpoint = f"{ANTHROPIC_API_BASE}/v1/complete"
headers = {
"Content-Type": "application/json",
"X-API-Key": ANTHROPIC_API_KEY
}
# Format the persona data into a readable string
characteristics = '\n'.join([
f"{key.replace('_', ' ').capitalize()}: {value}"
for key, value in persona_data.items()
if value is not None and key not in ['id', 'name']
])
decoding_prompt = f'''
You are to write a response in the style of {persona_data.get('name', 'Unknown Author')}, a writer with the following characteristics:
{characteristics}
Now, please write a response in this style about the following topic:
"{prompt_text}"
Begin with a compelling title that reflects the content of the post.
'''
# Prepare the prompt for Anthropic API
prompt = f"Human: {decoding_prompt.strip()}\n\nAssistant:"
payload = {
"prompt": prompt,
"model": "claude-2",
"max_tokens_to_sample": 1000,
"temperature": 0,
"stop_sequences": ["\n\nHuman:"]
}
try:
response = requests.post(endpoint, headers=headers, json=payload)
response.raise_for_status()
assistant_message = response.json()['completion'].strip()
logger.debug(f"Assistant message: {assistant_message}")
return assistant_message
except requests.exceptions.RequestException as e:
logger.error(f"HTTP Request failed: {e}")
return ''
except json.JSONDecodeError as e:
logger.error(f"JSON decoding failed: {e}")
return ''
except Exception as e:
logger.error(f"Unexpected error: {e}")
return ''
```
---
### **Updated `settings.py`**
```python
# Ensure you have python-decouple installed:
# pip install python-decouple
import decouple
from decouple import config
# ...
# Replace XAI_API_KEY with ANTHROPIC_API_KEY
ANTHROPIC_API_KEY = config('ANTHROPIC_API_KEY')
# ...
```
---
### **Updates in `serializers.py` (if any references to XAI)**
No changes are needed in `serializers.py` as it uses the `analyze_writing_sample` function from `utils.py`, which we've already updated.
---
### **Updates in `views.py` (if any references to XAI)**
No changes are needed in `views.py` as it imports `generate_content` from `utils.py`, which we've already updated.
---
### **Updates in Other Files**
Ensure that in your environment variables or `.env` file, you have replaced `XAI_API_KEY` with `ANTHROPIC_API_KEY`.
---
### **Explanation of Changes**
- **API Endpoint Update**: Changed the API endpoint from XAI's to Anthropic's by updating `XAI_API_BASE` to `ANTHROPIC_API_BASE` and adjusting the endpoint paths accordingly.
- **Authentication Header**: Replaced `"Authorization": f"Bearer {XAI_API_KEY}"` with `"X-API-Key": ANTHROPIC_API_KEY` to match Anthropic's authentication method.
- **Payload Structure**: Adjusted the payload to fit Anthropic's API requirements. Instead of using `messages`, we now construct a single `prompt` string that simulates a conversation between `Human:` and `Assistant:`.
- **Model Update**: Updated the model from `"grok-beta"` to `"claude-2"` to use Anthropic's latest model.
- **Prompt Formatting**: Wrapped instructions and content in `Human:` and `Assistant:` format as required by Anthropic's API.
- **Response Parsing**: Updated the response parsing to extract the assistant's completion from `response.json()['completion']`.
- **Error Handling**: Adjusted error handling to account for changes in the response structure.
---
### **Environment Variable**
Make sure to set your Anthropic API key in your environment variables or `.env` file:
```
ANTHROPIC_API_KEY=your_anthropic_api_key_here
```
---
### **Additional Notes**
- **Max Tokens**: Adjust `max_tokens_to_sample` in the payload if you expect longer responses.
- **Stop Sequences**: Added `stop_sequences` with `["\n\nHuman:"]` to ensure the assistant stops generating when it sees a new `Human:` prompt.
- **Temperature**: Kept `temperature` at `0` for deterministic outputs. Adjust as needed.
---
By making these changes, your application should now use Anthropic's API instead of XAI's API while maintaining the desired functionality.
o1-preview
Teflon Content Script injected
contentscript.bundle.js:15045 Logger init
chunk-NUMECXU6.js?v=06cd9abe:21551 Download the React DevTools for a better development experience: https://reactjs.org/link/react-devtools
page_all.js:2 init command html
page_all.js:2 init command http://localhost:5173/
page_all.js:2 init command clearCart
page_all.js:2 init command http://localhost:5173/
page_all.js:2 init command getOrdersListProduct
page_all.js:2 init command http://localhost:5173/
page_all.js:2 init command getTrackDetals
page_all.js:2 init command http://localhost:5173/
page_all.js:2 init command getOrderDetail
page_all.js:2 init command http://localhost:5173/
page_all.js:2 init command getReview
page_all.js:2 init command http://localhost:5173/
page_all.js:2 init command clearAddres
page_all.js:2 init command http://localhost:5173/
page_all.js:2 init command countAddress
page_all.js:2 init command http://localhost:5173/
page_all.js:2 add class expansion-alids-init
@mui_material.js?v=66a6302e:42898 MUI: The `value` provided to the Tabs component is invalid.
None of the Tabs' children match with "/login".
You can provide one of the following values: /, /personas, /blog-posts.
getTabsMeta @ @mui_material.js?v=66a6302e:42898
(anonymous) @ @mui_material.js?v=66a6302e:42921
(anonymous) @ chunk-KI44QKTA.js?v=06cd9abe:6234
(anonymous) @ @mui_material.js?v=66a6302e:43133
commitHookEffectListMount @ chunk-NUMECXU6.js?v=06cd9abe:16915
commitPassiveMountOnFiber @ chunk-NUMECXU6.js?v=06cd9abe:18156
commitPassiveMountEffects_complete @ chunk-NUMECXU6.js?v=06cd9abe:18129
commitPassiveMountEffects_begin @ chunk-NUMECXU6.js?v=06cd9abe:18119
commitPassiveMountEffects @ chunk-NUMECXU6.js?v=06cd9abe:18109
flushPassiveEffectsImpl @ chunk-NUMECXU6.js?v=06cd9abe:19490
flushPassiveEffects @ chunk-NUMECXU6.js?v=06cd9abe:19447
performSyncWorkOnRoot @ chunk-NUMECXU6.js?v=06cd9abe:18868
flushSyncCallbacks @ chunk-NUMECXU6.js?v=06cd9abe:9119
(anonymous) @ chunk-NUMECXU6.js?v=06cd9abe:18627
Show 14 more frames
Show lessUnderstand this error
@mui_material.js?v=66a6302e:42898 MUI: The `value` provided to the Tabs component is invalid.
None of the Tabs' children match with "/login".
You can provide one of the following values: /, /personas, /blog-posts.
getTabsMeta @ @mui_material.js?v=66a6302e:42898
(anonymous) @ @mui_material.js?v=66a6302e:43040
(anonymous) @ chunk-KI44QKTA.js?v=06cd9abe:6234
(anonymous) @ @mui_material.js?v=66a6302e:43136
commitHookEffectListMount @ chunk-NUMECXU6.js?v=06cd9abe:16915
commitPassiveMountOnFiber @ chunk-NUMECXU6.js?v=06cd9abe:18156
commitPassiveMountEffects_complete @ chunk-NUMECXU6.js?v=06cd9abe:18129
commitPassiveMountEffects_begin @ chunk-NUMECXU6.js?v=06cd9abe:18119
commitPassiveMountEffects @ chunk-NUMECXU6.js?v=06cd9abe:18109
flushPassiveEffectsImpl @ chunk-NUMECXU6.js?v=06cd9abe:19490
flushPassiveEffects @ chunk-NUMECXU6.js?v=06cd9abe:19447
performSyncWorkOnRoot @ chunk-NUMECXU6.js?v=06cd9abe:18868
flushSyncCallbacks @ chunk-NUMECXU6.js?v=06cd9abe:9119
(anonymous) @ chunk-NUMECXU6.js?v=06cd9abe:18627
Show 14 more frames
Show lessUnderstand this error
@mui_material.js?v=66a6302e:42898 MUI: The `value` provided to the Tabs component is invalid.
None of the Tabs' children match with "/login".
You can provide one of the following values: /, /personas, /blog-posts.
getTabsMeta @ @mui_material.js?v=66a6302e:42898
(anonymous) @ @mui_material.js?v=66a6302e:42921
(anonymous) @ chunk-KI44QKTA.js?v=06cd9abe:6234
(anonymous) @ @mui_material.js?v=66a6302e:43133
commitHookEffectListMount @ chunk-NUMECXU6.js?v=06cd9abe:16915
invokePassiveEffectMountInDEV @ chunk-NUMECXU6.js?v=06cd9abe:18324
invokeEffectsInDev @ chunk-NUMECXU6.js?v=06cd9abe:19701
commitDoubleInvokeEffectsInDEV @ chunk-NUMECXU6.js?v=06cd9abe:19686
flushPassiveEffectsImpl @ chunk-NUMECXU6.js?v=06cd9abe:19503
flushPassiveEffects @ chunk-NUMECXU6.js?v=06cd9abe:19447
performSyncWorkOnRoot @ chunk-NUMECXU6.js?v=06cd9abe:18868
flushSyncCallbacks @ chunk-NUMECXU6.js?v=06cd9abe:9119
(anonymous) @ chunk-NUMECXU6.js?v=06cd9abe:18627
Show 13 more frames
Show lessUnderstand this error
@mui_material.js?v=66a6302e:42898 MUI: The `value` provided to the Tabs component is invalid.
None of the Tabs' children match with "/login".
You can provide one of the following values: /, /personas, /blog-posts.
getTabsMeta @ @mui_material.js?v=66a6302e:42898
(anonymous) @ @mui_material.js?v=66a6302e:43040
(anonymous) @ chunk-KI44QKTA.js?v=06cd9abe:6234
(anonymous) @ @mui_material.js?v=66a6302e:43136
commitHookEffectListMount @ chunk-NUMECXU6.js?v=06cd9abe:16915
invokePassiveEffectMountInDEV @ chunk-NUMECXU6.js?v=06cd9abe:18324
invokeEffectsInDev @ chunk-NUMECXU6.js?v=06cd9abe:19701
commitDoubleInvokeEffectsInDEV @ chunk-NUMECXU6.js?v=06cd9abe:19686
flushPassiveEffectsImpl @ chunk-NUMECXU6.js?v=06cd9abe:19503
flushPassiveEffects @ chunk-NUMECXU6.js?v=06cd9abe:19447
performSyncWorkOnRoot @ chunk-NUMECXU6.js?v=06cd9abe:18868
flushSyncCallbacks @ chunk-NUMECXU6.js?v=06cd9abe:9119
(anonymous) @ chunk-NUMECXU6.js?v=06cd9abe:18627
Show 13 more frames
Show lessUnderstand this error
@mui_material.js?v=66a6302e:42898 MUI: The `value` provided to the Tabs component is invalid.
None of the Tabs' children match with "/login".
You can provide one of the following values: /, /personas, /blog-posts.
getTabsMeta @ @mui_material.js?v=66a6302e:42898
(anonymous) @ @mui_material.js?v=66a6302e:42921
(anonymous) @ chunk-KI44QKTA.js?v=06cd9abe:6234
(anonymous) @ @mui_material.js?v=66a6302e:43133
commitHookEffectListMount @ chunk-NUMECXU6.js?v=06cd9abe:16915
commitPassiveMountOnFiber @ chunk-NUMECXU6.js?v=06cd9abe:18156
commitPassiveMountEffects_complete @ chunk-NUMECXU6.js?v=06cd9abe:18129
commitPassiveMountEffects_begin @ chunk-NUMECXU6.js?v=06cd9abe:18119
commitPassiveMountEffects @ chunk-NUMECXU6.js?v=06cd9abe:18109
flushPassiveEffectsImpl @ chunk-NUMECXU6.js?v=06cd9abe:19490
flushPassiveEffects @ chunk-NUMECXU6.js?v=06cd9abe:19447
(anonymous) @ chunk-NUMECXU6.js?v=06cd9abe:19328
workLoop @ chunk-NUMECXU6.js?v=06cd9abe:197
flushWork @ chunk-NUMECXU6.js?v=06cd9abe:176
performWorkUntilDeadline @ chunk-NUMECXU6.js?v=06cd9abe:384
Show 15 more frames
Show lessUnderstand this error
@mui_material.js?v=66a6302e:42898 MUI: The `value` provided to the Tabs component is invalid.
None of the Tabs' children match with "/login".
You can provide one of the following values: /, /personas, /blog-posts.
getTabsMeta @ @mui_material.js?v=66a6302e:42898
(anonymous) @ @mui_material.js?v=66a6302e:43040
(anonymous) @ chunk-KI44QKTA.js?v=06cd9abe:6234
(anonymous) @ @mui_material.js?v=66a6302e:43136
commitHookEffectListMount @ chunk-NUMECXU6.js?v=06cd9abe:16915
commitPassiveMountOnFiber @ chunk-NUMECXU6.js?v=06cd9abe:18156
commitPassiveMountEffects_complete @ chunk-NUMECXU6.js?v=06cd9abe:18129
commitPassiveMountEffects_begin @ chunk-NUMECXU6.js?v=06cd9abe:18119
commitPassiveMountEffects @ chunk-NUMECXU6.js?v=06cd9abe:18109
flushPassiveEffectsImpl @ chunk-NUMECXU6.js?v=06cd9abe:19490
flushPassiveEffects @ chunk-NUMECXU6.js?v=06cd9abe:19447
(anonymous) @ chunk-NUMECXU6.js?v=06cd9abe:19328
workLoop @ chunk-NUMECXU6.js?v=06cd9abe:197
flushWork @ chunk-NUMECXU6.js?v=06cd9abe:176
performWorkUntilDeadline @ chunk-NUMECXU6.js?v=06cd9abe:384
Show 15 more frames
Show lessUnderstand this error
@mui_material.js?v=66a6302e:42898 MUI: The `value` provided to the Tabs component is invalid.
None of the Tabs' children match with "/login".
You can provide one of the following values: /, /personas, /blog-posts.
getTabsMeta @ @mui_material.js?v=66a6302e:42898
(anonymous) @ @mui_material.js?v=66a6302e:42921
(anonymous) @ chunk-KI44QKTA.js?v=06cd9abe:6234
(anonymous) @ @mui_material.js?v=66a6302e:43064
later @ chunk-KI44QKTA.js?v=06cd9abe:6068
setTimeout
debounced @ chunk-KI44QKTA.js?v=06cd9abe:6071
Show 6 more frames
Show lessUnderstand this error
UploadSample.tsx:22 Payload being sent: {name: 'Karamazov 3', writing_sample: 'Alexey Fyodorovitch Karamazov was the third son of…ebated everywhere at the time—the position of the'}
UploadSample.tsx:23
POST http://localhost:8000/api/personas/ 400 (Bad Request)
dispatchXhrRequest @ axios.js?v=a3e43c70:1680
xhr @ axios.js?v=a3e43c70:1560
dispatchRequest @ axios.js?v=a3e43c70:2035
Promise.then
_request @ axios.js?v=a3e43c70:2222
request @ axios.js?v=a3e43c70:2141
httpMethod @ axios.js?v=a3e43c70:2269
wrap @ axios.js?v=a3e43c70:8
handleSubmit @ UploadSample.tsx:23
callCallback2 @ chunk-NUMECXU6.js?v=06cd9abe:3674
invokeGuardedCallbackDev @ chunk-NUMECXU6.js?v=06cd9abe:3699
invokeGuardedCallback @ chunk-NUMECXU6.js?v=06cd9abe:3733
invokeGuardedCallbackAndCatchFirstError @ chunk-NUMECXU6.js?v=06cd9abe:3736
executeDispatch @ chunk-NUMECXU6.js?v=06cd9abe:7014
processDispatchQueueItemsInOrder @ chunk-NUMECXU6.js?v=06cd9abe:7034
processDispatchQueue @ chunk-NUMECXU6.js?v=06cd9abe:7043
dispatchEventsForPlugins @ chunk-NUMECXU6.js?v=06cd9abe:7051
(anonymous) @ chunk-NUMECXU6.js?v=06cd9abe:7174
batchedUpdates$1 @ chunk-NUMECXU6.js?v=06cd9abe:18913
batchedUpdates @ chunk-NUMECXU6.js?v=06cd9abe:3579
dispatchEventForPluginEventSystem @ chunk-NUMECXU6.js?v=06cd9abe:7173
dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay @ chunk-NUMECXU6.js?v=06cd9abe:5478
dispatchEvent @ chunk-NUMECXU6.js?v=06cd9abe:5472
dispatchDiscreteEvent @ chunk-NUMECXU6.js?v=06cd9abe:5449
Show 22 more frames
Show lessUnderstand this error
UploadSample.tsx:30 Error uploading writing sample: AxiosError {message: 'Request failed with status code 400', name: 'AxiosError', code: 'ERR_BAD_REQUEST', config: {…}, request: XMLHttpRequest, …}
handleSubmit @ UploadSample.tsx:30
await in handleSubmit
callCallback2 @ chunk-NUMECXU6.js?v=06cd9abe:3674
invokeGuardedCallbackDev @ chunk-NUMECXU6.js?v=06cd9abe:3699
invokeGuardedCallback @ chunk-NUMECXU6.js?v=06cd9abe:3733
invokeGuardedCallbackAndCatchFirstError @ chunk-NUMECXU6.js?v=06cd9abe:3736
executeDispatch @ chunk-NUMECXU6.js?v=06cd9abe:7014
processDispatchQueueItemsInOrder @ chunk-NUMECXU6.js?v=06cd9abe:7034
processDispatchQueue @ chunk-NUMECXU6.js?v=06cd9abe:7043
dispatchEventsForPlugins @ chunk-NUMECXU6.js?v=06cd9abe:7051
(anonymous) @ chunk-NUMECXU6.js?v=06cd9abe:7174
batchedUpdates$1 @ chunk-NUMECXU6.js?v=06cd9abe:18913
batchedUpdates @ chunk-NUMECXU6.js?v=06cd9abe:3579
dispatchEventForPluginEventSystem @ chunk-NUMECXU6.js?v=06cd9abe:7173
dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay @ chunk-NUMECXU6.js?v=06cd9abe:5478
dispatchEvent @ chunk-NUMECXU6.js?v=06cd9abe:5472
dispatchDiscreteEvent @ chunk-NUMECXU6.js?v=06cd9abe:5449
Show 15 more frames
Show lessUnderstand this error
UploadSample.tsx:31 Error response: {data: {…}, status: 400, statusText: 'Bad Request', headers: AxiosHeaders, config: {…}, …}October 25, 2024 - 17:03:18
Django version 5.1.2, using settings 'backend.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
[25/Oct/2024 17:04:13] "POST /api/token/ HTTP/1.1" 200 483
HTTP Request failed: 400 Client Error: Bad Request for url: https://api.anthropic.com/v1/complete
Failed to analyze writing sample.
Bad Request: /api/personas/
[25/Oct/2024 17:04:29] "POST /api/personas/ HTTP/1.1" 400 58
Based on the error logs you've provided, it seems that when you submit a writing sample to be analyzed, the server encounters a `400 Bad Request` error from Anthropic's API:
```
HTTP Request failed: 400 Client Error: Bad Request for url: https://api.anthropic.com/v1/complete
Failed to analyze writing sample.
```
This indicates that the request sent to Anthropic's API is malformed or contains invalid parameters. Let's work through the possible causes and solutions step by step.
---
## **1. Verify the API Key**
**Issue**: An incorrect or invalid API key can cause a `400 Bad Request` error.
**Solution**:
- **Check the Environment Variable**: Ensure that the `ANTHROPIC_API_KEY` environment variable is set correctly in your environment or `.env` file.
```env
ANTHROPIC_API_KEY=your_actual_anthropic_api_key
```
- **Restart the Server**: After setting the environment variable, restart your Django server to make sure it picks up the new value.
- **Test the API Key**: You can test your API key using a simple `curl` command or Postman to make sure it's valid.
---
## **2. Add Detailed Logging for the API Response**
**Issue**: Without the exact error message from Anthropic's API, it's challenging to diagnose the problem.
**Solution**:
Modify your `utils.py` functions to log the response content when a request fails.
### **Updated `utils.py`**
```python
import logging
import requests
import json
import re
from decouple import config
logger = logging.getLogger(__name__)
ANTHROPIC_API_KEY = config('ANTHROPIC_API_KEY')
ANTHROPIC_API_BASE = "https://api.anthropic.com"
def analyze_writing_sample(writing_sample):
endpoint = f"{ANTHROPIC_API_BASE}/v1/complete"
headers = {
"Content-Type": "application/json",
"X-API-Key": ANTHROPIC_API_KEY
}
# ... [rest of your code remains the same]
try:
response = requests.post(endpoint, headers=headers, json=payload)
response.raise_for_status() # Raises HTTPError for bad responses
# Log the API response for debugging
logger.debug(f"Anthropic API response: {response.text}")
# ... [rest of your code remains the same]
except requests.exceptions.HTTPError as e:
logger.error(f"HTTP Error: {e}")
logger.error(f"Response content: {response.text}") # Log the response content
return None
except requests.exceptions.RequestException as e:
logger.error(f"Request Exception: {e}")
return None
except json.JSONDecodeError as e:
logger.error(f"JSON decoding failed: {e}")
return None
except Exception as e:
logger.error(f"Unexpected error: {e}")
return None
# Similarly update the generate_content function to log response content on errors
def generate_content(persona_data, prompt_text):
# ... [rest of your code remains the same]
try:
response = requests.post(endpoint, headers=headers, json=payload)
response.raise_for_status()
assistant_message = response.json()['completion'].strip()
logger.debug(f"Assistant message: {assistant_message}")
return assistant_message
except requests.exceptions.HTTPError as e:
logger.error(f"HTTP Error: {e}")
logger.error(f"Response content: {response.text}") # Log the response content
return ''
except requests.exceptions.RequestException as e:
logger.error(f"Request Exception: {e}")
return ''
except json.JSONDecodeError as e:
logger.error(f"JSON decoding failed: {e}")
return ''
except Exception as e:
logger.error(f"Unexpected error: {e}")
return ''
```
**Explanation**:
- **Log Response Content**: When an `HTTPError` occurs, we log the `response.text` to capture the error message returned by Anthropic's API.
- **Separate Exception Handling**: Differentiate between `HTTPError` and other `RequestException` to provide more specific error information.
---
## **3. Verify Payload Structure and Parameters**
**Issue**: The payload sent to Anthropic's API might not match their expected format.
**Solution**:
- **Review Anthropic's API Documentation**: Ensure that the payload parameters match exactly what the API expects.
- **Check Model Name**: Confirm that `"model": "claude-2"` is a valid model name you have access to. Some users might need to specify the full model name like `"claude-2"` or `"claude-instant-1"` depending on their account privileges.
- **Adjust Payload**: Here's the updated payload with potential corrections:
```python
payload = {
"prompt": prompt,
"model": "claude-2",
"max_tokens_to_sample": 1000,
"temperature": 0,
"stop_sequences": ["\n\nHuman:"],
"stream": False # Add if streaming is not desired
}
```
- **Ensure Correct Data Types**:
- `temperature` should be a float between 0 and 1.
- `max_tokens_to_sample` should be an integer within allowed limits.
- **Remove Unnecessary Parameters**: If the API does not expect certain parameters, remove them.
---
## **4. Test the API Call Independently**
**Issue**: There might be issues specific to your application code.
**Solution**:
- **Use `curl` or Postman**: Make a direct API call to Anthropic's endpoint using the same payload and headers to see if it works outside your application.
```bash
curl https://api.anthropic.com/v1/complete \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_anthropic_api_key' \
-d '{
"prompt": "Human: Hello\n\nAssistant:",
"model": "claude-2",
"max_tokens_to_sample": 100,
"temperature": 0
}'
```
- **Check the Response**: If you receive a successful response, the issue might be in your application code. If not, the API key or payload might be incorrect.
---
## **5. Ensure Prompt Formatting**
**Issue**: Anthropic's API is sensitive to prompt formatting, especially the `Human:` and `Assistant:` tokens.
**Solution**:
- **Clean Up the Prompt**: Ensure there are no extra spaces or newlines that could affect the parsing.
- **Example Prompt**:
```python
prompt = f"Human: {instructions.strip()}\n\nAssistant:"
```
Ensure that `instructions` does not contain unintended leading or trailing whitespace.
---
## **6. Update Exception Handling to Provide More Insights**
**Issue**: The current exception handling may swallow important error information.
**Solution**:
Modify your exception handling to capture and log more details.
```python
except requests.exceptions.HTTPError as e:
logger.error(f"HTTP Error: {e}")
logger.error(f"Status Code: {response.status_code}")
logger.error(f"Response Content: {response.content.decode()}")
return None
```
---
## **7. Possible Issues with Model Access**
**Issue**: Your API key might not have access to the specified model.
**Solution**:
- **Verify Model Availability**: Check if your Anthropic account has access to the `claude-2` model.
- **Try a Different Model**: If unsure, try using a model like `claude-instant-1` which might have broader availability.
```python
payload = {
"prompt": prompt,
"model": "claude-instant-1",
"max_tokens_to_sample": 1000,
"temperature": 0,
"stop_sequences": ["\n\nHuman:"]
}
```
---
## **8. Confirm API Endpoint and Version**
**Issue**: There might be an issue with the API endpoint URL.
**Solution**:
- **Double-Check Endpoint**: Ensure that the endpoint is correct: `https://api.anthropic.com/v1/complete`
- **Check for Updates**: Verify if Anthropic has updated their API endpoints or parameters since you last checked.
---
## **9. Sample Code to Test the API Separately**
Create a simple Python script to test the API call:
```python
import requests
import os
ANTHROPIC_API_KEY = os.getenv('ANTHROPIC_API_KEY')
ANTHROPIC_API_BASE = "https://api.anthropic.com"
endpoint = f"{ANTHROPIC_API_BASE}/v1/complete"
headers = {
"Content-Type": "application/json",
"X-API-Key": ANTHROPIC_API_KEY
}
prompt = "Human: Hello\n\nAssistant:"
payload = {
"prompt": prompt,
"model": "claude-2",
"max_tokens_to_sample": 100,
"temperature": 0
}
response = requests.post(endpoint, headers=headers, json=payload)
print("Status Code:", response.status_code)
print("Response Content:", response.text)
```
Run this script to see if the API responds correctly.
---
## **10. Check Rate Limits and Quotas**
**Issue**: You might have exceeded your API usage limits.
**Solution**:
- **Check Account Usage**: Log into your Anthropic account dashboard to check your usage and ensure you haven't exceeded any limits.
- **Handle Rate Limiting**: If you're making multiple rapid requests, consider adding rate-limiting logic.
---
## **Summary**
By following these steps, you should be able to identify and resolve the issue causing the `400 Bad Request` error when communicating with Anthropic's API. The key actions are:
- Verify your API key and ensure it's correctly set.
- Add detailed logging to capture the response content from Anthropic's API.
- Ensure that the payload sent matches the API's expected format.
- Test the API call independently to isolate the issue.
- Adjust model names or parameters as necessary.
---
If you continue to face issues after these steps, please provide the specific error message returned in the response content from Anthropic's API (make sure to redact any sensitive information). This will help in diagnosing the problem more precisely.
o1-preview