저는 3년째 AI 챗봇 플랫폼을 운영하며 수많은 동반자(companion) 애플리케이션을 개발해왔습니다. 사용자가 감정적으로 연결되는 AI 캐릭터를 만들기 위해서는 단순한 채팅超越了 기술적 난관이 필요합니다. 이 튜토리얼에서는 캐릭터 카드 시스템, 장기 기억 아키텍처, 실시간 감정 엔진을 통합하는 프로덕션 수준의 시스템을 설계하고 구현합니다.

1. 시스템 아키텍처 설계

AI 동반자 애플리케이션의 핵심은 사용자와 AI 캐릭터 간의 감정적 연속성입니다. 이는 단순히 대화 로그를 저장하는 것이 아니라, 캐릭터의 일관된 페르소나, 누적된 경험 기억, 실시간 감정 상태를 통합적으로 관리해야 함을 의미합니다.

1.1 전체 아키텍처 개요

┌─────────────────────────────────────────────────────────────────┐
│                      Client Application                          │
│  (React Native / Flutter / Web)                                  │
└─────────────────────────┬───────────────────────────────────────┘
                          │ WebSocket / REST
                          ▼
┌─────────────────────────────────────────────────────────────────┐
│                    API Gateway Layer                             │
│  - Rate Limiting (100 req/min per user)                          │
│  - Token Budget Enforcement                                      │
│  - Request Validation                                             │
└─────────────────────────┬───────────────────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────────────────┐
│                 Application Service Layer                        │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐              │
│  │ Character    │ │ Memory       │ │ Emotion      │              │
│  │ Service      │ │ Service      │ │ Engine       │              │
│  └──────────────┘ └──────────────┘ └──────────────┘              │
└─────────────────────────┬───────────────────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────────────────┐
│                   Data Layer                                      │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐              │
│  │ PostgreSQL   │ │ Redis        │ │ Pinecone     │              │
│  │ (关系数据)    │ │ (缓存/会话)   │ │ (向量检索)    │              │
│  └──────────────┘ └──────────────┘ └──────────────┘              │
└─────────────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────────────┐
│              HolySheep AI Gateway                                │
│  https://api.holysheep.ai/v1 (단일 API 키로 전체 모델 통합)        │
│  - GPT-4.1: $8/MTok (고품질 캐릭터 응답)                           │
│  - Claude Sonnet 4: $15/MTok (긴 컨텍스트)                        │
│  - Gemini 2.5 Flash: $2.50/MTok (비용 효율적)                     │
│  - DeepSeek V3.2: $0.42/MTok (대량 메모리 처리)                    │
└─────────────────────────────────────────────────────────────────┘

1.2 비용 최적화 전략

실제 운영 데이터 기준, 일活跃用户 10,000명 규모의 동반자 앱 월간 비용 구조는 다음과 같습니다:

2. 캐릭터 카드 시스템 구현

캐릭터 카드는 AI 동반자의 정체성 정의입니다. 프로ンプ프트에 직접 삽입하면 컨텍스트 윈도우가 빠르게 소진됩니다. 효율적인 캐릭터 카드 설계 방법을 소개합니다.

2.1 캐릭터 카드 데이터 구조

# 캐릭터 카드 스키마 (Python/Pydantic)

from pydantic import BaseModel, Field
from typing import Optional, List, Dict
from enum import Enum

class PersonalityTrait(str, Enum):
    EXTROVERT = "외向적"
    INTROVERT = "내향적"
    OPTIMISTIC = "긍정적"
    PESSIMISTIC = "비관적"
    HUMOROUS = "유머러스함"
    SERIOUS = "진지함"

