260322:1648 Correct Coresspondence / Doing RFA / Correct CI
CI Pipeline / build (push) Failing after 12m41s
Build and Deploy / deploy (push) Failing after 2m44s

This commit is contained in:
admin
2026-03-22 16:48:12 +07:00
parent e5deedb42e
commit 11984bfa29
683 changed files with 105251 additions and 29068 deletions
+170 -149
View File
@@ -5,6 +5,7 @@
**Version:** 1.8.1
**Date:** 2026-03-13
**Related Documents:**
- [ADR-017: Ollama Data Migration](../06-Decision-Records/ADR-017-ollama-data-migration.md)
- [ADR-018: AI Boundary Hardening](../06-Decision-Records/Patch%201.8.1.md)
- [n8n Migration Setup Guide](./03-05-n8n-migration-setup-guide.md)
@@ -12,12 +13,14 @@
- [OpenRAG (openr.ag)](https://www.openr.ag/) — IBM open-source RAG: Docling + OpenSearch + Langflow
> ⚠️ **หมายเหตุ:** เอกสารนี้ออกแบบ RAG Pipeline **2 ส่วน**:
>
> 1. **OpenRAG (Extraction Phase)** — ทำหน้าที่ "พนักงานคัดกรองข้อมูล" อ่าน PDF ทั้ง Folder แล้วเขียน JSON ลง `rag-output/` บน Shared NAS
> 2. **n8n + Ollama + Elasticsearch (Integration & Search Phase)** — Poll ไฟล์ JSON จาก `rag-output/` ทีละไฟล์ แล้วนำเข้า DMS
>
> ทั้งหมดทำงาน **On-Premise** เท่านั้น — ไม่ส่งข้อมูลออกนอกเครือข่าย (ADR-018 AI Isolation)
>
> **Integration Model: File-based Queue (Pull)**
>
> - Admin Desktop mount `R:\` (Drive Letter) → QNAP NAS Shared Folder (`staging_ai`)
> - OpenRAG เขียน JSON ลง `R:\staging_ai\rag-output\` → n8n อ่านจาก `staging_ai/rag-output/`
> - **ไม่มี HTTP ระหว่าง OpenRAG กับ n8n** — NAS Folder เป็น Shared Queue
@@ -27,6 +30,7 @@
## 🎯 วัตถุประสงค์ (Objective)
เพิ่มความสามารถ **Semantic Search และ Document Q&A** ให้กับระบบ DMS โดยใช้ Infrastructure ที่มีอยู่แล้ว:
- ไม่ส่งข้อมูลออกนอกเครือข่ายองค์กร (Data Privacy)
- ไม่มีค่าใช้จ่ายต่อ Query (Zero Cost)
- ต่อยอดจากสถาปัตยกรรม Migration ที่ผ่าน Validate แล้ว (ADR-017/018)
@@ -37,16 +41,16 @@
ตาม Patch 1.8.1 (ADR-018) Infrastructure Layout ที่กำหนดไว้:
| Component | Host | บทบาทใน RAG Pipeline |
| ---------------------- | ------------- | ------------------------------------------------- |
| **OpenRAG** (Docling + OpenSearch + Langflow) | Admin Desktop | **Phase 0: Extraction** — สกัด Metadata + Text จาก PDF เป็น JSON |
| **Tika** (Fallback OCR) | QNAP | สกัดข้อความจาก PDF กรณีไม่ใช้ OpenRAG หรือ Fallback |
| **Elasticsearch 8.11** | QNAP | Vector Store + Full-text Index |
| **n8n** | QNAP | Orchestrator — Poll JSON จาก `rag-output/` (ทีละไฟล์) แล้วนำเข้า DMS |
| **DMS Backend (NestJS)**| QNAP | API Gateway — รับ Query / ส่งผล / บันทึก Metadata |
| **Ollama** | Admin Desktop | AI Inference (Embedding + Generate) บน RTX 2060 SUPER |
| **MariaDB 11.8** | QNAP | Document Metadata (Authoritative DB) |
| **Redis 7.2** | QNAP | Cache (Query Result Cache) |
| Component | Host | บทบาทใน RAG Pipeline |
| --------------------------------------------- | ------------- | -------------------------------------------------------------------- |
| **OpenRAG** (Docling + OpenSearch + Langflow) | Admin Desktop | **Phase 0: Extraction** — สกัด Metadata + Text จาก PDF เป็น JSON |
| **Tika** (Fallback OCR) | QNAP | สกัดข้อความจาก PDF กรณีไม่ใช้ OpenRAG หรือ Fallback |
| **Elasticsearch 8.11** | QNAP | Vector Store + Full-text Index |
| **n8n** | QNAP | Orchestrator — Poll JSON จาก `rag-output/` (ทีละไฟล์) แล้วนำเข้า DMS |
| **DMS Backend (NestJS)** | QNAP | API Gateway — รับ Query / ส่งผล / บันทึก Metadata |
| **Ollama** | Admin Desktop | AI Inference (Embedding + Generate) บน RTX 2060 SUPER |
| **MariaDB 11.8** | QNAP | Document Metadata (Authoritative DB) |
| **Redis 7.2** | QNAP | Cache (Query Result Cache) |
> ⛔ **ข้อห้าม (ADR-018):** OpenRAG และ Ollama **ห้ามอยู่บน QNAP** และห้ามเข้า DB โดยตรง
> ✅ OpenRAG เขียนผล JSON ลง `rag-output/` บน Shared NAS (R:\ บน Admin Desktop = `staging_ai` บน QNAP)
@@ -174,11 +178,11 @@ n8n ทำงานแบบ **Pull (Schedule-based)** — ดึง JSON ที
> 📁 **File State Machine ใน `rag-output/`:**
>
> | สถานะ | Filename | ความหมาย |
> |-------|----------|----------|
> | Pending | `TCC-COR-001.json` | รอ n8n ดึงไป Process |
> | Done | `TCC-COR-001.done` | นำเข้า DMS สำเร็จ |
> | Error | `TCC-COR-001.error` | ล้มเหลว — รอ Manual Review |
> | สถานะ | Filename | ความหมาย |
> | ------- | ------------------- | -------------------------- |
> | Pending | `TCC-COR-001.json` | รอ n8n ดึงไป Process |
> | Done | `TCC-COR-001.done` | นำเข้า DMS สำเร็จ |
> | Error | `TCC-COR-001.error` | ล้มเหลว — รอ Manual Review |
### Phase 2: Indexing Pipeline — สร้าง Vector Index ใน Elasticsearch
@@ -267,9 +271,10 @@ PUT /dms_rag_chunks
```
> ⚠️ **ขนาด Embedding Vector:** ขึ้นอยู่กับ Model ที่ใช้
>
> - `nomic-embed-text`: 768 dims
> - `llama3.2:3b` (ใช้ layer สุดท้าย): 3072 dims
> ต้องทดสอบ Performance บน RTX 2060 SUPER 8GB ก่อนเลือก
> ต้องทดสอบ Performance บน RTX 2060 SUPER 8GB ก่อนเลือก
---
@@ -286,19 +291,19 @@ PUT /dms_rag_chunks
Poll ไฟล์ JSON จาก Shared NAS ทีละไฟล์ แล้วนำข้อมูลเข้า DMS:
| Node | ชื่อ | หน้าที่ |
|------|------|----------|
| 0 | Schedule Trigger | ทำงานทุก 5 นาที (หรือ Manual Trigger) |
| 1 | List JSON Files | อ่านรายการ `staging_ai/rag-output/*.json` (กรอง .done/.error) |
| 2 | Loop Items | วนลูปทีละ 1 ไฟล์ |
| 3 | Read JSON File | อ่านเนื้อหา JSON จาก NAS |
| 4 | JSON Schema Validator | ตรวจสอบ field ครบ + ค่า is_valid |
| 5 | Confidence Router | แยก Auto / Review / Reject ตาม Threshold |
| 6A | Auto Ingest | POST `/api/migration/import` พร้อม Idempotency-Key |
| 6B | Review Queue | INSERT `migration_review_queue` เท่านั้น |
| 6C | Rename to .error | Rename ไฟล์ → `.error` + บันทึกเหตุผล |
| 7 | Rename to .done | Rename ไฟล์ → `.done` (กรณีสำเร็จ) |
| 8 | Save Checkpoint | UPDATE `migration_progress` ทุก 10 records |
| Node | ชื่อ | หน้าที่ |
| ---- | --------------------- | ------------------------------------------------------------- |
| 0 | Schedule Trigger | ทำงานทุก 5 นาที (หรือ Manual Trigger) |
| 1 | List JSON Files | อ่านรายการ `staging_ai/rag-output/*.json` (กรอง .done/.error) |
| 2 | Loop Items | วนลูปทีละ 1 ไฟล์ |
| 3 | Read JSON File | อ่านเนื้อหา JSON จาก NAS |
| 4 | JSON Schema Validator | ตรวจสอบ field ครบ + ค่า is_valid |
| 5 | Confidence Router | แยก Auto / Review / Reject ตาม Threshold |
| 6A | Auto Ingest | POST `/api/migration/import` พร้อม Idempotency-Key |
| 6B | Review Queue | INSERT `migration_review_queue` เท่านั้น |
| 6C | Rename to .error | Rename ไฟล์ → `.error` + บันทึกเหตุผล |
| 7 | Rename to .done | Rename ไฟล์ → `.done` (กรณีสำเร็จ) |
| 8 | Save Checkpoint | UPDATE `migration_progress` ทุก 10 records |
---
@@ -306,37 +311,37 @@ Poll ไฟล์ JSON จาก Shared NAS ทีละไฟล์ แล้
Index Chunks (จาก OpenRAG JSON หรือ Tika Fallback) เข้า Elasticsearch:
| Node | ชื่อ | หน้าที่ |
|------|------|----------|
| 0 | Webhook / Schedule Trigger | รับ `doc_id` ที่นำเข้าแล้ว หรือ Batch รายคืน |
| 1 | Fetch Chunks | ดึง chunks จาก OpenRAG JSON หรือเรียก Tika Fallback |
| 2 | Tika OCR (Fallback) | POST `http://tika:9998/tika` กรณีไม่มี chunks จาก OpenRAG |
| 3 | Ollama Embeddings | POST `http://<OLLAMA_HOST>:11434/api/embeddings` |
| 4 | Elasticsearch Ingest | Bulk Index Chunks เข้า `dms_rag_chunks` |
| 5 | Update DMS Index Status | PATCH `/api/documents/{id}` ตั้ง `is_indexed: true` |
| Node | ชื่อ | หน้าที่ |
| ---- | -------------------------- | --------------------------------------------------------- |
| 0 | Webhook / Schedule Trigger | รับ `doc_id` ที่นำเข้าแล้ว หรือ Batch รายคืน |
| 1 | Fetch Chunks | ดึง chunks จาก OpenRAG JSON หรือเรียก Tika Fallback |
| 2 | Tika OCR (Fallback) | POST `http://tika:9998/tika` กรณีไม่มี chunks จาก OpenRAG |
| 3 | Ollama Embeddings | POST `http://<OLLAMA_HOST>:11434/api/embeddings` |
| 4 | Elasticsearch Ingest | Bulk Index Chunks เข้า `dms_rag_chunks` |
| 5 | Update DMS Index Status | PATCH `/api/documents/{id}` ตั้ง `is_indexed: true` |
---
## ⚙️ n8n Workflow: RAG Query (Node Overview)
| Node | ชื่อ | หน้าที่ |
|------|------|----------|
| 0 | Webhook | รับ `{ query, project_id, user_id, top_k }` จาก Backend |
| 1 | Ollama: Embed Query | แปลง Query เป็น Vector |
| 2 | Elasticsearch: kNN Search | ค้นหา Top-k Chunks พร้อม RBAC Filter |
| 3 | Build RAG Prompt | รวม Context Chunks + System Prompt + User Query |
| 4 | Ollama: Generate | สร้างคำตอบ, Output JSON เท่านั้น |
| 5 | Return to Backend | Respond Webhook พร้อม `{ answer, sources, confidence }` |
| Node | ชื่อ | หน้าที่ |
| ---- | ------------------------- | ------------------------------------------------------- |
| 0 | Webhook | รับ `{ query, project_id, user_id, top_k }` จาก Backend |
| 1 | Ollama: Embed Query | แปลง Query เป็น Vector |
| 2 | Elasticsearch: kNN Search | ค้นหา Top-k Chunks พร้อม RBAC Filter |
| 3 | Build RAG Prompt | รวม Context Chunks + System Prompt + User Query |
| 4 | Ollama: Generate | สร้างคำตอบ, Output JSON เท่านั้น |
| 5 | Return to Backend | Respond Webhook พร้อม `{ answer, sources, confidence }` |
---
## 📏 Confidence & Hallucination Guard
| ระดับ Confidence | การดำเนินการ |
|-----------------|--------------|
| `>= 0.80` | แสดงผลทันที พร้อม Sources |
| `0.60 0.79` | แสดงผลพร้อม Warning "โปรดตรวจสอบเอกสารต้นฉบับ" |
| `< 0.60` | ไม่แสดงคำตอบ — แสดงเฉพาะ Document Links ที่เกี่ยวข้อง |
| ระดับ Confidence | การดำเนินการ |
| ---------------- | ----------------------------------------------------- |
| `>= 0.80` | แสดงผลทันที พร้อม Sources |
| `0.60 0.79` | แสดงผลพร้อม Warning "โปรดตรวจสอบเอกสารต้นฉบับ" |
| `< 0.60` | ไม่แสดงคำตอบ — แสดงเฉพาะ Document Links ที่เกี่ยวข้อง |
> AI ไม่มีสิทธิ์ Write ข้อมูลใดๆ — Output เป็น JSON Read-only เสมอ (ADR-018)
@@ -344,20 +349,20 @@ Index Chunks (จาก OpenRAG JSON หรือ Tika Fallback) เข้า El
## 🚧 ข้อจำกัดและความเสี่ยง
| ความเสี่ยง | ผลกระทบ | Mitigation |
|-----------|----------|------------|
| NAS Drive R: disconnect ขณะ OpenRAG รัน | เขียน JSON ไม่ได้ | Langflow ตรวจ Drive ก่อนเริ่ม Loop — แจ้งเตือนถ้า mount หาย |
| ไฟล์ JSON เขียนไม่สมบูรณ์ (crash กลางคัน) | n8n อ่าน JSON เสีย | n8n ตรวจ JSON valid ก่อน Process — Rename → .error |
| OpenRAG Process PDF ซ้ำ (Retry) | JSON เขียนทับ | Skip ถ้า `.json` มีอยู่แล้ว (Idempotent by filename) |
| n8n อ่านไฟล์ขณะ OpenRAG ยังเขียนไม่เสร็จ | JSON ไม่สมบูรณ์ | OpenRAG เขียนเป็น `.tmp` ก่อน → Rename เป็น `.json` เมื่อเสร็จ |
| rag-output/ เต็ม (เก่าสะสม) | Disk บน NAS เต็ม | ตั้ง Schedule ลบ `.done` ที่เกิน 30 วัน |
| OpenRAG Metadata ผิด | นำข้อมูลผิดเข้า DMS | Confidence < 0.85 → Human Review Queue (ADR-017 Policy) |
| Embedding Dim Mismatch | Index ใช้งานไม่ได้ | กำหนด Model + Dims ก่อน Index แรก ห้ามเปลี่ยน |
| RTX 2060 SUPER VRAM (8GB) | Timeout ถ้า Model ใหญ่เกินไป | ใช้ `nomic-embed-text` สำหรับ Embedding |
| AI Hallucination | คำตอบผิด | Confidence Threshold + Source Citation บังคับ |
| Cross-project Data Leak | Security Issue | RBAC Filter ทุก Query ที่ Elasticsearch Layer |
| Elasticsearch Storage | Disk Usage สูง | เปิด ILM Policy หรือจำกัดเฉพาะ Project สำคัญ |
| Ollama ไม่พร้อม | Query ล้มเหลว | Graceful Fallback: ใช้ Elasticsearch Full-text เท่านั้น |
| ความเสี่ยง | ผลกระทบ | Mitigation |
| ----------------------------------------- | ---------------------------- | -------------------------------------------------------------- |
| NAS Drive R: disconnect ขณะ OpenRAG รัน | เขียน JSON ไม่ได้ | Langflow ตรวจ Drive ก่อนเริ่ม Loop — แจ้งเตือนถ้า mount หาย |
| ไฟล์ JSON เขียนไม่สมบูรณ์ (crash กลางคัน) | n8n อ่าน JSON เสีย | n8n ตรวจ JSON valid ก่อน Process — Rename → .error |
| OpenRAG Process PDF ซ้ำ (Retry) | JSON เขียนทับ | Skip ถ้า `.json` มีอยู่แล้ว (Idempotent by filename) |
| n8n อ่านไฟล์ขณะ OpenRAG ยังเขียนไม่เสร็จ | JSON ไม่สมบูรณ์ | OpenRAG เขียนเป็น `.tmp` ก่อน → Rename เป็น `.json` เมื่อเสร็จ |
| rag-output/ เต็ม (เก่าสะสม) | Disk บน NAS เต็ม | ตั้ง Schedule ลบ `.done` ที่เกิน 30 วัน |
| OpenRAG Metadata ผิด | นำข้อมูลผิดเข้า DMS | Confidence < 0.85 → Human Review Queue (ADR-017 Policy) |
| Embedding Dim Mismatch | Index ใช้งานไม่ได้ | กำหนด Model + Dims ก่อน Index แรก ห้ามเปลี่ยน |
| RTX 2060 SUPER VRAM (8GB) | Timeout ถ้า Model ใหญ่เกินไป | ใช้ `nomic-embed-text` สำหรับ Embedding |
| AI Hallucination | คำตอบผิด | Confidence Threshold + Source Citation บังคับ |
| Cross-project Data Leak | Security Issue | RBAC Filter ทุก Query ที่ Elasticsearch Layer |
| Elasticsearch Storage | Disk Usage สูง | เปิด ILM Policy หรือจำกัดเฉพาะ Project สำคัญ |
| Ollama ไม่พร้อม | Query ล้มเหลว | Graceful Fallback: ใช้ Elasticsearch Full-text เท่านั้น |
---
@@ -367,6 +372,7 @@ Index Chunks (จาก OpenRAG JSON หรือ Tika Fallback) เข้า El
> ต้องผ่าน Go-Live Gate ของ Migration (ADR-017) ก่อนเริ่มพัฒนา
**OpenRAG Setup (Admin Desktop):**
- [ ] ติดตั้ง OpenRAG บน Admin Desktop ตาม `## 🛠️ OpenRAG Setup Guide` ด้านล่าง
- [ ] กำหนด Langflow Workflow: PDF Input → Docling Parse → Ollama Extract → JSON Output
- [ ] ตั้งค่า System Prompt ใน Langflow ให้ Output ตรง JSON Contract ด้านบน
@@ -374,11 +380,13 @@ Index Chunks (จาก OpenRAG JSON หรือ Tika Fallback) เข้า El
- [ ] ยืนยัน OpenRAG ไม่มี DB Credentials และ Mount `staging_ai` เป็น Read-only
**n8n Webhook Integration:**
- [ ] สร้าง n8n Webhook Endpoint: รับ JSON จาก OpenRAG (validate schema + route ตาม Confidence)
- [ ] ทดสอบ Idempotency-Key กรณี OpenRAG ส่ง Duplicate
- [ ] สร้าง n8n Workflow: RAG Indexer (Dry Run กับ 10 เอกสาร)
**Search & Query:**
- [ ] Migration v1.8.x เสร็จสมบูรณ์และ Stable (Prerequisite)
- [ ] ทดสอบ `nomic-embed-text` บน Admin Desktop — วัด VRAM + Speed
- [ ] กำหนด Elasticsearch Index Schema + Dims (lock ก่อน Index แรก)
@@ -436,6 +444,7 @@ uv --version # ต้องแสดง version เช่น uv 0.5.x
เมื่อรัน `uvx --python 3.13 openrag` ในขั้นตอนถัดไป `uv` จะ **ดาวน์โหลด Python 3.13 เองโดยอัตโนมัติ** ไม่ต้องติดตั้งแยก
> **ทางเลือก:** ถ้าต้องการ Python 3.13 ระดับ System จริงๆ (ไม่บังคับ):
>
> ```bash
> sudo add-apt-repository ppa:deadsnakes/ppa -y
> sudo apt update && sudo apt install -y python3.13 python3.13-venv
@@ -461,20 +470,21 @@ uvx --with easyocr --python 3.13 openrag
**ระหว่าง Interactive Setup ตอบดังนี้:**
| Prompt | คำตอบ (สำหรับระบบ LCBP3) |
|--------|--------------------------|
| OpenSearch Admin password | ตั้งรหัสผ่านแข็งแรง บันทึกไว้ |
| Langflow Admin password | ตั้งรหัสผ่านแข็งแรง บันทึกไว้ |
| OpenAI API key | **กด N / Skip** — เราใช้ Ollama แทน |
| Use custom LLM provider? | **Y** → เลือก **Ollama** |
| Ollama base URL | `http://192.168.20.100:11434` (Internal VLAN — Ollama รันบน Admin Desktop โดยตรง) |
| Configure Langfuse tracing? | **N** |
| Configure cloud connectors? | **N** |
| Start services now? | **Y** |
| Prompt | คำตอบ (สำหรับระบบ LCBP3) |
| --------------------------- | --------------------------------------------------------------------------------- |
| OpenSearch Admin password | ตั้งรหัสผ่านแข็งแรง บันทึกไว้ |
| Langflow Admin password | ตั้งรหัสผ่านแข็งแรง บันทึกไว้ |
| OpenAI API key | **กด N / Skip** — เราใช้ Ollama แทน |
| Use custom LLM provider? | **Y** → เลือก **Ollama** |
| Ollama base URL | `http://192.168.20.100:11434` (Internal VLAN — Ollama รันบน Admin Desktop โดยตรง) |
| Configure Langfuse tracing? | **N** |
| Configure cloud connectors? | **N** |
| Start services now? | **Y** |
> ️ **Ollama รันบน Windows โดยตรง** (ไม่ใช่ใน Docker) ที่ IP `192.168.20.100` — ตรงกับ Config ใน `03-05-n8n-migration-setup-guide.md`
>
> ถ้าตั้งค่าผิดพลาด แก้ไขได้ที่:
>
> ```bash
> nano ~/.openrag/tui/.env
> # แก้บรรทัด OLLAMA_ENDPOINT=http://192.168.20.100:11434
@@ -496,11 +506,11 @@ docker ps
**URL ที่ใช้งานได้:**
| Service | URL | หมายเหตุ |
|---------|-----|----------|
| Service | URL | หมายเหตุ |
| ---------- | ----------------------- | ------------------------- |
| OpenRAG UI | `http://localhost:3000` | หน้าหลัก (เหมือน Chat UI) |
| Langflow | `http://localhost:7860` | สร้าง/แก้ไข Workflow |
| OpenSearch | `http://localhost:9200` | Vector Store API |
| Langflow | `http://localhost:7860` | สร้าง/แก้ไข Workflow |
| OpenSearch | `http://localhost:9200` | Vector Store API |
---
@@ -547,12 +557,13 @@ ollama pull nomic-embed-text
> **Component:** `Read File` (หมวด Data / Helpers)
| Setting | ค่า |
|---------|-----|
| Files | อัปโหลด หรือ ชี้ไปที่ `/data/staging_ai/` |
| Advanced Parser | `OFF` (ปิด — อ่านเป็น raw text ธรรมดา) |
| Setting | ค่า |
| --------------- | ----------------------------------------- |
| Files | อัปโหลด หรือ ชี้ไปที่ `/data/staging_ai/` |
| Advanced Parser | `OFF` (ปิด — อ่านเป็น raw text ธรรมดา) |
**การเชื่อมต่อ:**
- Output `Files` → Input `Inputs` ของ Loop
> ℹ️ Read File จะโหลดไฟล์ทั้งหมดมาเป็น list แล้วส่งให้ Loop วนลูปทีละไฟล์
@@ -564,11 +575,12 @@ ollama pull nomic-embed-text
> **Component:** `Loop` (หมวด Logic)
| Setting | ค่า |
|---------|-----|
| Inputs | รับจาก `Read File → Files` |
| Setting | ค่า |
| ------- | -------------------------- |
| Inputs | รับจาก `Read File → Files` |
**Output ที่ใช้:**
- `Item` → ส่งต่อให้ Parser และ Custom Code (filename)
- `Done` → ไม่ต้องเชื่อมไปไหน (สัญญาณสิ้นสุด Loop)
@@ -580,12 +592,13 @@ ollama pull nomic-embed-text
> **Component:** `Parser` (หมวด Processing)
| Setting | ค่า |
|---------|-----|
| Mode | **`Stringify`** (ไม่ใช่ Parser) |
| Data or DataFrame | รับจาก `Loop → Item` |
| Setting | ค่า |
| ----------------- | ------------------------------- |
| Mode | **`Stringify`** (ไม่ใช่ Parser) |
| Data or DataFrame | รับจาก `Loop → Item` |
**การเชื่อมต่อ:**
- Input `Data or DataFrame``Loop → Item`
- Output `Parsed Text` → Input `extracted_text` ของ Prompt Template
@@ -599,12 +612,13 @@ ollama pull nomic-embed-text
> **Component:** `Prompt Template` (หมวด Prompts)
| Setting | ค่า |
|---------|-----|
| Template | ใส่ System Prompt จากขั้นตอนที่ 7 ด้านล่าง |
| Variable `{extracted_text}` | เชื่อมกับ `Parser → Parsed Text` |
| Setting | ค่า |
| --------------------------- | ------------------------------------------ |
| Template | ใส่ System Prompt จากขั้นตอนที่ 7 ด้านล่าง |
| Variable `{extracted_text}` | เชื่อมกับ `Parser → Parsed Text` |
**การเชื่อมต่อ:**
- Variable `extracted_text``Parser → Parsed Text`
- Output `Prompt` → Input `Input` ของ Ollama
@@ -617,16 +631,17 @@ ollama pull nomic-embed-text
> **Component:** `Ollama` (หมวด Models)
| Setting | ค่า |
|---------|-----|
| Ollama API URL | `http://localhost:11434` (ถ้ารันบน WSL ไม่ต้องใส่ IP) |
| Model Name | `scb10x/typhoon2.1-gemma3-4b` |
| Format | ไม่ต้องตั้ง — ใช้ Enable Structured Output แทน |
| Temperature | `0.1` |
| Enable Structured Output | `ON` |
| Tool Model Enabled | `ON` (เห็นใน screenshot) |
| Setting | ค่า |
| ------------------------ | ----------------------------------------------------- |
| Ollama API URL | `http://localhost:11434` (ถ้ารันบน WSL ไม่ต้องใส่ IP) |
| Model Name | `scb10x/typhoon2.1-gemma3-4b` |
| Format | ไม่ต้องตั้ง — ใช้ Enable Structured Output แทน |
| Temperature | `0.1` |
| Enable Structured Output | `ON` |
| Tool Model Enabled | `ON` (เห็นใน screenshot) |
**การเชื่อมต่อ:**
- Input `Input``Prompt Template → Prompt`
- Input `System Message` ← ปล่อยว่าง (System Prompt อยู่ใน Prompt Template แล้ว)
- Output `Text` → Input ของ Custom Code (Node 6)
@@ -655,46 +670,46 @@ from pathlib import Path
class WriteJsonIdempotent(Component):
display_name = "Write JSON (Idempotent)"
description = "Writes JSON to staging_ai dynamically based on loop item filename"
inputs = [
StrInput(name="json_content", display_name="JSON Content"),
DataInput(name="loop_item", display_name="Loop Item (PDF)"),
]
outputs = [
Output(display_name="Result Path", name="result_path", method="write_file")
]
def write_file(self) -> Data:
# Extract filename from loop_item
pdf_path = self.loop_item.data.get("file_path", "")
if not pdf_path:
return Data(data={"error": "No file_path in loop item"})
base_name = Path(pdf_path).stem
out_dir = Path("/data/staging_ai/rag-output")
out_dir.mkdir(parents=True, exist_ok=True)
json_path = out_dir / f"{base_name}.json"
# Idempotency check
if json_path.exists():
return Data(data={"status": "skipped", "path": str(json_path), "reason": "already exists"})
# Parse and write content to ensure it's valid JSON before saving
try:
parsed = json.loads(self.json_content)
# Inject source file name if missing
if not parsed.get("source_file"):
parsed["source_file"] = f"{base_name}.pdf"
tmp_path = out_dir / f"{base_name}.tmp"
with open(tmp_path, "w", encoding="utf-8") as f:
json.dump(parsed, f, ensure_ascii=False, indent=2)
# Atomic rename
os.replace(tmp_path, json_path)
return Data(data={"status": "written", "path": str(json_path)})
except Exception as e:
err_path = out_dir / f"{base_name}.error"
@@ -704,6 +719,7 @@ class WriteJsonIdempotent(Component):
```
**การเชื่อมต่อ:**
- Input `json_content``Ollama → Text`
- Input `loop_item``Loop → Item`
- Output `result_path``Loop → item` (Feedback loop กลับไปบอกว่ารอบนี้จบแล้ว)
@@ -716,27 +732,26 @@ class WriteJsonIdempotent(Component):
#### สรุปการ Wire ทั้ง Workflow
| From | Port | To | Port |
|------|------|----|------|
| Read File | Files | Loop | Inputs |
| Loop | Item | Parser | Data or DataFrame |
| Parser | Parsed Text | Prompt Template | extracted_text |
| Prompt Template | Prompt | Ollama | input_value (Input) |
| Ollama | Text | Write JSON (Idempotent) | json_content |
| Loop | Item | Write JSON (Idempotent) | loop_item |
| Write JSON | result_path | Loop | element |
| From | Port | To | Port |
| --------------- | ----------- | ----------------------- | ------------------- |
| Read File | Files | Loop | Inputs |
| Loop | Item | Parser | Data or DataFrame |
| Parser | Parsed Text | Prompt Template | extracted_text |
| Prompt Template | Prompt | Ollama | input_value (Input) |
| Ollama | Text | Write JSON (Idempotent) | json_content |
| Loop | Item | Write JSON (Idempotent) | loop_item |
| Write JSON | result_path | Loop | element |
**ตั้งค่า Ollama LLM Component:**
| ฟิลด์ | ค่า |
|-------|-----|
| Model Name | `scb10x/typhoon2.1-gemma3-4b` |
| Base URL | `http://192.168.20.100:11434` |
| Format | `json` (บังคับ JSON Output) |
| Temperature | `0.1` (ลด Hallucination) |
| Max Tokens | `2048` |
| Enable Structured Output | `ON` |
| ฟิลด์ | ค่า |
| ------------------------ | ----------------------------- |
| Model Name | `scb10x/typhoon2.1-gemma3-4b` |
| Base URL | `http://192.168.20.100:11434` |
| Format | `json` (บังคับ JSON Output) |
| Temperature | `0.1` (ลด Hallucination) |
| Max Tokens | `2048` |
| Enable Structured Output | `ON` |
> ℹ️ **เหตุผลที่เลือก Typhoon 2.1:**
> `scb10x/typhoon2.1-gemma3-4b` โดย SCB10X เป็น Model ที่ออกแบบมาสำหรับภาษาไทยโดยเฉพาะ
@@ -804,7 +819,7 @@ services:
volumes:
# staging_ai mount จาก NAS
# Windows R:\ drive จะปรากฏใน WSL เป็น /mnt/r/
- /mnt/r/staging_ai:/data/staging_ai # ← Read PDF + Write rag-output/
- /mnt/r/staging_ai:/data/staging_ai # ← Read PDF + Write rag-output/
# หมายเหตุ: ต้องเขียนได้ที่ rag-output/ จึงไม่ใส่ :ro
opensearch:
@@ -812,6 +827,7 @@ services:
```
> ⚠️ **ตรวจสอบ R:\ ใน WSL:**
>
> ```bash
> # ใน WSL Terminal ตรวจว่า mount อยู่ที่ไหน
> ls /mnt/r/staging_ai/
@@ -819,6 +835,7 @@ services:
> ```
>
> ✅ **สร้าง rag-output/ ก่อนรัน:**
>
> ```bash
> mkdir -p /mnt/r/staging_ai/rag-output
> ```
@@ -851,12 +868,12 @@ ls /mnt/r/staging_ai/rag-output/
สร้าง Test Workflow ใน n8n:
| Node | Type | Config |
|------|------|--------|
| Trigger | Manual | - |
| List Files | Read/Write Files from Disk | Path: `staging_ai/rag-output/*.json` |
| Read File | Read/Write Files from Disk | Dynamic path จาก List node |
| Parse JSON | Code | `JSON.parse(items[0].binary.data.toString())` |
| Node | Type | Config |
| ---------- | -------------------------- | --------------------------------------------- |
| Trigger | Manual | - |
| List Files | Read/Write Files from Disk | Path: `staging_ai/rag-output/*.json` |
| Read File | Read/Write Files from Disk | Dynamic path จาก List node |
| Parse JSON | Code | `JSON.parse(items[0].binary.data.toString())` |
```bash
# ตรวจสอบ path ใน n8n container
@@ -865,6 +882,7 @@ docker exec n8n ls /home/node/.n8n/staging_ai/rag-output/
```
> 💡 **Path Mapping:**
>
> - Admin Desktop (WSL): `/mnt/r/staging_ai/rag-output/`
> - n8n บน QNAP: `staging_ai/rag-output/` (ตาม Volume Mount ใน docker-compose)
@@ -872,18 +890,18 @@ docker exec n8n ls /home/node/.n8n/staging_ai/rag-output/
### ขั้นตอนที่ 10: Pre-Production Verification
| ลำดับ | รายการ | วิธีตรวจสอบ |
|-------|--------|-------------|
| 1 | Ollama เชื่อมต่อได้ | `curl http://192.168.20.100:11434/api/tags` จาก WSL |
| 2 | `nomic-embed-text` พร้อม | `ollama list` บน Windows Terminal |
| 3 | Langflow รันได้ | เปิด `http://localhost:7860` |
| 4 | R:\ mount เห็น PDF | `ls /mnt/r/staging_ai/*.pdf` ใน WSL |
| 5 | Langflow เขียน rag-output/ ได้ | ดู `/mnt/r/staging_ai/rag-output/` หลังรัน Test |
| 6 | ไม่มี DB Credentials ใน env | ตรวจ `~/.openrag/tui/docker-compose.yml` |
| 7 | Extraction ถูกต้อง ≥ 85% | รัน Batch กับเอกสาร 20 ฉบับ นับ field ที่ถูก |
| 8 | JSON ถูกต้อง (valid JSON) | `python3 -m json.tool rag-output/test.json` |
| 9 | n8n อ่าน JSON จาก NAS ได้ | รัน Test Workflow ใน n8n ดู Execution Log |
| 10 | GPU VRAM < 7.5GB ระหว่างรัน | `nvidia-smi --query-gpu=memory.used --format=csv` |
| ลำดับ | รายการ | วิธีตรวจสอบ |
| ----- | ------------------------------ | --------------------------------------------------- |
| 1 | Ollama เชื่อมต่อได้ | `curl http://192.168.20.100:11434/api/tags` จาก WSL |
| 2 | `nomic-embed-text` พร้อม | `ollama list` บน Windows Terminal |
| 3 | Langflow รันได้ | เปิด `http://localhost:7860` |
| 4 | R:\ mount เห็น PDF | `ls /mnt/r/staging_ai/*.pdf` ใน WSL |
| 5 | Langflow เขียน rag-output/ ได้ | ดู `/mnt/r/staging_ai/rag-output/` หลังรัน Test |
| 6 | ไม่มี DB Credentials ใน env | ตรวจ `~/.openrag/tui/docker-compose.yml` |
| 7 | Extraction ถูกต้อง ≥ 85% | รัน Batch กับเอกสาร 20 ฉบับ นับ field ที่ถูก |
| 8 | JSON ถูกต้อง (valid JSON) | `python3 -m json.tool rag-output/test.json` |
| 9 | n8n อ่าน JSON จาก NAS ได้ | รัน Test Workflow ใน n8n ดู Execution Log |
| 10 | GPU VRAM < 7.5GB ระหว่างรัน | `nvidia-smi --query-gpu=memory.used --format=csv` |
```bash
# ตรวจสอบ VRAM บน Admin Desktop (Windows Terminal)
@@ -898,6 +916,7 @@ nvidia-smi --query-gpu=memory.used,memory.total --format=csv
> ต้องผ่าน Go-Live Gate ของ Migration (ADR-017) ก่อนเริ่มพัฒนา
**OpenRAG Setup (Admin Desktop):**
- [ ] WSL 2 + Docker Desktop ติดตั้งเสร็จ (ขั้นตอนที่ 1)
- [ ] OpenRAG ติดตั้งผ่าน `uvx --python 3.13 openrag` (ขั้นตอนที่ 23)
- [ ] Ollama เชื่อมต่อจาก Docker Container ได้ (ขั้นตอนที่ 5)
@@ -910,6 +929,7 @@ nvidia-smi --query-gpu=memory.used,memory.total --format=csv
- [ ] ยืนยัน OpenRAG ไม่มี DB Credentials ใน docker-compose.yml
**n8n File-based Queue Integration:**
- [ ] ตรวจสอบ n8n Volume Mount เห็น `staging_ai/rag-output/` (ขั้นตอนที่ 9)
- [ ] สร้าง n8n Schedule Workflow: List JSON Files → Loop → Read → Validate → Route
- [ ] ทดสอบ Rename ไฟล์ `.json``.done` / `.error` ใน n8n
@@ -917,6 +937,7 @@ nvidia-smi --query-gpu=memory.used,memory.total --format=csv
- [ ] ทดสอบ Idempotency-Key กรณีรัน n8n ซ้ำ (ไฟล์ `.done` ไม่ถูก Process ซ้ำ)
**Search & Query (Post-Migration):**
- [ ] Migration v1.8.x เสร็จสมบูรณ์และ Stable (Prerequisite)
- [ ] กำหนด Elasticsearch Index Schema + Dims (lock ก่อน Index แรก)
- [ ] ออกแบบ RBAC Filter สำหรับ kNN Search
@@ -927,5 +948,5 @@ nvidia-smi --query-gpu=memory.used,memory.total --format=csv
---
*เอกสารนี้เป็น Living Document — อัปเดตเมื่อมีการตัดสินใจ Architecture ใหม่*
_เอกสารนี้เป็น Living Document — อัปเดตเมื่อมีการตัดสินใจ Architecture ใหม่_
**Version:** 1.8.1 | **Author:** Development Team | **Last Updated:** 2026-03-13