DEV Community

Cover image for When I Fed 20,000 Words into an AI Prompt... It Gained a Personality: Part 2 – Prompt Design Deep Dive
Haru
Haru

Posted on

When I Fed 20,000 Words into an AI Prompt... It Gained a Personality: Part 2 – Prompt Design Deep Dive

Introduction

In the first part, I introduced VFriend, a chatbot app designed to simulate realistic conversations with AI-powered characters.

Key highlights included:

  • Ultra-detailed prompts to simulate personality
  • A human-like 3-layer memory system (instant, sequential, and summarized)
  • Characters that “grow” with continued interaction
  • Trio Mode for dynamic 3-character conversations
  • A polished UI/UX for immersive dialogue

This article dives into how I actually built all of that—the system architecture, logic flow, and prompt design behind the scenes.

We’ll cover:

  • The full tech stack and app structure
  • The chat processing flow (input → response → memory updates)
  • How the 20,000+ character prompts are built and managed

If you're curious how it all works, read on.

Project Overview

Let’s begin with the architecture and design philosophy behind VFriend.

This app dynamically generates 20,000-character prompts per chat turn, calls the OpenAI API, and processes real-time responses and memory updates.

It’s a complex system, and every layer of the stack was designed to handle this flow with clear responsibilities.

Tech Stack

Here’s what powers the project:

  • Backend: Laravel (using Breeze with separate guards for user/admin)
  • Frontend: React (via Inertia.js)
  • Chat UI: chat-ui-kit-react
  • Database: MySQL
  • AI Interface: OpenAI API (GPT-4.1 + GPT-4.1-mini)
  • Deployment: Amazon Lightsail

Directory Structure (Simplified)

app/

"app
├── Http
│   ├── Controllers
│   │   ├── Admin
│   │   │   ├── Auth
│   │   │   ├── AdminProfileController.php
│   │   │   ├── PromptController.php
│   │   │   └── UserController.php
│   │   ├── Auth
│   │   ├── ChatController.php
│   │   ├── Controller.php
│   │   └── ProfileController.php
│   ├── Middleware
│   └── Requests
├── Jobs
│   └── UpdateSummaryMemory.php
├── Models
│   ├── Admin.php
│   ├── Prompt.php
│   └── User.php
├── Notifications
├── Prompts
│   ├── ChatPromptBuilder.php
│   └── SummaryPromptBuilder.php
├── Providers
└── Services
    └── ChatService.php"
Enter fullscreen mode Exit fullscreen mode

resources/

"resources
├── css
│   └── app.css
├── js
│   ├── Components
│   │   ├── action
│   │   ├── content
│   │   ├── form
│   │   ├── function
│   │   ├── layout
│   │   └── page
│   ├── Layouts
│   │   ├── AdminAuthenticatedLayout.tsx
│   │   ├── AdminGuestLayout.tsx
│   │   ├── AuthenticatedLayout.tsx
│   │   └── GuestLayout.tsx
│   ├── lib
│   ├── Pages
│   │   ├── Admin
│   │   │   ├── Auth
│   │   │   ├── Profile
│   │   │   ├── Dashboard.tsx
│   │   │   ├── Prompt.tsx
│   │   │   └── Users.tsx
│   │   ├── Auth
│   │   ├── Profile
│   │   ├── Chat.tsx
│   │   └── Dashboard.tsx
│   ├── styles
│   ├── types
│   ├── app.tsx
│   ├── bootstrap.ts
│   └── ziggy.js
└── views
    └── app.blade.php"
Enter fullscreen mode Exit fullscreen mode

Environment Setup

The local environment is powered by Laravel Sail + MySQL, and uses Laravel Breeze for authentication.

To separate user and admin login flows, I implemented multi-guard auth, duplicating and adjusting Breeze’s controllers and views accordingly.

Production runs on Amazon Lightsail.

Design System