class CharacterCard(BaseModel):
    id: str = Field(..., description="캐릭터 고유 식별자")
    name: str = Field(..., max_length=50, description="캐릭터 이름")
    description: str = Field(..., max_length=500, description="짧은 설명")
    
    # 핵심 페르소나 속성
    personality: List[PersonalityTrait] = Field(
        default_factory=list,
        description="성격 특성 (2-4개 권장)"
    )
    speaking_style: str = Field(
        ...,
        description="말투 예: '~다 endings, 존댓말, 부드러운 어투'"
    )
    
    # 시각적 및 감정적 속성
    avatar_url: Optional[str] = None
    primary_emotion: str = "neutral"  # neutral, happy, sad, excited, anxious
    emotion_range: Dict[str, float] = Field(
        default_factory=lambda: {"min": 0.2, "max": 0.9},
        description="감정 표현 범위 (0.0-1.0)"
    )
    
    # 배경 스토리 (메모리 시스템과 연결)
    backstory: str = Field(
        default="",
        max_length=2000,
        description="캐릭터 배경 스토리 (메모리 검색용)"
    )
    
    # 대화 예시 (Few-shot 학습용)
    example_dialogs: List[Dict[str, str]] = Field(
        default_factory=list,
        description="[{'user': '...', 'character': '...'}] 형식"
    )
    
    # 메타데이터
    version: int = 1
    created_at: str
    updated_at: str

    def to_system_prompt(self) -> str:
        """캐릭터 카드를 시스템 프롬프트로 변환"""
        traits = ", ".join([t.value for t in self.personality])
        examples = "\n".join([
            f"User: {d['user']}\nCharacter: {d['character']}"
            for d in self.example_dialogs[:3]
        ])
        
        return f"""당신은 {self.name}입니다.

캐릭터 설명

{self.description}

성격 특성

{traits}

말투

{self.speaking_style}

감정 범위

기본 감정 상태: {self.primary_emotion} 감정 표현 강도: {self.emotion_range['min']:.0%} ~ {self.emotion_range['max']:.0%}

배경 스토리

{self.backstory}

대화 예시

{examples} 당신의 결정과 대답은 항상 이 캐릭터 설정에 맞게 이루어져야 합니다."""

2.2 HolySheep AI 게이트웨이 연동

# HolySheep AI 게이트웨이 연동 (Python)

import httpx
from typing import List, Dict, Optional, Generator
import json
import time

class HolySheepAIClient:
    """HolySheep AI 게이트웨이 클라이언트 - 캐릭터 동반자 최적화"""
    
    BASE_URL = "https://api.holysheep.ai/v1"
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.client = httpx.AsyncClient(
            timeout=httpx.Timeout(60.0, connect=10.0),
            limits=httpx.Limits(max_connections=100, max_keepalive_connections=20)
        )
    
    async def chat_completion(
        self,
        messages: List[Dict[str, str]],
        character: CharacterCard,
        memory_context: str = "",
        emotion_state: Dict = None,
        model: str = "gpt-4.1",
        temperature: float = 0.8,
        max_tokens: int = 500
    ) -> Dict:
        """
        캐릭터 기반 대화 생성
        
        Args:
            messages: 대화 이력
            character: 캐릭터 카드
            memory_context: 검색된 기억 컨텍스트
            emotion_state: 현재 감정 상태
            model: 사용 모델 (gpt-4.1, claude-3.5-sonnet, gemini-2.5-flash)
            temperature: 창의성 (0.5-1.0 권장)
            max_tokens: 최대 응답 길이
        
        Returns:
            AI 응답 및 메타데이터
        """
        # 감정 상태를 시스템 프롬프트에 반영
        emotion_instruction = ""
        if emotion_state:
            current_emotion = emotion_state.get("current", "neutral")
            intensity = emotion_state.get("intensity", 0.5)
            emotion_instruction = f"\n\n현재 감정 상태: {current_emotion} (강도: {intensity:.0%})"
        
        # 통합 시스템 프롬프트 구성
        system_prompt = f"""{character.to_system_prompt()}

관련 기억

{memory_context if memory_context else "특별한 기억이 없습니다."} {emotion_instruction} 사용자와의 대화를 자연스럽게 이어가세요. 감정적으로 반응하되 과하게 감정적이지 않도록 합니다.""" # 메시지 앞에 시스템 프롬프트 삽입 full_messages = [ {"role": "system", "content": system_prompt}, *messages[-10:] # 최근 10개 메시지만 사용 (토큰 절약) ] headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" } payload = { "model": model, "messages": full_messages, "temperature": temperature, "max_tokens": max_tokens } start_time = time.time() response = await self.client.post( f"{self.BASE_URL}/chat/completions", headers=headers, json=payload ) latency_ms = (time.time() - start_time) * 1000 if response.status_code != 200: raise Exception(f"API Error: {response.status_code} - {response.text}") result = response.json() return { "content": result["choices"][0]["message"]["content"], "model": result.get("model", model), "usage": result.get("usage", {}), "latency_ms": latency_ms } async def embed_texts(self, texts: List[str], model: str = "text-embedding-3-small") -> List[List[float]]: """ 텍스트를 벡터로 변환 (기억 검색용) DeepSeek Embeddings 모델 권장: $0.001/1K 토큰 """ headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" } payload = { "model": model, "input": texts } response = await self.client.post( f"{self.BASE_URL}/embeddings", headers=headers, json=payload ) if response.status_code != 200: raise Exception(f"Embedding Error: {response.text}") result = response.json() return [item["embedding"] for item in result["data"]] async def analyze_emotion(self, text: str) -> Dict: """ 텍스트 감정 분석 (Gemini 2.5 Flash 사용 - 비용 효율적) 응답 시간 목표: < 500ms """ headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" } payload = { "model": "gemini-2.5-flash", "messages": [ {"role": "system", "content": """다음 텍스트의 감정을 분석해주세요. 응답 형식 (JSON): { "emotion": "happy|sad|angry|fearful|surprised|neutral", "intensity": 0.0-1.0, "reason": "분석 근거" }"""}, {"role": "user", "content": text} ], "temperature": 0.3, "max_tokens": 100 } start_time = time.time() response = await self.client.post( f"{self.BASE_URL}/chat/completions", headers=headers, json=payload ) latency_ms = (time.time() - start_time) * 1000 if response.status_code != 200: return {"emotion": "neutral", "intensity": 0.5, "latency_ms": latency_ms} result = response.json() content = result["choices"][0]["message"]["content"] try: return json.loads(content) except: return {"emotion": "neutral", "intensity": 0.5, "latency_ms": latency_ms}

