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"
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"
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
andSummaryPromptBuilder
- 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:
- Loads system prompt, user data, summary memory, and chat history from DB
- Stores them in the session
- Restores and displays previous conversation
On Message Send
When the user sends a message:
- Display "Typing..." state and disable input
- Pull session data:
- System prompt
- User profile
- Summary memory
- Sequential memory
- Instant memory (last message)
- Generate prompt for Character 1 → send to API → show reply
- Generate prompt for Character 2 (based on Char 1's reply) → API → show
- Generate prompt for Character 3 (based on all prior) → API → show
- Save this as a new conversation turn (set of 3 replies)
- Update sequential memory (remove oldest if over 10)
- Save to DB
- Clear instant memory
- Every 5 turns → trigger async UpdateSummaryMemory job
Summary Memory Update (Async)
Triggered every 5 turns:
- Job pulls latest memory data
- Builds summary prompt
- Sends to GPT-4.1
- Parses and stores updated JSON summary
- 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
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" }
]
}
}
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": [ ... ]
}
}
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
Top comments (0)