Files
lcbp3/specs/06-Decision-Records/ADR-027-ai-admin-console-and-dynamic-control.md

18 KiB

ADR-027: AI Admin Panel and Dynamic Control Architecture

Status: Accepted Date: 2026-05-20 Decision Makers: Development Team, System Architect, DevOps Engineer Related Documents:

หมายเหตุ: ADR นี้กำหนดสถาปัตยกรรมการพัฒนาแผงควบคุมระบบ AI (AI Admin Panel) สำหรับสิทธิ์ Superadmin เท่านั้น เพื่อใช้ในการควบคุมความพร้อมใช้งานของบริการ AI แบบ Dynamic, ตรวจสอบสุขภาพระบบโครงสร้างพื้นฐาน (Ollama/Qdrant/BullMQ) และการรัน Sandbox ทดสอบภายใต้สภาพแวดล้อมที่ควบคุมความปลอดภัยสูงสุด


บริบทและปัญหา (Context and Problem Statement)

เนื่องจากระบบปัญญาประดิษฐ์ของโครงการ LCBP3 DMS (Ollama & Qdrant) รันอยู่บนสภาพแวดล้อมแบบ On-premises บนเครื่อง AI Host (Desk-5439) ซึ่งมีความเสี่ยงที่จะเกิดเหตุสุดวิสัย เช่น เครื่องล่ม, Latency สูงขึ้นอย่างผิดปกติจากการประมวลผลงานชุดใหญ่ หรือมีความจำเป็นต้องปิดปรับปรุง Prompt หรือตัวโมเดลชั่วคราว

ปัญหากลุ่มนี้ทำให้ระบบต้องการกลไกควบคุมและติดตามดังนี้:

  1. Dynamic Switch: แอดมินจำเป็นต้องสั่งปิดการให้บริการ AI แก่ผู้ใช้ปกติได้ทันทีโดยไม่ต้องรัน Build หรือ Restart เซิร์ฟเวอร์
  2. Graceful Degradation: เมื่อปิดระบบ AI, หน้าจอของผู้ใช้ปกติและ API จะต้องปิดตัวลงอย่างสง่างาม ไม่โยนข้อผิดพลาดแปลกๆ ที่ไม่เป็นมิตรต่อผู้ใช้
  3. Isolated Test Laboratory: ในขณะที่ AI ถูกปิดปรับปรุง แอดมินยังคงต้องการพื้นที่ Sandbox ในการทดสอบประมวลผลจริงเพื่อปรับปรุงความถูกต้อง โดยงานประมวลผลของแอดมินจะต้องไม่ถูกรบกวนจากงานตกค้างของผู้ใช้ทั่วไป หรือทำตัวโมเดลล่ม

ปัจจัยขับเคลื่อนการตัดสินใจ (Decision Drivers)

  • Security Isolation (Tier 1): แผงควบคุมและ Sandbox ทั้งหมดต้องควบคุมสิทธิ์อย่างเหนียวแน่นสำหรับสิทธิ์ Superadmin เท่านั้น (system.manage_all)
  • Latency-free Status Check: การตรวจสอบสวิตช์เปิด/ปิด AI ใน API ผู้ใช้ภายนอกต้องไม่มี Overhead ในการคิวรีฐานข้อมูลตลอดเวลา
  • User Experience (UX): หน้าจอผู้ใช้ปกติในฟอร์มเอกสารต้องตอบสนองได้อย่างนุ่มนวล (Soft Fallback) เมื่อ AI ถูกปิด แทนการกดปุ่มแล้วแจ้งเตือนข้อผิดพลาดสีแดง
  • Resource Protection: การรัน Playground Sandbox ของแอดมินจะต้องไม่ก่อให้เกิด Race Condition หรือโหลดกระแทกบน VRAM ของ GPU RTX 2060 Super (8GB) บนเครื่อง Desk-5439

ทางเลือกที่ถูกพิจารณา (Considered Options)