3. 기억 시스템 아키텍처

장기 기억은 동반자 애플리케이션의 소듕(소중한) 가치를 결정합니다. 사용자가 "우리 처음 만났을 때..."라고 말했을 때, AI가 그 대화를 기억해야 합니다.

3.1 계층적 기억 시스템

# 계층적 기억 시스템 구현

from dataclasses import dataclass, field
from typing import List, Optional, Dict
from datetime import datetime, timedelta
import asyncio

@dataclass
class Memory:
    """단일 기억 단위"""
    id: str
    content: str
    embedding: List[float]
    timestamp: datetime
    importance: float = 0.5  # 0.0-1.0
    emotional_valence: float = 0.0  # -1.0(부정)~1.0(긍정)
    access_count: int = 0
    last_accessed: Optional[datetime] = None
    
    def touch(self):
        self.access_count += 1
        self.last_accessed = datetime.utcnow()

@dataclass
class MemoryLayer:
    """기억 계층"""
    name: str
    max_size: int
    ttl_hours: int
    memories: List[Memory] = field(default_factory=list)
    
    def add(self, memory: Memory):
        self.memories.append(memory)
        if len(self.memories) > self.max_size:
            # 가장 오래되고 중요도가 낮은 기억 제거
            self.memories.sort(key=lambda m: (m.access_count, m.importance))
            self.memories.pop(0)

