Files
lcbp3/specs/06-Decision-Records/ADR-023A-unified-ai-architecture.md
T
admin 6cb3ae10ee
CI / CD Pipeline / build (push) Failing after 5m36s
CI / CD Pipeline / deploy (push) Has been skipped
feat(ai): unify AI architecture, implement RAG and legacy migration
2026-05-15 11:10:44 +07:00

494 lines
41 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-023A: Unified AI Architecture — Model Revision (gemma4:e4b Q8_0, 2-Model Stack)
**Status:** Accepted
**Date:** 2026-05-15
**Decision Makers:** Development Team, System Architect, Security Team, AI Integration Lead
**Supersedes Revision:** ADR-023 v1.1 (2026-05-14)
**Related Documents:**
- [Glossary](../00-Overview/00-02-glossary.md)
- [Data Dictionary](../03-Data-and-Storage/03-01-data-dictionary.md)
- [Legacy Data Migration Plan](../03-Data-and-Storage/03-04-legacy-data-migration.md)
- [n8n Migration Setup Guide](../03-Data-and-Storage/03-05-n8n-migration-setup-guide.md)
- [ADR-016: Security & Authentication](./ADR-016-security-authentication.md)
- [ADR-019: Hybrid Identifier Strategy](./ADR-019-hybrid-identifier-strategy.md)
- [RAG Implementation Guide v1.1.2](../08-Tasks/ADR-022-Retrieval-Augmented-Generation/LCBP3-RAG-Implementation-Guide-v1.1.2.md)
- [ADR-023: Unified AI Architecture (Base)](./ADR-023-unified-ai-architecture.md)
> **หมายเหตุ:** ADR-023A เป็นสำเนาอัปเดตของ ADR-023 v1.1 โดยปรับปรุงชุดโมเดล AI เพื่อให้ใช้งาน VRAM ≤ 8GB ได้อย่างมีเสถียรภาพ ลดจาก 3 โมเดล (gemma4:9b + Typhoon Local + nomic-embed-text) เหลือ 2 โมเดล (gemma4:e4b Q8_0 + nomic-embed-text) โดย gemma4:e4b ทำหน้าที่ครอบคลุมทั้ง General Inference และ OCR Post-processing/Extraction แทน Typhoon Local
---
## 🎯 Gap Analysis & Purpose
### ปิด Gap จากเอกสาร:
- **Product Vision v1.8.5** - Section 3.1, 3.2, 3.3: ความต้องการยกระดับประสิทธิภาพการนำเข้าเอกสารเก่าและการจัดการเอกสารใหม่ด้วย AI Intelligence
- เหตุผล: การทำงานด้วยมือ (Manual) มีต้นทุนเวลาสูงและเกิดความผิดพลาดได้ง่าย จำเป็นต้องมี AI ช่วยตรวจสอบและจัดหมวดหมู่โดยอัตโนมัติ
- **Security Requirements & Risk Assessment** - Section 3.1, 4.2: ความเสี่ยงด้านข้อมูลรั่วไหลและการเข้าถึงฐานข้อมูลโดยตรงจากเซอร์วิส AI
- เหตุผล: ข้อมูลเอกสารก่อสร้างท่าเรือแหลมฉบัง เฟส 3 เป็นความลับระดับสูง (Confidential) ต้องมีขอบเขตความปลอดภัย (AI Boundary) ที่รัดกุม
### แก้ไขความขัดแย้งและการกระจายตัวของเอกสาร:
- **ADR-017, ADR-017B, ADR-018, ADR-020, ADR-022** มีความทับซ้อนในเชิงสถาปัตยกรรมและข้อกำหนด
- การตัดสินใจนี้ช่วยแก้ไขโดย: ยุบรวมข้อกำหนดทั้งหมดเข้าสู่ร่มใหญ่ฉบับเดียว (Consolidation) เพื่อลดปัญหา Revision Drift และทำให้การทบทวนสถาปัตยกรรม (Review Cycle) เป็นไปอย่างสอดคล้องกันทั้งระบบ
---
## Context and Problem Statement
โครงการ LCBP3-DMS มีความต้องการประยุกต์ใช้ AI ในการเพิ่มประสิทธิภาพการบริหารจัดการเอกสารวิศวกรรมโยธาขนาดใหญ่ โดยเผชิญกับโจทย์และความท้าทายหลัก 5 ด้าน:
1. **Legacy Document Migration:** เอกสาร PDF เก่ากว่า 20,000 ฉบับ ต้องนำเข้าระบบพร้อมตรวจสอบความสอดคล้องกับ Metadata ใน Excel
2. **Real-time Ingestion & Classification:** เอกสารใหม่ที่ผู้ใช้อัปโหลดต้องการการสกัด Metadata และจัดหมวดหมู่แบบเรียลไทม์เพื่อลดภาระงานกรอกข้อมูล
3. **Conversational Retrieval (RAG):** Full-text search บน MariaDB ไม่เข้าใจบริบท (Semantic) และการตัดคำภาษาไทย ทำให้สืบค้นข้อมูลเชิงลึกได้ยาก
4. **Data Confidentiality & Privacy:** ห้ามส่งข้อมูลความลับออกนอกเครือข่ายองค์กรไปยัง Cloud AI Provider (เช่น OpenAI, Google)
5. **System Stability & Isolation:** การรัน AI Inference ใช้ทรัพยากรสูง (GPU VRAM/CPU) ไม่ควรรันร่วมกับ Production Server หลัก (QNAP NAS) เพื่อไม่ให้กระทบประสิทธิภาพของระบบ
---
## Decision Drivers
- **Zero Trust & Physical Isolation:** AI ต้องถูกปฏิบัติเสมือน Untrusted Component รันแยกต่างหากบน Admin Desktop เท่านั้น
- **RFA-First Approach:** มุ่งเน้นกระบวนการเอกสาร RFA (Request for Approval) ซึ่งซับซ้อนที่สุดเป็นแกนหลัก
- **Data Integrity & Human-in-the-Loop:** ข้อมูลจาก AI ต้องผ่านการทวนสอบและยืนยันโดยมนุษย์ก่อน Commit ลงฐานข้อมูลจริงเสมอ
- **Multi-tenant Isolation:** ต้องแยกขอบเขตข้อมูลของแต่ละโครงการอย่างเด็ดขาดในระดับ Vector Database Payload Filter
- **Cost Effectiveness:** ประมวลผลภายในองค์กร (On-Premises) เพื่อหลีกเลี่ยงค่าใช้จ่ายแบบ Pay-per-use
- **Two-Phase Storage Governance:** ควบคุมการย้ายไฟล์ทุกขั้นตอนผ่าน `StorageService` เพื่อให้สแกนไวรัสและเก็บ Audit Log ได้ครบถ้วน
- **GPU VRAM Budget ≤ 8GB:** โมเดลทั้งหมดต้องโหลดพร้อมกันภายใน RTX 2060 Super 8GB ได้โดยไม่เกิด OOM (Out-of-Memory)
---
## Considered Options
### Option 1: Fragmented AI Subsystems (แยกระบบ AI ตาม Use Case)
**Pros:**
- ออกแบบและพัฒนาง่ายในระยะสั้น แต่ละส่วนไม่พึ่งพากัน
**Cons:**
- ❌ เกิด Code Duplication สูง
- ❌ มาตรฐานความปลอดภัยและการควบคุมสิทธิ์ไม่สม่ำเสมอ
- ❌ บำรุงรักษายากเมื่อมีการเปลี่ยนโมเดลหรือโครงสร้าง Prompt
### Option 2: Cloud AI Platform Integration
**Pros:**
- โมเดลมีความฉลาดแม่นยำสูงมาก ไม่ต้องลงทุนและบำรุงรักษา Hardware
**Cons:**
-**ผิดข้อกำหนดด้าน Data Privacy** อย่างรุนแรงสำหรับเอกสาร Confidential
- ❌ ค่าใช้จ่ายสูงมากเมื่อต้องประมวลผลเอกสารเก่ากว่า 20,000 ฉบับและรองรับ RAG
### Option 3: 3-Model Stack (gemma4:9b + Typhoon Local + nomic-embed-text)
**เหตุผลที่ไม่เลือก:**
- ❌ Typhoon Local (~4GB VRAM) + gemma4:9b (~5.5GB VRAM) = ~9.5GB → เกิน RTX 2060 Super 8GB ทำให้เกิด GPU Swap และลดความเสถียร
- ❌ Ollama ไม่สลับโมเดลได้ฉับพลัน หากโหลดพร้อมกัน VRAM เต็มแน่นอน
- ❌ ต้องจัดการ Routing Logic (เลือกว่างานไหนใช้โมเดลไหน) เพิ่ม Complexity
### Option 4: Unified 2-Model Stack (gemma4:e4b Q8_0 + nomic-embed-text) ⭐ **SELECTED**
| โมเดล | ขนาด (VRAM โดยประมาณ) | หน้าที่ |
|-------|----------------------|---------|
| `gemma4:e4b Q8_0` | ~4.5GB | General Inference + OCR Post-processing + Extraction + RAG Q&A |
| `nomic-embed-text` | ~0.3GB | Embedding 768-dim สำหรับ Qdrant |
| **รวม** | **~4.8GB** | **เผื่อ headroom ~3.2GB สำหรับ KV Cache และ context window ขนาดใหญ่** |
**Pros:**
-**VRAM ≤ 8GB อย่างมีเสถียรภาพ:** โมเดลทั้ง 2 โหลดพร้อมกันได้ มี headroom เพียงพอสำหรับ KV Cache ขนาดใหญ่
-**Single Model ลด Routing Complexity:** gemma4:e4b ครอบคลุมทุก Use Case (OCR clean-up, Extraction, RAG, Classification) ผ่าน Prompt Engineering ที่แตกต่างกัน
-**BullMQ Sequential Queue:** การใช้โมเดลเดียวทำให้ Queue ทำงานได้ตรงไปตรงมา — ไม่มีปัญหา Worker ต้องสลับโมเดลระหว่างงาน
-**GPU Overload Prevention ตาม ADR-023:** สอดคล้องกับนโยบายที่กำหนดไว้แต่เดิม
-**gemma4:e4b Q8_0:** quantization Q8_0 รักษาความแม่นยำของ weights ใกล้เคียง FP16 มากที่สุด เหมาะกับงานที่ต้องการความละเอียดด้านภาษา
**Cons:**
- ❌ ไม่มี Typhoon Local ซึ่งถูก Fine-tune มาสำหรับภาษาไทยโดยเฉพาะ — ต้องพึ่ง Prompt Engineering บน gemma4:e4b แทน
---
## Decision Outcome
**Chosen Option:** Option 4 — Unified 2-Model Stack (gemma4:e4b Q8_0 + nomic-embed-text)
### Rationale
การลด Model Stack จาก 3 → 2 โมเดลช่วยให้ VRAM Budget ≤ 8GB อย่างมีเสถียรภาพ โดย gemma4:e4b Q8_0 สามารถทำหน้าที่แทน Typhoon Local ได้ผ่าน Prompt Engineering เนื่องจาก gemma4 architecture รองรับ Multimodal Context ได้ดี และ Q8_0 quantization รักษา quality ไว้ในระดับสูง BullMQ Sequential Queue ทำงานได้ตรงไปตรงมามากขึ้นเมื่อมีโมเดลเดียว ลด complexity ของ Job Routing
---
## 🔍 Impact Analysis
### Affected Components
| Component | Level | Impact Description | Required Action |
|-----------|-------|-------------------|-----------------|
| **Security Layer** | 🔴 High | บังคับใช้ขอบเขตการเชื่อมต่อผ่าน API Gateway เท่านั้น | เพิ่ม permissions `ai.suggest`, `ai.rag_query`, `ai.migration_manage`, `ai.audit_log_delete` และ Assign ตาม Role Matrix ด้านล่าง |
| **Backend (NestJS)** | 🔴 High | สร้าง `AiModule` เป็นศูนย์กลางควบคุม Pipeline และ RAG | พัฒนา Gateway Services และ Validation Layers |
| **Database** | 🔴 High | ตารางจัดเก็บประวัติการทวนสอบและสถานะเวกเตอร์ | สร้าง `migration_review_queue` และ `ai_audit_logs` (แยก table, ไม่ใช่ Compliance — เป็น AI Development Feedback Log) |
| **Frontend (Next.js)** | 🟡 Medium | หน้าจอแสดงผลลัพธ์จาก AI พร้อมค่า Confidence | พัฒนา Reusable Form Components และ Dashboard |
| **Infrastructure** | 🔴 High | การตั้งค่า Admin Desktop (Desk-5439) สำหรับ AI | ติดตั้ง Ollama, Qdrant, n8n, PaddleOCR, PyThaiNLP |
### Cross-Module Dependencies
```mermaid
graph TB
subgraph QNAP["🖥️ QNAP NAS (Production Host)"]
BE[NestJS Backend API]
N8N[n8n Workflow Orchestrator]
end
subgraph DESK["🖥️ Desk-5439 (AI Isolation Host)"]
OLLAMA["Ollama\ngemma4:e4b Q8_0\n+ nomic-embed-text"]
QDRANT[Qdrant Vector Store]
NLP[PaddleOCR + PyThaiNLP]
end
BE --"HTTP API"--> N8N
N8N --"Ollama REST API"--> OLLAMA
N8N --"Qdrant REST API"--> QDRANT
N8N --"HTTP"--> NLP
N8N --"DMS API (MariaDB update)"--> BE
BE --"RAG Query"--> QDRANT
BE --"LLM Inference"--> OLLAMA
```
---
## 📋 Version Dependency Matrix
| ADR | Version | Dependency Type | Affected Version(s) | Implementation Status |
|-----|---------|-----------------|---------------------|----------------------|
| **ADR-023A** | 1.2 | Model Revision | v1.9.0+ | ✅ Active |
| **ADR-023** | 1.1 | Base Architecture | v1.9.0+ | ✅ Active (superseded by 023A for model config) |
| **ADR-016** | 2.0 | Governs | v1.8.0+ | ✅ Active |
| **ADR-019** | 1.5 | Governs | v1.8.0+ | ✅ Active |
---
## Implementation Details (ข้อกำหนดเชิงลึกรายหมวด)
### 1. Security Isolation Policy (ขอบเขตความปลอดภัย)
* **Physical Isolation:** เซอร์วิส AI ทั้งหมด (Ollama, Qdrant, PaddleOCR) **ต้องรันบน Admin Desktop (Desk-5439)** ที่มี GPU RTX 2060 Super 8GB เท่านั้น ห้ามรันบน QNAP NAS หลัก
* **No Direct DB/Storage Access:** เครื่อง AI Host **ห้าม**มีการเชื่อมต่อฐานข้อมูล MariaDB หรือเมาท์ Storage ปลายทางโดยตรง การอ่าน/เขียนข้อมูลทั้งหมดต้องทำผ่าน **DMS Backend API**
* **Validation Layer:** Backend ต้องตรวจสอบความถูกต้องของ Output จาก AI (Schema, System Enum, Confidence Threshold) ก่อนบันทึกลงฐานข้อมูลเสมอ
#### AI RBAC Permission Matrix
> Permission ใหม่ที่ต้องเพิ่มใน `lcbp3-v1.9.0-seed-permissions.sql` (module: `ai`, ID range: 181-190)
| Permission | คำอธิบาย | Superadmin (1) | Org Admin (2) | Document Control (3) | Editor (4) | Viewer (5) |
|---|---|:---:|:---:|:---:|:---:|:---:|
| `ai.suggest` | รับ AI Suggestion เมื่อสร้าง/แก้ไขเอกสาร | ✅ | ✅ | ✅ | ❌ | ❌ |
| `ai.rag_query` | ใช้ RAG Q&A สืบค้นเอกสาร | ✅ | ✅ | ✅ | ❌ | ❌ |
| `ai.migration_manage` | จัดการ Migration Batch (Review/Import/Reject) | ✅ | ✅ | ✅ | ❌ | ❌ |
| `ai.audit_log_delete` | Hard Delete `ai_audit_logs` | ✅ | ❌ | ❌ | ❌ | ❌ |
### 2. Core Infrastructure & Models
> **นโยบาย:** เอกสารทั้งหมดใน LCBP3 จัดชั้นเป็น **INTERNAL** — AI Inference ทั้งหมดต้องรันภายใน Physical Isolation Boundary บน Desk-5439 เท่านั้น ห้ามใช้ Cloud AI Provider โดยเด็ดขาด
#### 2.1 Model Stack (2 โมเดลเท่านั้น)
| โมเดล | Role | VRAM (โดยประมาณ) | หมายเหตุ |
|-------|------|-----------------|---------|
| `gemma4:e4b Q8_0` | General Inference + OCR Post-processing + Extraction + RAG Q&A | ~4.0GB (weights) + ~0.2GB (KV Cache) | Q8_0 ≈ 4B × 8-bit = ~4.0GB; KV Cache ต่ำเพราะ input ≤ 3 หน้า (~2,000 tokens) |
| `nomic-embed-text` | Embedding 768-dim → Qdrant | ~0.3GB | สร้าง Semantic Vector สำหรับ Hybrid Search |
| **รวม (peak)** | | **~4.5GB** | **เผื่อ headroom ~3.5GB — มั่นใจสูง เพราะ PDF input จำกัด ≤ 3 หน้า** |
* **Orchestrator:** ใช้ **n8n** เป็นตัวควบคุม Flow **Migration Phase เท่านั้น** (trigger batch, monitor progress, handle retry ระดับ batch) — ห้าม n8n เรียก Ollama หรือ PaddleOCR โดยตรง
* **Job Executor:** ทุก AI Inference (OCR, Extraction, Embedding, RAG) ต้องผ่าน **BullMQ บน NestJS เท่านั้น** — n8n call `POST /api/ai/jobs` เพื่อ queue job แล้ว poll ผลผ่าน `GET /api/ai/jobs/:jobId`
```
Migration Flow:
n8n → POST /api/ai/jobs (DMS API) → BullMQ (ai-batch)
→ Worker: PaddleOCR / Ollama บน Desk-5439
→ n8n poll GET /api/ai/jobs/:jobId → ได้ผล → POST /api/ai/migration/review
Real-time Flow (User Upload):
NestJS Controller → BullMQ (ai-batch) → Worker → ai_audit_logs
```
> **เหตุผล:** การ inference ทั้งหมดผ่าน BullMQ ทำให้ RBAC, ADR-007 Error Handling และ `ai_audit_logs` ครอบคลุมทุก job โดยอัตโนมัติ — ถ้า n8n bypass BullMQ จะเกิด audit gap
* **LLM Engine:** ใช้ **Ollama** บน Desk-5439 รันโมเดล `gemma4:e4b Q8_0` สำหรับงานทั้งหมด ได้แก่ General Inference, OCR Post-processing, Metadata Extraction, Classification และ RAG Q&A
* **Embedding Model:** ใช้ `nomic-embed-text` รันผ่าน Ollama บน Desk-5439 สำหรับแปลงเวกเตอร์ 768-มิติ
* **OCR & NLP:** ใช้ **PaddleOCR** สกัดข้อความจาก Scanned PDF และใช้ **PyThaiNLP** ตัดคำ/เตรียมข้อความภาษาไทย — ทั้งคู่รันบน Desk-5439
***Typhoon Local:** ไม่ใช้ — ถูกแทนที่โดย `gemma4:e4b Q8_0` เพื่อรักษา VRAM Budget
***Typhoon Cloud API:** ไม่ใช้ — `rag/typhoon.service.ts` ต้องถูก Remove ออกจาก Codebase (Dead Code + Security Risk)
#### 2.2 BullMQ Queue Architecture (GPU Overload Prevention)
> **นโยบาย:** ใช้ **2 Queues แยกอิสระ** เพื่อป้องกัน RAG Q&A ถูก Block โดย Batch Jobs และป้องกัน VRAM Overflow บน RTX 2060 Super 8GB ทั้งสอง Queue มี **concurrency = 1** เสมอ
##### Phase Context (กำหนดลักษณะการใช้งานแต่ละช่วง)
| Phase | ช่วงเวลา | Volume | หมายเหตุ |
|-------|---------|--------|---------|
| **Migration Phase** | Pre-launch (ก่อนเปิดให้ User ทั่วไป) | ~20,000 ฉบับ (Batch ครั้งเดียว) | รัน `ai-batch` เต็มแรง ไม่มี User แย่งคิว — queue contention = 0 |
| **Production Phase** | หลัง Go-live | ~50 ฉบับ/วัน | Volume ต่ำมาก ทั้ง 2 queues แทบไม่มีงานค้าง |
##### Queue 1: `ai-realtime` (Interactive User Requests)
```
Queue: ai-realtime (BullMQ)
concurrency: 1
defaultJobOptions:
attempts: 3
backoff: { type: 'exponential', delay: 3000 }
```
| Job Type | โมเดลที่ใช้ | SLA Target |
|----------|-----------|------------|
| `rag-query` | `gemma4:e4b Q8_0` | p95 < 10s (นับตั้งแต่ dequeue) |
| `ai-suggest` | `gemma4:e4b Q8_0` | p95 < 8s |
##### Queue 2: `ai-batch` (Background Processing)
```
Queue: ai-batch (BullMQ)
concurrency: 1
defaultJobOptions:
attempts: 3
backoff: { type: 'exponential', delay: 5000 }
```
| Job Type | โมเดลที่ใช้ | Priority |
|----------|-----------|---------|
| `ocr-postprocess` | `gemma4:e4b Q8_0` | Normal |
| `metadata-extract` | `gemma4:e4b Q8_0` | Normal |
| `embed-document` | `nomic-embed-text` | Low |
> ⚠️ **GPU Constraint:** แม้จะแยก 2 Queue แต่ Ollama Worker บน Desk-5439 มี GPU เดียว — หาก `ai-realtime` และ `ai-batch` รัน Job พร้อมกัน VRAM อาจเต็ม ให้ตั้งค่า `ai-batch` pause อัตโนมัติเมื่อ `ai-realtime` มี active job (ผ่าน BullMQ Event hooks: `active` / `completed`)
### 3. Legacy Data Migration (การนำเข้าข้อมูลเก่า)
#### 3.0 Ingestion Workflow Overview
```
┌─────────────────────────────────────────────────────────────────┐
│ 1.1 LEGACY INGESTION (Pre-launch, Migration Phase) │
│ │
│ Admin วางไฟล์ใน Folder │
│ → n8n UI: Admin กด "Run Migration Workflow" ด้วยตนเอง │
│ → n8n → POST /api/ai/jobs → BullMQ (ai-batch) │
│ → OCR/Extract → migration_review_queue (PENDING) │
│ → DMS Frontend /admin/ai-migration: Admin Approve/Reject │
│ → IMPORTED → AUTO: queue embed-document (ai-batch) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 1.2 NEW DOCUMENT INGESTION (Production Phase, ~50/วัน) │
│ │
│ User Upload ผ่านช่องทางปกติ (RFA / Correspondence form) │
│ → Two-Phase Upload: Temp storage + ClamAV scan │
│ → User Submit → DMS commit (temp → permanent) │
│ → AUTO (parallel): │
│ ├─ queue ocr-postprocess + metadata-extract (ai-batch)│
│ │ → AI Suggestion แสดงบนฟอร์ม │
│ │ → Human confirm Metadata │
│ └─ queue embed-document (ai-batch, Low priority) │
│ → Qdrant indexed (background, ไม่บล็อก User) │
│ │
│ ช่วง gap (commit → embed เสร็จ): ค้นหาผ่าน DB search ปกติได้ │
│ ไม่ต้อง real-time — ยอมรับได้ │
└─────────────────────────────────────────────────────────────────┘
```
> **RAG Embedding Trigger: AUTO เสมอ หลัง commit ทันที** — ห้าม Manual trigger เพื่อป้องกัน Qdrant index ไม่สมบูรณ์
> - **Legacy:** trigger หลัง Admin Approve (`PENDING` → `IMPORTED`)
> - **New doc:** trigger ทันทีหลัง Two-Phase commit สำเร็จ (parallel กับ AI Suggestion — ไม่รอ Human confirm)
> - **Gap period** (ระหว่าง commit → embed เสร็จ): ใช้ MariaDB full-text search แทน — ยอมรับได้ ไม่ต้อง real-time
#### 3.1 Frontend UI Scope (DMS Frontend vs n8n UI)
| งาน | UI ที่ใช้ | หมายเหตุ |
|-----|---------|---------|
| Trigger Migration Batch | **n8n Workflow UI** | Admin กด Run ใน n8n — ไม่มีปุ่มใน DMS Frontend |
| Review migration_review_queue | **DMS Frontend** `/admin/ai-migration` | Approve / Reject + แก้ไข Metadata |
| Monitor job progress | **DMS Frontend** (BullMQ dashboard หรือ job status API) | `GET /api/ai/jobs/:jobId` |
| AI Suggestion on new doc | **DMS Frontend** (form inline) | แสดงบนฟอร์ม RFA/Correspondence |
* **Staging Area:** ข้อมูลที่ประมวลผลผ่าน n8n จะถูกส่งเข้าตาราง `migration_review_queue` เสมอ
* **Record Lifecycle:** Record ใน `migration_review_queue` **ไม่ถูกลบ** หลัง Import — เปลี่ยน `status` เป็น `IMPORTED` เก็บไว้ตลอดเพื่อ Debug และตรวจสอบ Batch ย้อนหลัง
* Status transitions: `PENDING``IMPORTED` | `PENDING``REJECTED`
* **Confidence Threshold Policy** (กำหนดผ่าน `.env` — ไม่ Hardcode, ไม่มี Admin UI):
* `AI_THRESHOLD_HIGH=0.85` และ `is_valid = true` $\rightarrow$ สถานะ `PENDING` (พร้อม Import)
* `AI_THRESHOLD_MID=0.60` ถึง `AI_THRESHOLD_HIGH-0.01` $\rightarrow$ สถานะ `PENDING` (ไฮไลต์เตือน Admin)
* ต่ำกว่า `AI_THRESHOLD_MID` หรือ `is_valid = false` $\rightarrow$ สถานะ `REJECTED`
* การเปลี่ยนค่าต้อง Restart service และมีร่องรอยใน deployment log
* **Threshold Recalibration Policy:**
* ค่าเริ่มต้น (0.85/0.60) ถูกกำหนดในยุค gemma4:9b — ใช้เป็น baseline สำหรับ Migration Phase แรก
* **หลัง import เอกสารชุดแรก 100–500 ฉบับ:** ทบทวนค่า threshold โดยดูจาก `ai_audit_logs` (`confidence_score` distribution) เปรียบเทียบกับ Admin override rate
* **เกณฑ์ปรับ:** ถ้า REJECTED rate > 30% หรือ Admin override rate > 40% ให้ปรับลด threshold ลง และบันทึกการเปลี่ยนแปลงใน Review History ของ ADR นี้
* **ผู้รับผิดชอบ:** AI Integration Lead ร่วมกับ Document Control Team
* **Idempotency Header:** บังคับส่ง `Idempotency-Key: <doc_number>:<batch_id>` ป้องกันบันทึกซ้ำ
* **Two-Phase Storage:** ไฟล์ถูกอัปโหลดเป็น Temp (`is_temporary = true`) และย้ายเข้า Storage จริงเมื่อเรียก API Commit เท่านั้น
### 4. Smart Classification & Real-time Ingestion
#### 4.1 PDF Input Limit (Hard Constraint)
> **กฎ:** ส่ง PDF เข้า **gemma4:e4b Q8_0** ได้ **สูงสุด 3 หน้าแรกเท่านั้น** สำหรับงาน Summarization, Classification และ Tagging
> ⚠️ **ข้อยกเว้น:** งาน `embed-document` (RAG) ใช้เอกสารทั้งฉบับ — ดู Section 5
**เหตุผล:**
- หน้าปก + หน้าที่ 1–2 ของเอกสารวิศวกรรมมักมีข้อมูลหลักครบ (Document Title, Drawing No., Discipline, Project Code, Revision)
- จำกัด KV Cache ที่ ~2,000 tokens → VRAM peak ≤ ~4.5GB ตามที่ออกแบบไว้
- ป้องกัน Job ใช้เวลานานเกิน SLA
**Implementation Note:**
```
n8n PDF Pre-processor:
extract_pages: [1, 2, 3] ← hard limit, ห้ามเปลี่ยนโดยไม่ review ADR
fallback: ถ้า PDF < 3 หน้า → ใช้ทั้งหมด
```
#### 4.2 PDF Type Auto-Detection (OCR Routing)
> **กฎ:** ระบบต้อง **detect อัตโนมัติ** ว่า PDF มี selectable text หรือไม่ ก่อนเลือก pipeline — ห้ามให้ User เลือกเอง
```
PDF Upload
└─ n8n: ตรวจสอบ text layer (PyMuPDF: page.get_text())
├─ มีข้อความ (len > threshold) → Fast Path: text parser โดยตรง
└─ ไม่มีข้อความ / image-only → Slow Path: PaddleOCR → PyThaiNLP
```
| Path | เงื่อนไข | เครื่องมือ | เวลาโดยประมาณ |
|------|---------|----------|--------------|
| **Fast Path** | `extracted_chars > 100` ต่อหน้า | PyMuPDF text parser | < 1s |
| **Slow Path** | `extracted_chars ≤ 100` ต่อหน้า | PaddleOCR + PyThaiNLP | 530s/หน้า |
> **หมายเหตุ:** threshold `100 chars` ป้องกัน PDF ที่มี text layer แต่ข้อมูลน้อยมาก (เช่น มีแค่ watermark) ถูก route ไป Fast Path ผิด — ปรับค่าได้ผ่าน `.env: OCR_CHAR_THRESHOLD=100`
* **Enum Enforcement:** ฟิลด์หมวดหมู่และประเภทเอกสารที่สกัดได้ ต้องนำไปทวนสอบกับ Master Data (`GET /api/meta/categories`) เสมอ ห้ามให้ AI สร้างประเภทเอกสารขึ้นมาเองโดยพลการ
* **Human Override:** นำเสนอผลลัพธ์บนหน้าจอ RFA/Correspondence ให้ผู้ใช้กดยืนยันหรือแก้ไขก่อนบันทึก
### 5. Hybrid Retrieval-Augmented Generation (RAG)
#### 5.1 Document Embedding Scope (แตกต่างจาก Classification)
> **กฎ:** `embed-document` ต้องฝัง Vector จาก **เอกสารทั้งฉบับ** (ไม่ใช่แค่ 3 หน้า) เพื่อให้ RAG ค้นหาเนื้อหาจากทุกหน้าได้
**เหตุผลที่ไม่กระทบ VRAM:**
- `nomic-embed-text` ประมวลผล **chunk ทีละชิ้น** (stateless) — ไม่ต้องโหลดเอกสารทั้งฉบับพร้อมกัน
- VRAM peak ของ embed job = ขนาด 1 chunk (~512 tokens) เท่านั้น ≈ negligible
**Chunking Strategy:**
```
Chunk size: 512 tokens
Overlap: 64 tokens ← รักษา context ข้ามขอบ chunk
Unit: paragraph-aware (ตัดที่ paragraph boundary ก่อน token boundary)
Max chunks/doc: ไม่จำกัด — ขึ้นกับความยาวเอกสาร
```
**Qdrant Payload ต่อ chunk:**
```json
{
"document_public_id": "<uuid>",
"project_public_id": "<uuid>",
"page_number": 12,
"chunk_index": 5
}
```
#### 5.2 RAG Pipeline
* **Hybrid Search Strategy:** ผสานคะแนน Vector Similarity (0.7) + Keyword Exact Match (0.3) และผ่าน Score-based Re-ranking
* **Multi-tenant Isolation:** บังคับใช้ Qdrant Payload Filter กำหนด `project_public_id` เป็นเงื่อนไขในการสืบค้น **ทุกครั้ง** — enforce ผ่าน `QdrantService` wrapper ที่กำหนด `projectPublicId: string` เป็น **required parameter** (ไม่มี optional fallback) ดังนี้:
```typescript
// ✅ Required contract — ห้ามเปลี่ยน signature โดยไม่ review ADR
@Injectable()
export class QdrantService {
async search(
projectPublicId: string, // required — compile-time enforcement
vector: number[],
topK: number = 5,
): Promise<QdrantSearchResult[]> {
return this.client.search('documents', {
vector,
limit: topK,
filter: {
must: [{ key: 'project_public_id', match: { value: projectPublicId } }],
},
});
}
async upsert(
projectPublicId: string, // required
chunks: DocumentChunk[],
): Promise<void> { ... }
// ❌ ห้าม expose rawSearch() หรือ method ที่ไม่บังคับ filter
}
```
* **LLM สำหรับ RAG Q&A:** ใช้ **Local Ollama (`gemma4:e4b Q8_0`)** บน Desk-5439 เท่านั้น — ไม่มี Cloud Fallback เนื่องจากเอกสารทั้งหมดจัดชั้นเป็น INTERNAL
* **Context Window สำหรับ RAG:** ส่ง top-K chunks (K=5) เข้า gemma4:e4b ≈ 5 × 512 = ~2,560 tokens — อยู่ในขีดจำกัด VRAM
* **Performance Target:** $p95 < 10s$ สำหรับการตอบคำถามผ่าน Local LLM (นับตั้งแต่ dequeue จาก `ai-realtime`)
### 6. `ai_audit_logs` — AI Development Feedback Log
> **วัตถุประสงค์:** บันทึก AI Suggestion + การตัดสินใจของมนุษย์เพื่อใช้วิเคราะห์และปรับปรุงคุณภาพโมเดล AI — **ไม่ใช่ Compliance Audit Trail**
> Compliance จริงๆ ถูกบันทึกอยู่ใน `audit_logs` แล้ว (Human Confirm Action)
* **Key Columns:** `document_public_id`, `model_name`, `ai_suggestion_json`, `human_override_json`, `confidence_score`, `confirmed_by_user_id`, `created_at`
* **Retention:** ตลอดอายุโครงการ (~5-10 ปี) — Admin สามารถ **Hard Delete** ได้ผ่าน Frontend เพื่อจัดการ Test Data และ Storage
* **RBAC:** เฉพาะ Role `SYSTEM_ADMIN` เท่านั้นที่ลบได้ — การลบทุกครั้งต้องบันทึกใน `audit_logs` (`action: 'AI_AUDIT_LOG_DELETED'`)
* **ห้าม Merge:** ต้องเป็น Table แยกจาก `audit_logs` เพื่อให้ Query ด้วย Typed Columns ได้ (เช่น `WHERE confidence_score < 0.85`)
---
## Consequences
### Positive
1. ✅ มีมาตรฐานสถาปัตยกรรม AI ที่เป็นหนึ่งเดียว ง่ายต่อการอ้างอิงและตรวจสอบสิทธิ์
2. ✅ ปลอดภัยสูงสุดตามหลักการ Zero Trust ป้องกันฐานข้อมูลและไฟล์ระบบจากความเสี่ยงของเซอร์วิสภายนอก
3. ✅ รองรับเอกสารภาษาไทยได้อย่างแม่นยำผ่านกระบวนการ NLP เฉพาะทาง
4. ✅ ควบคุมการใช้งานทรัพยากรได้อย่างมีประสิทธิภาพ ไม่รบกวน Production NAS
5.**VRAM Budget เสถียร:** 2-Model Stack ใช้ ~4.5GB จาก 8GB (peak) — มี headroom ~3.5GB; ยืนยันโดย PDF 3-page hard limit
6.**BullMQ Sequential Queue ลด Complexity:** ไม่มี Logic เลือกโมเดล Worker ทำงานได้ตรงไปตรงมา
### Negative
1. ❌ ระบบมีความซับซ้อนในการตั้งค่าและเชื่อมต่อเครือข่ายระหว่าง NAS และ Admin Desktop
2. ❌ มี Overhead ในการดูแลรักษาเครื่อง Desktop (GPU Temperature, Service Uptime)
3. ❌ ไม่มี Typhoon Local ที่ Fine-tune ภาษาไทยโดยเฉพาะ — ต้องใช้ Prompt Engineering ชดเชย
### ⚠️ Unvalidated Assumptions (ข้อสมมติที่ยังไม่ได้ทดสอบ)
> การตัดสินใจเปลี่ยนจาก Typhoon Local → gemma4:e4b Q8_0 อาศัยเหตุผล VRAM เป็นหลัก **ยังไม่มีหลักฐานเชิงคุณภาพ** รองรับ
| Assumption | ความเสี่ยง | Validation Plan |
|------------|-----------|-----------------|
| gemma4:e4b Q8_0 + Prompt Engineering ชดเชยคุณภาพ Typhoon Local ได้ | OCR Post-processing และ Metadata Extraction ภาษาไทยอาจด้อยกว่า → Confidence Score ต่ำ → เพิ่มภาระ Admin Review | ทดสอบด้วย Sample 50–100 ฉบับจากเอกสารจริง เปรียบเทียบ Accuracy ก่อน Go-live |
| PyThaiNLP Pre-processing เพียงพอชดเชยความขาด Thai Fine-tuning | ข้อความที่ตัดคำไม่ถูกต้องอาจทำให้โมเดลเข้าใจผิด | วัด Word Error Rate บน OCR output จริง ก่อน Production |
**ข้อตกลง:** หากทดสอบแล้วพบ Accuracy ต่ำกว่า 80% สำหรับ Thai Metadata Extraction ให้พิจารณา Option เพิ่มเติม เช่น Quantized Typhoon ขนาดเล็กหรือ LoRA Adapter บน gemma4:e4b
### Mitigation Strategies
* **Graceful Degradation (Desk-5439 ออฟไลน์):** DMS Core ยังทำงานได้ปกติทุก Feature — เฉพาะ AI Features ถูก Disable ชั่วคราว:
* Backend ตรวจสอบ Health Check ของ Desk-5439 ทุก **60 วินาที** ผ่าน `/health` endpoint ของ Ollama และ Qdrant
* เมื่อ Desk-5439 ออฟไลน์ → set `AI_AVAILABLE = false` ใน Redis Cache
* Frontend แสดง **Global Banner:** "⚠️ ระบบ AI ไม่พร้อมใช้งานชั่วคราว กรุณากรอกข้อมูลด้วยตนเอง"
* AI Classification form fields แสดงผล แต่ AI Suggestion ถูก hide — User กรอกเองได้ปกติ
* RAG Q&A endpoint return `503 Service Unavailable` พร้อม error message ที่อ่านเข้าใจได้
* **GPU Overload Prevention:** ใช้ BullMQ จัดคิวงาน AI แบบ Sequential (concurrency = 1) เพื่อไม่ให้ VRAM 8GB โอเวอร์โหลด — ดูรายละเอียดใน Section 2.2
* **Thai Language Quality:** ใช้ PyThaiNLP สำหรับ Pre-processing (word segmentation) ก่อนส่งให้ gemma4:e4b เพื่อรักษาคุณภาพข้อความภาษาไทย
---
## 🔄 Review Cycle & Maintenance
### Review Schedule
- **Next Review:** 2026-11-15 (6 months from revision)
- **Review Type:** Scheduled Core Architecture Review
- **Reviewers:** System Architect, Security Lead, AI Integration Lead
### Review History
| Version | Date | Changes | Status |
|---------|------|---------|--------|
| 1.0 | 2026-05-14 | ยุบรวมและแทนที่ ADR-017, 017B, 018, 020, 022 เป็นฉบับเดียว | ✅ Superseded |
| 1.1 | 2026-05-14 | Grilling Session: (1) ล็อค Local-only AI บน Desk-5439 ทั้งหมด (2) แยก Typhoon Local vs Cloud (3) ลบ Typhoon Cloud API ออก (4) กำหนด `ai_audit_logs` เป็น Development Feedback Log ไม่ใช่ Compliance (5) เพิ่ม Admin Hard Delete Policy | ✅ Superseded by 023A |
| 1.2 | 2026-05-15 | ADR-023A: เปลี่ยน Model Stack 3→2 (ลบ Typhoon Local, เปลี่ยน gemma4:9b → gemma4:e4b Q8_0), เพิ่ม BullMQ Queue Policy Table, เพิ่ม VRAM Budget breakdown | ✅ Active |
---
## Related ADRs (อดีตเอกสารที่ถูกแทนที่)
- [ADR-017: Ollama Data Migration Architecture](./ADR-017-ollama-data-migration.md) — **Superseded**
- [ADR-017B: AI Document Classification](./ADR-017B-ai-document-classification.md) — **Superseded**
- [ADR-018: AI Boundary Policy](./ADR-018-ai-boundary.md) — **Superseded**
- [ADR-020: AI Intelligence Integration Architecture](./ADR-020-ai-intelligence-integration.md) — **Superseded**
- [ADR-022: Retrieval-Augmented Generation (RAG) System](./ADR-022-retrieval-augmented-generation.md) — **Superseded**