I reused my original Figma-based design system (inspired by Japan's Digital Agency UI guidelines) to style the frontend.

I ported existing component libraries from Next.js to React (Inertia.js), replacing Laravel Breeze’s default UI completely.

Chat UI: Chat.tsx

This file implements the shared chat interface for both Solo and Trio modes.

Key features include:

  • Based on chat-ui-kit-react
  • <Avatar> support for character icons
  • <TypingIndicator> during AI response
  • Auto-scroll behavior
  • Markdown rendering via react-markdown
  • XSS protection with dompurify + rehype-raw

Chat Logic: ChatController.php

This is the main controller handling:

  • Session management (read/write)
  • Switching between Solo and Trio modes
  • Prompt generation per character
  • Calling ChatService
  • Triggering UpdateSummaryMemory job (every 5 turns)

OpenAI API Integration: ChatService.php

This handles prompt dispatch and API calls:

  • Configures GPT model, parameters (temperature, token limit)
  • Invokes ChatPromptBuilder and SummaryPromptBuilder
  • Sends prompt to OpenAI, parses the response

Prompt Generators

  • ChatPromptBuilder.php: Builds prompts for real-time conversation
  • SummaryPromptBuilder.php: Builds prompts to summarize and store memory

More on structure below.

Summary Memory Job: UpdateSummaryMemory.php

  • Triggers every 5 turns asynchronously
  • Extracts key information and stores it in JSON
  • Feeds the updated memory back to the character’s prompt context

This architecture allows tight integration between frontend, backend, AI, and memory handling, enabling seamless and responsive chat.

Now let’s walk through the actual processing flow.

Processing Flow

Here’s how VFriend handles conversation under the hood—especially in Trio Mode (3 characters talking).

On Page Load

When the user opens the chat UI:

  1. Loads system prompt, user data, summary memory, and chat history from DB
  2. Stores them in the session
  3. Restores and displays previous conversation

On Message Send

When the user sends a message:

  1. Display "Typing..." state and disable input
  2. Pull session data:
    • System prompt
    • User profile
    • Summary memory
    • Sequential memory
    • Instant memory (last message)
  3. Generate prompt for Character 1 → send to API → show reply
  4. Generate prompt for Character 2 (based on Char 1's reply) → API → show
  5. Generate prompt for Character 3 (based on all prior) → API → show
  6. Save this as a new conversation turn (set of 3 replies)
  7. Update sequential memory (remove oldest if over 10)
  8. Save to DB
  9. Clear instant memory
  10. Every 5 turns → trigger async UpdateSummaryMemory job

Summary Memory Update (Async)

Triggered every 5 turns:

  1. Job pulls latest memory data
  2. Builds summary prompt
  3. Sends to GPT-4.1
  4. Parses and stores updated JSON summary
  5. Updates session data

So each chat turn includes:

  • Prompt generation using contextual memory
  • Separate prompts per character
  • Real-time memory growth
  • Blended synchronous + async processing

Let’s now dig into how the prompts themselves are built.

Prompt Architecture

The core of VFriend is its structured, layered prompts.

We use two types:

  • Chat prompts for character conversations
  • Summary prompts to update long-term memory

Each can exceed 20,000 characters, so they’re built using Markdown + JSON hierarchy to help GPT parse the data efficiently.

Chat Prompt Design

The chat prompt includes:

  • You (the character) – Self-definition (~3,000 chars)
  • User (the human) – Detailed profile (~2,000 chars)
  • Rules – Roleplay, memory, and conversation rules
  • Memory – Instant, sequential, and summary (~15,000+ chars)

Prompt Layout Example

# You (Character) – ~3000 chars

## Common
### EQ – Empathy Axis
### EX – Behavior Axis

## Personal
### Character Profile
### Conversation Style
### Personality & Emotion Handling
### Topics & Expressions

# User – ~2000 chars

## Nickname
## Life Summary

# Roleplay Rules
# Memory Rules
# Conversation Guidelines
# Instructions
Enter fullscreen mode Exit fullscreen mode

Memory is attached as JSON:

{
  "memory": {
    "summary": {
      "character1": "...",
      "character2": "...",
      "character3": "..."
    },
    "sequential": [
      {
        "turn": [
          { "message": "Hi!", "sender": "user" },
          { "message": "Hello!", "sender": "character1" }
        ]
      }
    ],
    "instant": [
      { "message": "What’s your favorite color?", "sender": "user" },
      { "message": "Pink, obviously!", "sender": "character1" }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Summary Prompt Design

Summary prompts are sent to a separate AI agent, “the summarizer.”
Prompt includes:

  • Same character + user structure (~4,000 chars)
  • Meta-instructions about memory type, mode, and goal
  • Memory blocks to summarize (sequential, etc.)
{
  "memory": {
    "summary": { ... },
    "sequential": [ ... ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Model Selection

Different GPT models are used per task:

Use Case Model Reason
Chat generation GPT-4.1-mini Fast + high rate limit (200K RPM)
Memory summary GPT-4.1 Higher cost, but better accuracy

Each prompt is precisely optimized to maintain personality, memory consistency, and natural dialogue—all within one massive request.


Conclusion

We’ve now covered how VFriend works under the hood:

  • Laravel + React tightly integrated
  • Async memory jobs keep things flowing
  • 20,000+ character prompts built with Markdown + JSON
  • Structured, layered prompts enable long-term memory and character growth

Coming Next...

In the next (and final) article, I’ll share the behind-the-scenes process of building VFriend:

  • Why I made it
  • What challenges I faced
  • How AI helped me build it in just 2 weeks

Part 3 – Devlog & Reflections

Top comments (0)