diff --git a/.gitignore b/.gitignore index 64c5cb7..3163573 100644 --- a/.gitignore +++ b/.gitignore @@ -131,4 +131,6 @@ dist .editorconfig -personas/ \ No newline at end of file +personas/ + +.DS_Store \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 9c6c309..f50ff58 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,17 @@ import { makeRequest } from './utils/anthropicClient'; import { savePersonaJson } from './utils/savePersonaJson'; import { savePersonaDb } from './utils/savePersonaDb'; +import { generatePromptWithMBTI } from './utils/personalityTrait'; -const personaPromise = makeRequest(); +const prompt = + 'Generate a detailed, realistic persona with specific real-world values including store names, brands and locations. Use frequencies per week, ISO timestamps relative to the current week. Personality trait: '; + +const fullPrompt = generatePromptWithMBTI(prompt); + +const personaPromise = makeRequest(fullPrompt); personaPromise.then(persona => { - savePersonaDb(persona.text).then(id => savePersonaJson(persona.text, id)); + savePersonaDb(persona).then(id => savePersonaJson(persona, id)); - console.log('New persona:', persona.text); + console.log('New persona:', persona); }); diff --git a/src/utils/anthropicClient.ts b/src/utils/anthropicClient.ts index 1d2dfbb..7d2a9bf 100644 --- a/src/utils/anthropicClient.ts +++ b/src/utils/anthropicClient.ts @@ -1,82 +1,9 @@ import 'dotenv/config'; import Anthropic from '@anthropic-ai/sdk'; +import { Persona } from './types'; +import { PersonaTool } from './personaSchema'; -/* -THIS IS THE PROMPT IN HUMAN-READABLE FORMAT: - -Generate a JSON object for a persona with the following schema: -{ - "core": { - "age": , - "name": , - "occupation": { - "title": , - "level": , - "income": , - "location": , - "schedule": - }, - "home": { - "type": , - "ownership": , - "location": , - "commute_distance_km": - }, - "household": { - "status": , - "members": , - "pets": - } - }, - "routines": { - "weekday": , - "weekend": , - "commute": { - "method": , - "route": , - "regular_stops": - } - }, - "preferences": { - "diet": , - "brands": , - "price_sensitivity": , - "payment_methods": , - "shopping": { - "grocery_stores": , - "coffee_shops": , - "restaurants": , - "retail": - } - }, - "finances": { - "subscriptions": , - "regular_bills": , - "spending_patterns": { - "impulsive_score": , - "categories": - } - }, - "habits": { - "exercise": , - "social": , - "entertainment": , - "vices": - }, - "context": { - "stress_triggers": , - "reward_behaviors": , - "upcoming_events": , - "recent_changes": - } -} -Generate realistic values for all fields, including specific store names, brands, and locations. All temporal data should be relative to the current week. Frequencies should be specified as times per week. Include coordinates for locations. Use ISO timestamps for dates. -*/ - -const personaCreationPrompt = - 'Generate a detailed JSON object for a realistic persona following this schema, populating all fields with specific real-world values including store names, brands, coordinates, and locations. Use frequencies per week, ISO timestamps relative to the current week, and include coordinates for all locations. The output should be a complete JSON object with core demographic details (age, job, housing, household), daily routines, shopping preferences with actual store names and visit frequencies, brand loyalties with scores, diet, payment preferences, exercise habits, social patterns, entertainment choices, upcoming events, and stress factors. The data should reflect the current week for all temporal values. Schema: {"core":{age:,"name": ,occupation:{title:,level:,income:,location:,schedule:},home:{type:,ownership:,location:,commute_distance_km:},household:{status:,members:,pets:}},"routines":{weekday:,weekend:,commute:{method:,route:,regular_stops:}},"preferences":{diet:,brands:,price_sensitivity:,payment_methods:,shopping:{grocery_stores:,coffee_shops:,restaurants:,retail:}},"finances":{subscriptions:,regular_bills:,spending_patterns:{impulsive_score:,categories:}},"habits":{exercise:,social:,entertainment:,vices:},"context":{stress_triggers:,reward_behaviors:,upcoming_events:,recent_changes:}}'; - -export async function makeRequest() { +export async function makeRequest(prompt: string) { if (!process.env.ANTHROPIC_API_KEY) { throw Error('Anthropic API key missing.'); } @@ -88,18 +15,23 @@ export async function makeRequest() { const response = await anthropic.messages.create({ model: 'claude-3-5-sonnet-20241022', max_tokens: 2000, - messages: [{ role: 'user', content: personaCreationPrompt }] + temperature: 1, + tools: [PersonaTool], + messages: [{ role: 'user', content: prompt }] }); - if ( - response.stop_reason && - response.stop_reason !== 'end_turn' && - response.stop_reason !== 'stop_sequence' - ) { - throw Error(response.stop_reason); + if (response.stop_reason && response.stop_reason !== 'tool_use') { + throw Error(JSON.stringify(response)); } - console.log('Message ID:', response.id); + if (response.content.length != 2) { + throw Error(JSON.stringify(response)); + } - return response.content[0] as { text: string }; + const content = response.content as [ + { type: string; text: string }, + { type: string; input: object } + ]; + + return content[1].input as Persona; } diff --git a/src/utils/parsePersona.ts b/src/utils/parsePersona.ts deleted file mode 100644 index 42421f2..0000000 --- a/src/utils/parsePersona.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Persona } from './types'; - -export function parsePersona(persona: string) { - return JSON.parse(persona) as Persona; -} diff --git a/src/utils/personaSchema.ts b/src/utils/personaSchema.ts new file mode 100644 index 0000000..1a1c32e --- /dev/null +++ b/src/utils/personaSchema.ts @@ -0,0 +1,376 @@ +export const PersonaTool = { + name: 'PersonaSchema' as const, + input_schema: { + type: 'object' as const, + description: 'User persona', + properties: { + core: { + type: 'object' as const, + description: 'Core user information and demographics', + properties: { + age: { + type: 'number' as const, + description: "User's age in years" + }, + name: { + type: 'string' as const, + description: "User's full name" + }, + occupation: { + type: 'object' as const, + description: 'Employment details', + properties: { + title: { + type: 'string' as const, + description: 'Job title' + }, + level: { + type: 'string' as const, + description: 'Career level (e.g., entry, senior, manager)' + }, + income: { + type: 'number' as const, + description: 'Annual income' + }, + location: { + type: 'string' as const, + description: 'Work location' + }, + schedule: { + type: 'array' as const, + description: 'Working days/hours', + items: { + type: 'string' as const + } + } + } + }, + home: { + type: 'object' as const, + description: 'Housing information', + properties: { + type: { + type: 'string' as const, + description: 'Type of residence (e.g., apartment, house)' + }, + ownership: { + type: 'string' as const, + description: 'Ownership status (e.g., owned, rented)' + }, + location: { + type: 'string' as const, + description: 'Home address or area' + }, + commute_distance_km: { + type: 'number' as const, + description: 'Distance to work in kilometers' + } + } + }, + household: { + type: 'object' as const, + description: 'Household composition', + properties: { + status: { + type: 'string' as const, + description: 'Marital/living status' + }, + members: { + type: 'array' as const, + description: 'Other household members', + items: { + type: 'string' as const + } + }, + pets: { + type: 'array' as const, + description: 'Household pets', + items: { + type: 'object' as const, + properties: { + type: { + type: 'string' as const, + description: 'Type of pet' + }, + name: { + type: 'string' as const, + description: "Pet's name" + } + } + } + } + } + } + } + }, + routines: { + type: 'object' as const, + description: 'Daily and weekly routines', + properties: { + weekday: { + type: 'object' as const, + description: 'Typical weekday schedule', + additionalProperties: { + type: 'object' as const, + properties: { + activity: { + type: 'string' as const, + description: 'Activity description' + }, + location: { + type: 'string' as const, + description: 'Location of activity' + }, + duration_minutes: { + type: 'number' as const, + description: 'Duration in minutes' + } + } + } + }, + weekend: { + type: 'array' as const, + description: 'Regular weekend activities', + items: { + type: 'string' as const + } + }, + commute: { + type: 'object' as const, + description: 'Commute details', + properties: { + method: { + type: 'string' as const, + description: 'Primary mode of transportation' + }, + route: { + type: 'array' as const, + description: 'Regular route points', + items: { + type: 'string' as const + } + }, + regular_stops: { + type: 'array' as const, + description: 'Regular stops during commute', + items: { + type: 'object' as const, + properties: { + location: { + type: 'string' as const, + description: 'Stop location' + }, + purpose: { + type: 'string' as const, + description: 'Purpose of stop' + }, + frequency: { + type: 'string' as const, + description: 'How often this stop is made' + } + } + } + } + } + } + } + }, + preferences: { + type: 'object' as const, + description: 'User preferences and habits', + properties: { + diet: { + type: 'array' as const, + description: 'Dietary preferences and restrictions', + items: { + type: 'string' as const + } + }, + brands: { + type: 'array' as const, + description: 'Brand preferences', + items: { + type: 'object' as const, + properties: { + name: { + type: 'string' as const, + description: 'Brand name' + }, + loyalty_score: { + type: 'number' as const, + description: 'Brand loyalty score (1-10)', + minimum: 1, + maximum: 10 + } + } + } + }, + price_sensitivity: { + type: 'number' as const, + description: 'Price sensitivity score (1-10)', + minimum: 1, + maximum: 10 + }, + payment_methods: { + type: 'array' as const, + description: 'Preferred payment methods', + items: { + type: 'string' as const + } + } + } + }, + finances: { + type: 'object' as const, + description: 'Financial information', + properties: { + subscriptions: { + type: 'array' as const, + description: 'Regular subscriptions', + items: { + type: 'object' as const, + properties: { + name: { + type: 'string' as const, + description: 'Subscription name' + }, + amount: { + type: 'number' as const, + description: 'Monthly cost' + }, + frequency: { + type: 'string' as const, + description: 'Billing frequency' + }, + next_due_date: { + type: 'string' as const, + description: 'Next payment date', + format: 'date' + } + } + } + }, + spending_patterns: { + type: 'object' as const, + description: 'Spending behavior', + properties: { + impulsive_score: { + type: 'number' as const, + description: 'Impulse buying tendency (1-10)', + minimum: 1, + maximum: 10 + }, + categories: { + type: 'object' as const, + description: 'Spending categories', + additionalProperties: { + type: 'object' as const, + properties: { + preference_score: { + type: 'number' as const, + description: 'Category preference (1-10)', + minimum: 1, + maximum: 10 + }, + monthly_budget: { + type: 'number' as const, + description: 'Typical monthly spend' + } + } + } + } + } + } + } + }, + habits: { + type: 'object' as const, + description: 'Regular activities and habits', + properties: { + exercise: { + type: 'array' as const, + description: 'Exercise routines', + items: { + type: 'object' as const, + properties: { + activity: { + type: 'string' as const, + description: 'Type of exercise' + }, + frequency: { + type: 'string' as const, + description: 'How often performed' + }, + duration_minutes: { + type: 'number' as const, + description: 'Typical duration' + } + } + } + }, + social: { + type: 'array' as const, + description: 'Social activities', + items: { + type: 'object' as const, + properties: { + activity: { + type: 'string' as const, + description: 'Type of social activity' + }, + frequency: { + type: 'string' as const, + description: 'How often performed' + } + } + } + } + } + }, + context: { + type: 'object' as const, + description: 'Contextual information', + properties: { + stress_triggers: { + type: 'array' as const, + description: 'Known stress factors', + items: { + type: 'string' as const + } + }, + reward_behaviors: { + type: 'array' as const, + description: 'Activities used as rewards', + items: { + type: 'string' as const + } + }, + upcoming_events: { + type: 'array' as const, + description: 'Planned future events', + items: { + type: 'object' as const, + properties: { + name: { + type: 'string' as const, + description: 'Event name' + }, + date: { + type: 'string' as const, + description: 'Event date', + format: 'date' + }, + importance: { + type: 'number' as const, + description: 'Event importance (1-10)', + minimum: 1, + maximum: 10 + } + } + } + } + } + } + } + } +} as const; diff --git a/src/utils/personalityTrait.ts b/src/utils/personalityTrait.ts new file mode 100644 index 0000000..a661b30 --- /dev/null +++ b/src/utils/personalityTrait.ts @@ -0,0 +1,61 @@ +import { MBTIType } from './types'; + +const mbtiTypes: MBTIType[] = [ + { + type: 'INTJ', + traits: ['analytical', 'planning-focused', 'independent', 'private'] + }, + { + type: 'ENTJ', + traits: ['strategic', 'leadership-oriented', 'decisive', 'organized'] + }, + { + type: 'ISFP', + traits: ['artistic', 'spontaneous', 'nature-loving', 'adaptable'] + }, + { type: 'ESFJ', traits: ['caring', 'social', 'traditional', 'organized'] }, + { type: 'INTP', traits: ['logical', 'abstract', 'adaptable', 'private'] }, + { + type: 'ENFP', + traits: ['enthusiastic', 'creative', 'spontaneous', 'people-oriented'] + }, + { type: 'ISTJ', traits: ['practical', 'factual', 'organized', 'reliable'] }, + { + type: 'ENFJ', + traits: ['charismatic', 'inspiring', 'idealistic', 'people-focused'] + }, + { + type: 'ISTP', + traits: ['practical', 'adaptable', 'experiential', 'logical'] + }, + { type: 'ESFP', traits: ['spontaneous', 'energetic', 'social', 'practical'] }, + { + type: 'INFJ', + traits: ['idealistic', 'organized', 'insightful', 'private'] + }, + { + type: 'ESTP', + traits: ['energetic', 'practical', 'spontaneous', 'experiential'] + }, + { type: 'INFP', traits: ['idealistic', 'creative', 'authentic', 'adaptive'] }, + { + type: 'ENTP', + traits: ['innovative', 'adaptable', 'analytical', 'outgoing'] + }, + { type: 'ISFJ', traits: ['practical', 'caring', 'organized', 'traditional'] }, + { + type: 'ESTJ', + traits: ['practical', 'organized', 'leadership-oriented', 'traditional'] + } +]; + +export function generatePromptWithMBTI(prompt: string): string { + const selectedType = mbtiTypes[Math.floor(Math.random() * mbtiTypes.length)]; + + const mbtiJson = JSON.stringify({ + type: selectedType.type, + traits: selectedType.traits + }); + + return prompt.replace('', mbtiJson); +} diff --git a/src/utils/savePersonaDb.ts b/src/utils/savePersonaDb.ts index 62528a4..7d09653 100644 --- a/src/utils/savePersonaDb.ts +++ b/src/utils/savePersonaDb.ts @@ -1,12 +1,11 @@ import { prisma } from './prismaClient'; +import { Persona } from './types'; -export async function savePersonaDb(persona: string) { - const personaObject = JSON.parse(persona); - +export async function savePersonaDb(persona: Persona) { const result = await prisma.user.create({ data: { - name: personaObject['core']['name'], - age: personaObject['core']['age'], + name: persona.core.name, + age: persona.core.age, persona: JSON.stringify(persona) } }); diff --git a/src/utils/savePersonaJson.ts b/src/utils/savePersonaJson.ts index 995ef7a..87c3af3 100644 --- a/src/utils/savePersonaJson.ts +++ b/src/utils/savePersonaJson.ts @@ -1,10 +1,8 @@ import fs from 'fs'; -import { parsePersona } from './parsePersona'; +import { Persona } from './types'; -export function savePersonaJson(persona: string, id: number) { - fs.promises.writeFile(`personas/${id}.json`, persona, 'utf8'); +export function savePersonaJson(persona: Persona, id: number) { + fs.promises.writeFile(`personas/${id}.json`, JSON.stringify(persona), 'utf8'); - const personaObject = parsePersona(persona); - - console.log(`Persona ${personaObject.core.name} saved as persona/${id}.json`); + console.log(`Persona ${persona.core.name} saved as persona/${id}.json`); } diff --git a/src/utils/types.ts b/src/utils/types.ts index 5803aee..1de2897 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -116,3 +116,8 @@ export type Persona = { recent_changes: string[]; }; }; + +export interface MBTIType { + type: string; + traits: string[]; +}