Option A: Synchronous Direct Sandbox & API Hard Block

  • สั่งรัน RAG และ OCR Sandbox ของแอดมินตรงเข้าสู่ API Controller แบบ Synchronous โดยตรง (ไม่ผ่านคิว BullMQ) และเมื่อสวิตช์เปิด/ปิด AI ถูกตั้งค่าเป็นปิดใช้งาน จะทำการซ่อนปุ่มสกัดข้อมูลทั้งหมดในหน้าผู้ใช้ทั่วไปทันที

Option B: Shared BullMQ Queue & Soft Fallback (ตัวเลือกที่ได้รับเลือก)

  • สั่งรัน Sandbox ของแอดมินผ่านคิว ai-batch ที่มีอยู่แล้ว (ตาม ADR-023A) โดยใช้ job type sandbox-rag และ sandbox-extract พร้อม priority สูงกว่างาน batch ปกติ
  • จัดทำตาราง system_settings โดยเพิ่มลงใน schema file หลัก (ตาม ADR-009) ร่วมกับ Redis Cache และใช้กลไก Polling (ทุก 30 วินาที) ของ Frontend เพื่ออัปเดตสถานะปุ่ม AI Suggestion บนฟอร์มเป็นสถานะ Disabled (ใช้งานไม่ได้) พร้อมแสดงข้อความอธิบายความจำเป็นเมื่อชี้เมาส์ (Hover Tooltip)

ผลการตัดสินใจ (Decision Outcome)

ทางเลือกที่ได้รับเลือก: Option B เนื่องจากเหตุผลความเสถียรของระบบ VRAM และประสบการณ์การใช้งานที่ดียิ่งขึ้นของผู้ใช้งานทั่วไป (UX) โดยมีตารางวิเคราะห์เปรียบเทียบดังนี้:

เกณฑ์การประเมิน Option A (Direct) Option B (Shared Queue)
ความเสถียรของ VRAM บน Desk-5439 เสี่ยงล่มหากแอดมินรันโหลดหนักชนกับ Queue ปกติ ปลอดภัยสูงสุด ควบคุม Concurrency ของ ai-batch queue ตาม ADR-023A (concurrency=1)
ประสบการณ์การใช้งานทั่วไป (UX) ปุ่มหายกะทันหัน สร้างความสับสนว่าฟีเจอร์หายไปไหน แสดงปุ่ม disabled + Tooltip ชี้แจง ทำให้เกิดความเข้าใจและเป็นมิตร
การจำลองโหลดการทำงานจริง ไม่มีการเข้าคิว ไม่สะท้อนความเร็วจริงในสถานการณ์จริง สะท้อนพฤติกรรมความเร็วจริงของคิวและ VRAM ได้แม่นยำ 100%
ประสิทธิภาพของ Backend API เช็ค DB ทุกครั้งสร้าง Overhead เช็คผ่าน Redis Cache คืนสถานะภายใน <1ms
ความสอดคล้องกับ ADR-023A ไม่สอดคล้องกับ 2-Queue Architecture สอดคล้องกับ ADR-023A (ใช้ ai-batch queue ร่วมกัน)

รายละเอียดเชิงสถาปัตยกรรม (Implementation Details)

1. โครงสร้างข้อมูลตาราง system_settings (Refined)

ระบบจะนำเสนอตารางเก็บข้อมูลการตั้งค่าระบบแบบรวมศูนย์ (generic) เพื่อรองรับ settings อื่นๆ ในอนาคต (ตามมาตรฐาน ADR-009) ดังนี้:

  • Persistence Layer: เพิ่มตาราง system_settings ใน specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql โดยตรง (ไม่ใช้ migration file แยก)
  • Caching Layer: จัดเก็บค่าแยกเป็น Redis Key ต่อ setting (เช่น system_settings:AI_FEATURES_ENABLED, system_settings:MAX_UPLOAD_SIZE) เพื่อให้อ่านค่าได้เร็วในระดับไมโครวินาที (Microseconds) เมื่อ API Guard เรียกตรวจสอบ
