LEVEL 55 QUEST
테넌트별 설정 & 커스터마이징
JSON 기반 테넌트 설정, AI 에이전트 커스터마이징, 브랜딩 설정을 구현합니다.
tenant_settings 테이블 (JSON 기반)
왜 JSON 기반 설정인가?
테넌트마다 필요한 설정 항목이 다릅니다. 컬럼을 계속 추가하면 스키마가 비대해지고 마이그레이션이 복잡해집니다.
JSON 컬럼을 사용하면 스키마 변경 없이 유연하게 설정을 추가할 수 있습니다.
MariaDB 10.2 이상에서 JSON 타입과 JSON 함수를 완벽 지원합니다.
-- 테넌트 설정 테이블 CREATE TABLE tenant_settings ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, tenant_id INT UNSIGNED NOT NULL UNIQUE, settings JSON NOT NULL, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, updated_by INT UNSIGNED DEFAULT NULL, FOREIGN KEY (tenant_id) REFERENCES tenants(id) ); -- 기본 설정 JSON 구조 예시 INSERT INTO tenant_settings (tenant_id, settings) VALUES (1, '{ "branding": { "logo_url": null, "primary_color": "#8b5cf6", "secondary_color": "#1e1b4b", "company_name_display": "My SaaS Company", "favicon_url": null }, "ai_agent": { "personality": "professional", "tone": "formal", "language": "ko", "custom_greeting": "안녕하세요! 무엇을 도와드릴까요?", "max_response_length": 500, "allowed_topics": ["customer_support", "product_info"], "blocked_topics": ["competitor_info", "pricing_negotiation"] }, "notifications": { "email_alerts": true, "slack_webhook": null, "daily_report": false, "usage_warning_threshold": 80 }, "custom_domain": { "domain": null, "ssl_status": "pending", "verified": false } }');
// TenantSettings.php - 테넌트 설정 관리 클래스 class TenantSettings { private PDO $pdo; private array $settings = []; private bool $loaded = false; // 기본 설정값 (테넌트 설정이 없을 때 사용) private const DEFAULTS = [ 'branding.primary_color' => '#8b5cf6', 'branding.secondary_color' => '#1e1b4b', 'ai_agent.personality' => 'professional', 'ai_agent.tone' => 'formal', 'ai_agent.language' => 'ko', 'ai_agent.max_response_length' => 500, 'notifications.email_alerts' => true, 'notifications.usage_warning_threshold' => 80, ]; public function __construct(PDO $pdo) { $this->pdo = $pdo; } /** * 설정 로드 (Lazy Loading) */ private function load(): void { if ($this->loaded) return; $tenantId = TenantManager::id(); $stmt = $this->pdo->prepare(" SELECT settings FROM tenant_settings WHERE tenant_id = :tid "); $stmt->execute([':tid' => $tenantId]); $json = $stmt->fetchColumn(); $this->settings = $json ? json_decode($json, true) : []; $this->loaded = true; } /** * 설정값 조회 (dot notation 지원) * 예: $settings->get('branding.primary_color') */ public function get(string $key, mixed $default = null): mixed { $this->load(); $keys = explode('.', $key); $value = $this->settings; foreach ($keys as $k) { if (!is_array($value) || !isset($value[$k])) { return $default ?? (self::DEFAULTS[$key] ?? null); } $value = $value[$k]; } return $value; } /** * 설정값 변경 (dot notation) * 예: $settings->set('branding.primary_color', '#ff0000') */ public function set(string $key, mixed $value): void { $this->load(); $keys = explode('.', $key); $ref = &$this->settings; foreach ($keys as $i => $k) { if ($i === count($keys) - 1) { $ref[$k] = $value; } else { if (!isset($ref[$k]) || !is_array($ref[$k])) { $ref[$k] = []; } $ref = &$ref[$k]; } } } /** * 설정 저장 (DB 반영) */ public function save(): void { $tenantId = TenantManager::id(); $json = json_encode($this->settings, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); $stmt = $this->pdo->prepare(" INSERT INTO tenant_settings (tenant_id, settings, updated_by) VALUES (:tid, :settings, :uid) ON DUPLICATE KEY UPDATE settings = :settings2, updated_by = :uid2 "); $stmt->execute([ ':tid' => $tenantId, ':settings' => $json, ':uid' => $_SESSION['user_id'] ?? null, ':settings2' => $json, ':uid2' => $_SESSION['user_id'] ?? null, ]); } }
AI Agent 성격/톤 커스터마이징
테넌트마다 다른 AI 성격
SaaS의 핵심 가치 중 하나는 각 고객사에 맞는 AI 에이전트를 제공하는 것입니다.
법률 회사는 격식 있는 톤을, 스타트업은 친근한 톤을 원합니다.
이를 테넌트 설정으로 제어합니다.
// AIAgentConfig.php - 테넌트별 AI 에이전트 설정 class AIAgentConfig { private TenantSettings $settings; // 미리 정의된 성격 프리셋 private const PERSONALITY_PRESETS = [ 'professional' => [ 'system_prompt' => '당신은 전문적이고 신뢰감 있는 비즈니스 어시스턴트입니다. 정확하고 간결한 답변을 제공합니다.', 'temperature' => 0.3, ], 'friendly' => [ 'system_prompt' => '당신은 친근하고 따뜻한 고객 서비스 담당자입니다. 공감하며 대화합니다.', 'temperature' => 0.7, ], 'technical' => [ 'system_prompt' => '당신은 기술 전문가입니다. 코드와 기술 용어를 적극적으로 활용하여 설명합니다.', 'temperature' => 0.2, ], ]; public function __construct(TenantSettings $settings) { $this->settings = $settings; } /** * AI API 호출에 사용할 시스템 프롬프트 생성 */ public function buildSystemPrompt(): string { $personality = $this->settings->get('ai_agent.personality', 'professional'); $preset = self::PERSONALITY_PRESETS[$personality] ?? self::PERSONALITY_PRESETS['professional']; $tone = $this->settings->get('ai_agent.tone', 'formal'); $lang = $this->settings->get('ai_agent.language', 'ko'); $greeting = $this->settings->get('ai_agent.custom_greeting'); $maxLen = $this->settings->get('ai_agent.max_response_length', 500); $blocked = $this->settings->get('ai_agent.blocked_topics', []); // 커스텀 프롬프트가 있으면 우선 적용 $customPrompt = $this->settings->get('ai_agent.custom_system_prompt'); $basePrompt = $customPrompt ?: $preset['system_prompt']; $prompt = $basePrompt . "\n\n"; $prompt .= "톤: {$tone}\n"; $prompt .= "응답 언어: {$lang}\n"; $prompt .= "최대 응답 길이: {$maxLen}자\n"; if ($greeting) { $prompt .= "첫 인사: {$greeting}\n"; } if (!empty($blocked)) { $blockedStr = implode(', ', $blocked); $prompt .= "금지 주제: {$blockedStr} (이 주제에 대해서는 정중히 거절)\n"; } return $prompt; } /** * AI API 호출 파라미터 반환 */ public function getApiParams(): array { $personality = $this->settings->get('ai_agent.personality', 'professional'); $preset = self::PERSONALITY_PRESETS[$personality] ?? self::PERSONALITY_PRESETS['professional']; return [ 'temperature' => $preset['temperature'], 'max_tokens' => $this->settings->get('ai_agent.max_response_length', 500), ]; } }
system_prompt
AI 모델에게 역할과 행동 규칙을 지정하는 초기 지시문. 테넌트별로 다른 프롬프트를 사용하여 맞춤형 AI를 제공합니다.
temperature
AI 응답의 창의성/무작위성 조절 값 (0~1). 낮을수록 일관되고, 높을수록 다양한 답변을 생성합니다.
🔒
여기까지는 미리보기입니다
테넌트별 설정 & 커스터마이징
무료 가입하면 이어서 볼 수 있고, 강의를 완료할 때마다 XP와 레벨이 쌓입니다.
Google로 3초 만에 시작 →🧵 Threads로 시작무료 공개 강의 둘러보기 (Lv.1~3)무료 가입하면 이어서 볼 수 있고, 강의를 완료할 때마다 XP와 레벨이 쌓입니다.