class CompanionMemorySystem:
    """
    계층적 기억 시스템
    
    계층 구조:
    1. Working Memory (Redis, 1시간 TTL) - 현재 대화 컨텍스트
    2. Episodic Memory (PostgreSQL, 30일) - 구체적 사건 기억
    3. Semantic Memory (Pinecone, 영구) - 일반 지식 및 관계
    """
    
    def __init__(self, ai_client: HolySheepAIClient, user_id: str):
        self.ai_client = ai_client
        self.user_id = user_id
        
        # 기억 계층 초기화
        self.working_memory = MemoryLayer("working", 50, 1)  # 최근 50개, 1시간
        self.episodic_memory = MemoryLayer("episodic", 1000, 720)  # 30일
        self.semantic_memory = []  # Pinecone 연동
        
    async def store_interaction(
        self, 
        user_message: str, 
        character_response: str,
        emotion_state: Dict
    ):
        """대화 상호작용을 기억으로 저장"""
        
        # 1. 감정 분석 수행
        emotion = emotion_state.get("emotion", "neutral")
        intensity = emotion_state.get("intensity", 0.5)
        emotional_valence = {"happy": 1.0, "sad": -0.8, "angry": -0.9}.get(emotion, 0.0)
        
        # 2. 중요도 계산 (감정 강도 기반)
        importance = min(1.0, (intensity * emotional_valence + 1) / 2)
        
        # 3. 임베딩 생성
        combined_text = f"사용자: {user_message}\n캐릭터: {character_response}"
        embeddings = await self.ai_client.embed_texts([combined_text])
        
        # 4. Working Memory에 저장
        working_memory = Memory(
            id=f"w_{self.user_id}_{datetime.utcnow().timestamp()}",
            content=combined_text,
            embedding=embeddings[0],
            timestamp=datetime.utcnow(),
            importance=importance,
            emotional_valence=emotional_valence
        )
        self.working_memory.add(working_memory)
        
        # 5. 중요 기억은 Episodic Memory로 승격
        if importance > 0.7 or emotional_valence > 0.5 or emotional_valence < -0.5:
            episodic_memory = Memory(
                id=f"e_{self.user_id}_{datetime.utcnow().timestamp()}",
                content=combined_text,
                embedding=embeddings[0],
                timestamp=datetime.utcnow(),
                importance=importance,
                emotional_valence=emotional_valence
            )
            self.episodic_memory.add(episodic_memory)
            await self.persist_to_database(episodic_memory)
    
    async def retrieve_relevant_memories(
        self, 
        current_context: str,
        limit: int = 5
    ) -> str:
        """현재 맥락과 관련된 기억 검색"""
        
        # 1. 현재 맥락 임베딩
        query_embedding = await self.ai_client.embed_texts([current_context])
        
        # 2. 모든 기억 풀에서 유사도 계산
        all_memories = (
            self.episodic_memory.memories + 
            self.semantic_memory
        )
        
        # 3. 코사인 유사도 기반 정렬
        scored_memories = []
        for memory in all_memories:
            similarity = self._cosine_similarity(query_embedding[0], memory.embedding)
            # 시간 감쇠 적용 (최근 기억 우선)
            time_diff = (datetime.utcnow() - memory.timestamp).total_seconds() / 3600
            time_decay = max(0.5, 1.0 - (time_diff / 720) * 0.5)  # 30일 후 50%
            
            final_score = similarity * 0.7 + memory.importance * 0.2 + time_decay * 0.1
            scored_memories.append((memory, final_score))
        
        scored_memories.sort(key=lambda x: x[1], reverse=True)
        
        # 4. 상위 기억 반환
        context_parts = ["## 관련 기억\n"]
        for memory, score in scored_memories[:limit]:
            memory.touch()  # 접근 카운트 증가
            date_str = memory.timestamp.strftime("%Y년 %m월 %d일")
            context_parts.append(f"[{date_str}] {memory.content[:200]}...")
        
        return "\n".join(context_parts)
    
    def _cosine_similarity(self, a: List[float], b: List[float]) -> float:
        """코사인 유사도 계산"""
        dot_product = sum(x * y for x, y in zip(a, b))
        norm_a = sum(x * x for x in a) ** 0.5
        norm_b = sum(x * x for x in b) ** 0.5
        return dot_product / (norm_a * norm_b) if norm_a and norm_b else 0.0
    
    async def persist_to_database(self, memory: Memory):
        """기억을 영구 저장소(Pinecone/PostgreSQL)에 저장"""
        # 실제 구현: Pinecone upsert 또는 PostgreSQL INSERT
        pass

===== 실제 사용 예시 =====

async def main(): # HolySheep AI 클라이언트 초기화 client = HolySheepAIClient(api_key="YOUR_HOLYSHEEP_API_KEY") # 캐릭터 카드 로드 character = CharacterCard( id="assistant_luna", name="루나", description="친근하고 다정한 AI 동반자", personality=[PersonalityTrait.WARM, PersonalityTrait.EXTROVERT], speaking_style="~다 endings, 밝고 친근한 톤", primary_emotion="happy" ) # 기억 시스템 초기화 memory_system = CompanionMemorySystem(client, user_id="user_12345") # 대화 시뮬레이션 messages = [ {"role": "user", "content": "오늘 기분이 좋아서 카페에 다녀왔어!"}, ] # 감정 분석 emotion = await client.analyze_emotion("오늘 기분이 좋아서 카페에 다녀왔어!") print(f"감정 분석 결과: {emotion}") # 관련 기억 검색 memory_context = await memory_system.retrieve_relevant_memories( "오늘 기분이 좋아서 카페에 다녀왔어!", limit=3 ) # AI 응답 생성 response = await client.chat_completion( messages=messages, character=