[Client App] ---> [API Guard] ---> [Redis Cache (Key: system_settings:AI_FEATURES_ENABLED)]
                        |
                        +--(Miss)--> [MariaDB (system_settings)]

Schema Design (Generic):

CREATE TABLE system_settings (
  id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง',
  setting_key VARCHAR(100) NOT NULL UNIQUE COMMENT 'คีย์การตั้งค่าระบบ (เช่น AI_FEATURES_ENABLED, MAX_UPLOAD_SIZE)',
  setting_value TEXT NOT NULL COMMENT 'ค่าที่บันทึก (stringified)',
  data_type ENUM('string', 'number', 'boolean', 'json') NOT NULL DEFAULT 'string' COMMENT 'ประเภทข้อมูลสำหรับ validation',
  category VARCHAR(50) COMMENT 'หมวดหมู่ (เช่น ai, security, storage, notification)',
  is_encrypted TINYINT(1) DEFAULT 0 COMMENT 'เข้ารหัสค่า sensitive (เช่น API keys)',
  validation_rules JSON COMMENT 'กฎ validation (min, max, allowed_values)',
  description TEXT COMMENT 'คำอธิบายข้อมูลการตั้งค่า',
  is_public TINYINT(1) DEFAULT 0 COMMENT 'เผยแพร่ให้ frontend อ่านได้ (หรือ admin only)',
  updated_by INT COMMENT 'ผู้แก้ไขล่าสุด',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  FOREIGN KEY (updated_by) REFERENCES users(user_id) ON DELETE SET NULL,
  INDEX idx_category (category),
  INDEX idx_is_public (is_public)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเก็บข้อมูลการตั้งค่าระบบไดนามิก';

2. ระบบคิว Sandbox ร่วมกัน (Shared Queue)

ระบบจะใช้คิว ai-batch ที่มีอยู่แล้ว (ตาม ADR-023A) สำหรับงาน Sandbox ของแอดมิน โดย:

  • เพิ่ม job type sandbox-rag สำหรับคำถาม RAG ใน Playground
  • เพิ่ม job type sandbox-extract สำหรับ OCR/Extraction ใน Sandbox
  • ใช้ priority SUPERADMIN (ระดับใหม่ higher than HIGH) สำหรับงาน Sandbox เพื่อให้ได้รับการประมวลผลก่อนงาน batch ปกติโดยไม่ jump queue
  • Processor ใน ai-batch.processor.ts จะจัดการ job types เหล่านี้เพิ่มเติม
  • Concurrency คงที่ที่ 1 ตาม ADR-023A เพื่อป้องกัน VRAM overload
  • Dynamic Rate Limiting: ตรวจสอบความยาวคิว ai-batch ก่อน allow request (queue length < 3 → no limit, queue length ≥ 3 → 10 requests/hour)

3. มาตรการควบคุมสิทธิ์ (Security Controls)

  • การสลับสวิตช์ AI และการยิง Sandbox Endpoints ทั้งหมดจะถูกปิดกั้นอย่างเข้มงวดด้วยการเช็ค JWT Token และการใช้ @RequirePermission('system.manage_all') (CASL Guard)
  • AiEnabledGuard Layered Check: Superadmin ต้องมีทั้ง system.manage_all และ ai.suggest/ai.rag_query เพื่อ bypass เมื่อ AI disabled
  • Admin Endpoints: ไม่ใช้ AiEnabledGuard (ใช้ permission guard system.manage_all เพียงพอ)
  • Job Polling: ไม่ block job status requests (audit trail ไม่ใช่ AI inference)
  • ห้ามระบุ ID หลักเป็น Integer PK ในการทำงาน (เช่น การทดสอบ RAG หรือ Sandbox ประมวลผล) แต่จะใช้ UUIDv7 publicId ในการระบุโครงการและจัดกลุ่มเสมอตามข้อตกลง ADR-019 (Hybrid Identifier Strategy)

Grilling Session Decisions (2026-05-21)

การตัดสินใจต่อไปนี้ได้รับการ refine ผ่าน grilling session เพื่อความชัดเจนและความพร้อมในการ implement:

# ประเด็น การตัดสินใจ
1 Infrastructure Dependency ADR-023A infrastructure มีอยู่แล้ว (ai-realtime, ai-batch, permissions)
2 system_settings Schema Generic พร้อม data_type, category, is_encrypted, validation_rules, is_public
3 Redis Cache Strategy Cache แยก key ต่อ setting (เช่น system_settings:AI_FEATURES_ENABLED)
4 Security Controls Dynamic rate limiting ขึ้นกับ queue length (queue < 3 → no limit, queue ≥ 3 → 10 req/hr)
5 Frontend Polling Poll เฉพาะ users ที่มี AI permissions (ทุก 30 วินาที)
6 AiEnabledGuard Layered check (system.manage_all + ai.suggest/ai.rag_query)
7 Error Handling HTTP 503 + rate-limited warn logs (10 req/user/min) + custom banner debounce 5s
8 Cache Invalidation Invalid หลัง DB success (TypeORM transaction) + single key + ยอมรับ 30s latency
9 Sandbox Priority Priority ระดับใหม่ SUPERADMIN (higher than HIGH)
10 Health Check 5s timeout per service + 30s cache + basic queue metrics (waiting, active, failed, rate)
11 UI/UX Single page layout + 5s job polling + inline error (red box) + toast
12 Implementation Priority Phased (backend → frontend)

Refined Implementation Details

4. AiEnabledGuard Implementation

Logic:

const aiEnabled = await this.getAiFeaturesEnabled(); // from Redis/DB
const isSuperadmin = user.permissions.includes('system.manage_all');
const hasAiPermission = user.permissions.includes('ai.suggest') || user.permissions.includes('ai.rag_query');

if (!aiEnabled && !(isSuperadmin && hasAiPermission)) {
  throw new ServiceUnavailableException({
    message: 'AI features are temporarily unavailable',
    userMessage: 'ระบบ AI ไม่พร้อมใช้งานชั่วคราว กรุณากรอกข้อมูลด้วยตนเอง',
    recoveryAction: 'ติดต่อผู้ดูแลระบบหากต้องการความช่วยเหลือ'
  });
}

Error Response (ADR-007):

  • HTTP Status: 503 Service Unavailable
  • Logging: warn level แต่ rate limit (log ทุก 10 ครั้งต่อ user ต่อนาที)
  • Frontend: Custom Global Banner + debounce 5 วินาที

5. Cache Invalidation Strategy

Timing: Invalid Redis cache หลัง DB update success (TypeORM transaction) Scope: Invalid เฉพาะ key system_settings:AI_FEATURES_ENABLED (efficient) Frontend Sync: ยอมรับ latency 30 วินาที (polling strategy เพียงพอสำหรับ use case นี้)

6. Health Check Service

Timeout: 5 วินาที per service → timeout return DEGRADED (not DOWN) Frequency: Cache 30 วินาที (synchronized กับ AI status polling) Queue Metrics: Basic metrics (waiting, active, failed) + processing rate (jobs/second) Services: Ollama (Desk-5439), Qdrant (Desk-5439), BullMQ (ai-realtime, ai-batch)

7. Frontend Polling Strategy

Condition: Poll เฉพาะ users ที่มี ai.suggest หรือ ai.rag_query permission Frequency: ทุก 30 วินาที Cache: React Context + refresh on mount Implementation: useAiStatus() hook ใน SessionProvider

8. Admin Console UI/UX

Layout: Single page พร้อม tabs (RAG Playground / OCR Sandbox) Job Polling: 5 วินาที (reasonable balance ระหว่าง real-time และ performance) Error Display: Inline error ใน output area (red box) + toast notification Style: Glassmorphism + Health Indicators + Header Switch