The profile structuring system converts unstructured user onboarding text into a strongly-typed JSON schema with normalized badge phrases. It extracts key attributes about users and their preferences, enabling structured matching logic while preserving the richness of natural language input.
Profile structuring happens during onboarding after users submit their “About Me” and preferences text. The system uses OpenAI GPT-4o-mini to parse free-form responses into two distinct sections:
About: Facts and qualities about the user themselves
Preferences: What the user wants in a partner
The AI is explicitly instructed not to mix these categories - personal attributes stay in “about” and partner preferences stay in “preferences”.
// Input examples and their normalized output:"i like people with unhinged humor" → "unhinged humor""someone who's really kind and emotionally mature" → ["kind", "emotionally mature"]"I'm a coffee enthusiast" → "coffee enthusiast""being spontaneous" → "spontaneous"
Normalization Implementation
src/lib/profileStructuring.ts
function normalizeBadgePhrase(value: string): string | null { let normalized = stripTrailingPunctuation(value.trim().toLowerCase()); if (!normalized) return null; // Strip "I'm" or "I am" prefix if (normalized.startsWith("i'm ")) { normalized = normalized.slice(4); } else if (normalized.startsWith("i am ")) { normalized = normalized.slice(5); } // Remove articles for (const article of ["a ", "an ", "the "]) { if (normalized.startsWith(article)) { normalized = normalized.slice(article.length); break; } } normalized = collapseWhitespace(normalized); // Remove common leading phrase patterns const LEADING_PHRASE_PREFIXES = [ "being ", "is ", "someone who is ", "someone who's ", "a person who is ", "person who is ", "someone that is ", "someone that's ", ]; for (const prefix of LEADING_PHRASE_PREFIXES) { if (normalized.startsWith(prefix)) { normalized = normalized.slice(prefix.length).trim(); break; } } return normalized || null;}
The system uses a detailed prompt to guide GPT-4o-mini through the structuring process:
src/lib/profileStructuring.ts
const { text } = await generateText({ model: "openai/gpt-4o-mini", prompt: `Convert this dating onboarding profile into structured JSON.Use this split:- "about" = facts and qualities about the user- "preferences" = what the user wants in a partnerImportant:- Do not put partner preferences inside "about".- Do not put personal self-description inside "preferences".- Do not invent facts; unknown should be null or [].- Preserve explicit answers from Q/A content when present.Name: ${input.dlName ?? "Unknown"}Age: ${input.dlAge ?? "Unknown"}Height: ${input.dlHeight ?? "Unknown"}City: ${input.city ?? "Unknown"}About:${input.aboutMe ?? "Unknown"}Preferences:${input.preferences ?? "Unknown"}Rules:- keep every value concise: 1-3 words only (prefer 1-2 words).- remove filler phrases (example: "being smart" -> "smart").- do real semantic normalization, not truncation.- examples: "i like people with unhinged humor" -> "unhinged humor"- keep array items short and specific.- if unknown, use null or [].- no markdown and no extra keys.`, maxOutputTokens: 700,});
import { structureUserProfile } from "@/lib/profileStructuring";const structured = await structureUserProfile({ aboutMe: "I work in tech and love hiking on weekends. Very introverted but love deep conversations.", preferences: "Looking for someone ambitious, kind, and into outdoor activities. Must be a dog person.", city: "San Francisco", dlName: "Alex Johnson", dlAge: 28, dlHeight: "5'10\"",});// Returns:// {// about: {// work: "tech",// hobbies: ["hiking"],// introExtro: "introverted",// ...// },// preferences: {// personalityTraits: ["ambitious", "kind"],// mustHaves: ["dog person"],// ...// }// }