데브포일 홈
DEV MODE - 실서버 영향 없음 📦 배포 관리
대화 히스토리 & 컨텍스트 관리
+250 XP
LEVEL 52 QUEST

대화 히스토리 & 컨텍스트 관리

대화 저장, 토큰 관리, 장기 메모리, 멀티테넌트 분리

대화 저장 DB 설계
conversations & messages 테이블
AI 대화를 체계적으로 저장하려면 대화방(conversation)과 메시지(message)를 분리하여 설계합니다. SaaS에서는 테넌트(고객사) 분리도 필수입니다.
-- 대화방 테이블 CREATE TABLE conversations ( id BIGINT AUTO_INCREMENT PRIMARY KEY, tenant_id INT NOT NULL, -- SaaS 고객사 ID user_id INT NOT NULL, -- 최종 사용자 ID title VARCHAR(200), -- 대화 제목 (첫 질문 기반 자동 생성) summary TEXT, -- 대화 요약 (장기 메모리용) total_tokens INT DEFAULT 0, -- 누적 토큰 사용량 status ENUM('active','archived','deleted') DEFAULT 'active', metadata JSON, -- 추가 정보 (agent 타입, 모델 등) created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_tenant_user (tenant_id, user_id), INDEX idx_updated (updated_at) ); -- 메시지 테이블 CREATE TABLE messages ( id BIGINT AUTO_INCREMENT PRIMARY KEY, conversation_id BIGINT NOT NULL, role ENUM('user','assistant','system','tool') NOT NULL, content TEXT NOT NULL, token_count INT DEFAULT 0, tool_name VARCHAR(100), -- 도구 호출 시 도구 이름 tool_args JSON, -- 도구 호출 파라미터 cost_usd DECIMAL(10,8) DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE, INDEX idx_conv_created (conversation_id, created_at) );
컨텍스트 윈도우 관리
토큰 수 제한의 중요성
AI API는 최대 컨텍스트 크기가 정해져 있습니다. 대화가 길어지면 오래된 메시지를 잘라내야 합니다. 단순히 자르는 것이 아니라 전략적으로 관리해야 합니다.
전략 방법 장점 단점
슬라이딩 윈도우 최근 N개 메시지만 유지 구현 쉬움 오래된 맥락 유실
토큰 카운트 총 토큰 수 기준으로 잘라냄 정확한 비용 관리 구현 복잡
요약 + 최근 오래된 대화 요약 + 최근 메시지 맥락 보존 + 비용 절감 요약에 추가 API 비용
/** * 대화 컨텍스트 매니저 * 토큰 한도 내에서 최적의 메시지 구성 */ class ConversationManager { private PDO $db; private int $maxContextTokens; public function __construct(PDO $db, int $maxContextTokens = 4000) { $this->db = $db; $this->maxContextTokens = $maxContextTokens; } /** 새 대화 시작 */ public function createConversation(int $tenantId, int $userId): int { $stmt = $this->db->prepare(" INSERT INTO conversations (tenant_id, user_id) VALUES (?, ?) "); $stmt->execute([$tenantId, $userId]); return (int)$this->db->lastInsertId(); } /** 메시지 저장 */ public function addMessage( int $convId, string $role, string $content, float $cost = 0 ): void { $tokens = $this->estimateTokens($content); $stmt = $this->db->prepare(" INSERT INTO messages (conversation_id, role, content, token_count, cost_usd) VALUES (?, ?, ?, ?, ?) "); $stmt->execute([$convId, $role, $content, $tokens, $cost]); // 대화방 토큰 합계 업데이트 $stmt = $this->db->prepare(" UPDATE conversations SET total_tokens = total_tokens + ? WHERE id = ? "); $stmt->execute([$tokens, $convId]); // 첫 메시지면 제목 자동 생성 if ($role === 'user') { $this->autoSetTitle($convId, $content); } } /** * 컨텍스트 윈도우에 맞는 메시지 목록 반환 * 전략: 요약(있으면) + 최근 메시지 (토큰 한도 내) */ public function getContextMessages(int $convId): array { $messages = []; $usedTokens = 0; // 1. 대화 요약이 있으면 먼저 포함 $stmt = $this->db->prepare("SELECT summary FROM conversations WHERE id = ?"); $stmt->execute([$convId]); $summary = $stmt->fetchColumn(); if ($summary) { $summaryTokens = $this->estimateTokens($summary); $messages[] = [ 'role' => 'system', 'content' => "[이전 대화 요약]\n{$summary}" ]; $usedTokens += $summaryTokens; } // 2. 최근 메시지를 역순으로 가져와서 토큰 한도 체크 $stmt = $this->db->prepare(" SELECT role, content, token_count FROM messages WHERE conversation_id = ? ORDER BY created_at DESC "); $stmt->execute([$convId]); $recentMessages = []; while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { if ($usedTokens + $row['token_count'] > $this->maxContextTokens) { break; // 토큰 한도 초과 → 여기서 중단 } $recentMessages[] = [ 'role' => $row['role'], 'content' => $row['content'], ]; $usedTokens += $row['token_count']; } // 역순으로 가져왔으니 다시 시간순으로 정렬 $messages = array_merge($messages, array_reverse($recentMessages)); return $messages; } /** 토큰 수 추정 (한국어: 문자 수 / 1.5 근사) */ private function estimateTokens(string $text): int { // 한국어는 글자당 약 1.5~2 토큰 // 영어는 단어당 약 1.3 토큰 $koreanChars = preg_match_all('/[\x{AC00}-\x{D7A3}]/u', $text); $totalChars = mb_strlen($text); $englishChars = $totalChars - $koreanChars; return (int)($koreanChars * 1.7 + $englishChars * 0.4); } /** 첫 메시지 기반 제목 자동 생성 */ private function autoSetTitle(int $convId, string $firstMessage): void { $stmt = $this->db->prepare(" SELECT title FROM conversations WHERE id = ? "); $stmt->execute([$convId]); if (empty($stmt->fetchColumn())) { $title = mb_substr($firstMessage, 0, 50); $stmt = $this->db->prepare("UPDATE conversations SET title = ? WHERE id = ?"); $stmt->execute([$title, $convId]); } } }
🔒
여기까지는 미리보기입니다
대화 히스토리 & 컨텍스트 관리
무료 가입하면 이어서 볼 수 있고, 강의를 완료할 때마다 XP와 레벨이 쌓입니다.
Google로 3초 만에 시작 →🧵 Threads로 시작무료 공개 강의 둘러보기 (Lv.1~3)