- Регистрация
- 1 Мар 2015
- Сообщения
- 1,481
- Баллы
- 155
Introduction
In the , I introduced VFriend, a chatbot app designed to simulate realistic conversations with AI-powered characters.
Key highlights included:
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:
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:
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:
This is the main controller handling:
This handles prompt dispatch and API calls:
More on structure below.
Summary Memory Job: UpdateSummaryMemory.php
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:
When the user sends a message:
Triggered every 5 turns:
So each chat turn includes:
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:
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 (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:
{
"memory": {
"summary": { ... },
"sequential": [ ... ]
}
}
Model Selection
Different GPT models are used per task:
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:
In the next (and final) article, I’ll share the behind-the-scenes process of building VFriend:
In the , 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
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
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)
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
- 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
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
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)
# 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
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
→