From 844caf477de0ec27ca82ea86a92cdb35fb8de4f8 Mon Sep 17 00:00:00 2001 From: admin Date: Sun, 19 Apr 2026 11:09:35 +0700 Subject: [PATCH] 690419:1109 feat: update CI/CD to use SSH key authentication #02 --- .../LCBP3_RAG_Implementation_Guide.docx | Bin 20347 -> 0 bytes .../LCBP3-RAG-Implementation-Guide-v1.0.0.md | 544 ++++++++++++++++++ .../LCBP3-RAG-Implementation-Guide-v1.1.0.md} | 0 .../LCBP3-RAG-Implementation-Guide-v1.1.1.md} | 0 4 files changed, 544 insertions(+) delete mode 100644 specs/06-Decision-Records/ADR-022-/LCBP3_RAG_Implementation_Guide.docx create mode 100644 specs/06-Decision-Records/ADR-022-Retrieval-Augmented-Generation/LCBP3-RAG-Implementation-Guide-v1.0.0.md rename specs/06-Decision-Records/{ADR-022-/LCBP3 RAG Implementation Guide v1.1.0.md => ADR-022-Retrieval-Augmented-Generation/LCBP3-RAG-Implementation-Guide-v1.1.0.md} (100%) rename specs/06-Decision-Records/{ADR-022-/LCBP3 RAG Implementation Guide v1.1.1.md => ADR-022-Retrieval-Augmented-Generation/LCBP3-RAG-Implementation-Guide-v1.1.1.md} (100%) diff --git a/specs/06-Decision-Records/ADR-022-/LCBP3_RAG_Implementation_Guide.docx b/specs/06-Decision-Records/ADR-022-/LCBP3_RAG_Implementation_Guide.docx deleted file mode 100644 index 5f5da2198e91c6bc7d6a1f173c4c05be5e2e1480..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20347 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 diff --git a/specs/06-Decision-Records/ADR-022-Retrieval-Augmented-Generation/LCBP3-RAG-Implementation-Guide-v1.0.0.md b/specs/06-Decision-Records/ADR-022-Retrieval-Augmented-Generation/LCBP3-RAG-Implementation-Guide-v1.0.0.md new file mode 100644 index 0000000..14f836a --- /dev/null +++ b/specs/06-Decision-Records/ADR-022-Retrieval-Augmented-Generation/LCBP3-RAG-Implementation-Guide-v1.0.0.md @@ -0,0 +1,544 @@ + +**LCBP3 DMS** + +**RAG Implementation Guide** + +Retrieval-Augmented Generation for Document Management System + +| Version | 1.0.0 | +| :---- | :---- | +| **Project** | Laem Chabang Basin Phase 3 | +| **Stack** | NestJS \+ Next.js \+ MariaDB \+ Qdrant | +| **LLM** | Typhoon API \+ Ollama (nomic-embed-text) | + +# **1\. ภาพรวม Architecture** + +ระบบ RAG ของ LCBP3 DMS ออกแบบให้ทำงานแบบ Hybrid โดยแยกหน้าที่ชัดเจนระหว่าง embedding (local) และ generation (cloud Thai LLM) เพื่อให้ได้ทั้งความเป็นส่วนตัวของข้อมูลในส่วน embedding และคุณภาพภาษาไทยที่ดีในส่วนการตอบคำถาม + +## **1.1 Hybrid Architecture** + +| Flow: PDF/DOCX Upload → OCR (EasyOCR/Tesseract) → Text Chunking → Embedding (Ollama: nomic-embed-text) → Qdrant Vector Store RAG Query → Embed Question → Qdrant Similarity Search → Build Prompt → Typhoon API → Thai Answer | +| :---- | + +| Component | เครื่องมือ | ที่อยู่ | หมายเหตุ | +| :---- | :---- | :---- | :---- | +| Document Metadata | MariaDB | เดิม | ไม่เปลี่ยน | +| Vector Store | Qdrant | Docker container | เพิ่มใหม่ | +| Embedding Model | nomic-embed-text | Ollama local | 768 dimensions | +| LLM / Generation | Typhoon API | api.opentyphoon.ai | Thai-first, OpenAI-compatible | +| OCR | EasyOCR / Tesseract | Docker microservice | รองรับภาษาไทย | +| Async Queue | BullMQ \+ Redis | Docker container | async ingestion | + +# **2\. Chunking Strategy ตาม Document Type** + +แต่ละประเภทเอกสารใน LCBP3 มีโครงสร้างต่างกัน จึงไม่ควรใช้ fixed-size chunking เดียวกันทั้งหมด + +| Document Type | Strategy | Chunk Size | Overlap | +| :---- | :---- | :---- | :---- | +| CORR, MOM | Paragraph-based | \~500 tokens | 50 tokens | +| RFI, NCR | Section-based (Q\&A) | \~300 tokens | 30 tokens | +| DRAW, SUB | Metadata-heavy | \~200 tokens | 0 tokens | +| CONTRACT, INVOICE | Table-aware | \~400 tokens | 40 tokens | +| RPT | Sliding window | \~600 tokens | 100 tokens | +| TRANS | Header \+ body split | \~350 tokens | 35 tokens | + +| หมายเหตุ nomic-embed-text รองรับ input สูงสุด 8192 tokens แต่ chunk ที่เล็กกว่าให้ precision ดีกว่าในงาน retrieval สำหรับเอกสารที่มีตาราง (CONTRACT, INVOICE) ให้ extract ตารางแยกก่อน แล้ว serialize เป็น text | +| :---- | + +# **3\. ขั้นตอนการ Implement โดยละเอียด** + +| 1 | ติดตั้ง Qdrant และ Redis ผ่าน Docker Compose เพิ่ม services ใน docker-compose.yml ที่มีอยู่แล้ว | +| :---: | :---- | + +**เพิ่มใน docker-compose.yml:** + + qdrant: + + image: qdrant/qdrant:latest + + container\_name: lcbp3-qdrant + + ports: + + \- "6333:6333" + + volumes: + + \- qdrant\_data:/qdrant/storage + + restart: unless-stopped + + redis: + + image: redis:7-alpine + + container\_name: lcbp3-redis + + ports: + + \- "6379:6379" + + volumes: + + \- redis\_data:/data + + restart: unless-stopped + +ทดสอบว่า Qdrant ทำงาน: เปิด browser ไปที่ http://localhost:6333/dashboard + +| 2 | เพิ่ม Table document\_chunks ใน MariaDB เก็บ reference ระหว่าง MariaDB กับ Qdrant point ID | +| :---: | :---- | + +CREATE TABLE document\_chunks ( + + id CHAR(36) PRIMARY KEY, \-- UUID \= Qdrant point ID + + document\_id CHAR(36) NOT NULL, + + chunk\_index INT NOT NULL, + + content TEXT NOT NULL, + + created\_at DATETIME DEFAULT CURRENT\_TIMESTAMP, + + FOREIGN KEY (document\_id) + + REFERENCES documents(id) ON DELETE CASCADE + +); + +| 3 | ติดตั้ง Dependencies ใน NestJS ติดตั้ง packages ที่จำเป็นทั้งหมด | +| :---: | :---- | + +\# ใน packages/api (NestJS) + +pnpm add @qdrant/js-client-rest + +pnpm add openai + +pnpm add bullmq + +pnpm add @nestjs/bull bull + +pnpm add uuid + +pnpm add \-D @types/uuid + +| 4 | สร้าง RAG Module Structure จัดโครงสร้างไฟล์ใน NestJS | +| :---: | :---- | + +**โครงสร้างไฟล์ที่แนะนำ:** + +src/rag/ + + rag.module.ts \<- register all providers + + rag.controller.ts \<- POST /api/rag/query + + rag.service.ts \<- orchestrate pipeline + + embedding.service.ts \<- call Ollama nomic-embed-text + + qdrant.service.ts \<- Qdrant CRUD operations + + chunker.service.ts \<- smart chunking per doc type + + llm.service.ts \<- call Typhoon API + + ingestion.processor.ts \<- BullMQ worker + +| 5 | สร้าง EmbeddingService เรียก Ollama nomic-embed-text สำหรับแปลงข้อความเป็น vector | +| :---: | :---- | + +// embedding.service.ts + +@Injectable() + +export class EmbeddingService { + + private readonly OLLAMA\_URL \= process.env.OLLAMA\_URL + + ?? "http://localhost:11434"; + + async embed(text: string): Promise\ { + + const res \= await fetch(\`${this.OLLAMA\_URL}/api/embeddings\`, { + + method: "POST", + + headers: { "Content-Type": "application/json" }, + + body: JSON.stringify({ + + model: "nomic-embed-text", + + prompt: text, + + }), + + }); + + if (\!res.ok) throw new Error(\`Embedding failed: ${res.status}\`); + + const { embedding } \= await res.json(); + + return embedding; + + } + +} + +| 6 | สร้าง QdrantService จัดการ vector store สำหรับ upsert, search, delete | +| :---: | :---- | + +// qdrant.service.ts + +@Injectable() + +export class QdrantService implements OnModuleInit { + + private client: QdrantClient; + + private readonly COLLECTION \= "lcbp3\_docs"; + + async onModuleInit() { + + this.client \= new QdrantClient({ + + url: process.env.QDRANT\_URL ?? "http://localhost:6333", + + }); + + await this.ensureCollection(); + + } + + private async ensureCollection() { + + const { collections } \= await this.client.getCollections(); + + const exists \= collections.some(c \=\> c.name \=== this.COLLECTION); + + if (\!exists) { + + await this.client.createCollection(this.COLLECTION, { + + vectors: { size: 768, distance: "Cosine" }, + + }); + + } + + } + + async upsert(id: string, vector: number\[\], payload: Record\) { + + await this.client.upsert(this.COLLECTION, { + + points: \[{ id, vector, payload }\], + + }); + + } + + async search(vector: number\[\], topK \= 5, filter?: Record\) { + + return this.client.search(this.COLLECTION, { + + vector, + + limit: topK, + + filter: filter ? { + + must: Object.entries(filter).map((\[key, value\]) \=\> ({ + + key, match: { value }, + + })), + + } : undefined, + + with\_payload: true, + + }); + + } + + async deleteByDocumentId(documentId: string) { + + await this.client.delete(this.COLLECTION, { + + filter: { must: \[{ key: "document\_id", match: { value: documentId } }\] }, + + }); + + } + +} + +| 7 | สร้าง LlmService (Typhoon API) เรียก Typhoon สำหรับ generate คำตอบ | +| :---: | :---- | + +// llm.service.ts + +import OpenAI from "openai"; + +@Injectable() + +export class LlmService { + + private client: OpenAI; + + constructor() { + + this.client \= new OpenAI({ + + apiKey: process.env.TYPHOON\_API\_KEY, + + baseURL: "https://api.opentyphoon.ai/v1", + + }); + + } + + async generate(prompt: string, system?: string): Promise\ { + + const response \= await this.client.chat.completions.create({ + + model: "typhoon-v2.1-12b-instruct", + + messages: \[ + + { role: "system", content: system ?? LCBP3\_SYSTEM\_PROMPT }, + + { role: "user", content: prompt }, + + \], + + max\_tokens: 1024, + + temperature: 0.3, + + top\_p: 0.95, + + repetition\_penalty: 1.05, + + }); + + return response.choices\[0\].message.content ?? ""; + + } + +} + +**System Prompt สำหรับ LCBP3:** + +const LCBP3\_SYSTEM\_PROMPT \= \` + +You are a document assistant for LCBP3 (Laem Chabang Basin Phase 3), + +a large-scale construction project DMS. + +\- Answer in Thai if the question is in Thai + +\- Answer in English if the question is in English + +\- Always reference document numbers (e.g., CORR-LCBP3-2024-001) + +\- Be concise and factual. Do not speculate beyond the provided context. + +\- If context is insufficient, say so clearly. + +\`; + +| 8 | สร้าง ChunkerService แบ่ง text ตาม strategy ของแต่ละ document type | +| :---: | :---- | + +// chunker.service.ts + +@Injectable() + +export class ChunkerService { + + chunk(text: string, docType: string): { text: string }\[\] { + + const strategy \= this.getStrategy(docType); + + return strategy(text); + + } + + private getStrategy(docType: string) { + + const map: Record\ { text: string }\[\]\> \= { + + CORR: (t) \=\> this.paragraphChunk(t, 500, 50), + + MOM: (t) \=\> this.paragraphChunk(t, 500, 50), + + RFI: (t) \=\> this.sectionChunk(t, 300, 30), + + NCR: (t) \=\> this.sectionChunk(t, 300, 30), + + RPT: (t) \=\> this.slidingWindow(t, 600, 100), + + CONTRACT: (t) \=\> this.tableAware(t, 400, 40), + + INVOICE: (t) \=\> this.tableAware(t, 400, 40), + + }; + + return map\[docType\] ?? ((t) \=\> this.slidingWindow(t, 400, 50)); + + } + + // ... implement paragraphChunk, sectionChunk, slidingWindow, tableAware + +} + +| 9 | สร้าง Ingestion Pipeline เชื่อม upload event กับ vector indexing ผ่าน BullMQ queue | +| :---: | :---- | + +// rag.service.ts — triggerIngestion + +async ingestDocument(documentId: string) { + + const doc \= await this.documentsService.findOne(documentId); + + const rawText \= await this.extractText(doc.filePath, doc.docType); + + const chunks \= this.chunker.chunk(rawText, doc.docType); + + // ลบ chunks เก่าก่อน (กรณี re-ingest) + + await this.qdrant.deleteByDocumentId(documentId); + + await this.chunkRepo.delete({ documentId }); + + for (const \[i, chunk\] of chunks.entries()) { + + const chunkId \= uuidv4(); + + const embedding \= await this.embedding.embed(chunk.text); + + await this.qdrant.upsert(chunkId, embedding, { + + document\_id: documentId, + + doc\_type: doc.docType, + + doc\_number: doc.docNumber, + + revision: doc.revision, + + project\_code: "LCBP3", + + chunk\_index: i, + + }); + + await this.chunkRepo.save({ + + id: chunkId, documentId, + + chunkIndex: i, content: chunk.text, + + }); + + } + +} + +**เรียก triggerIngestion() หลัง upload สำเร็จ:** + +// documents.service.ts — หลัง save document + +await this.ragQueue.add("ingest", { documentId: doc.id }); + +| 10 | สร้าง RAG Query API Endpoint สำหรับ frontend เรียกใช้ | +| :---: | :---- | + +// rag.service.ts — query + +async query(question: string, filter?: { doc\_type?: string }) { + + // 1\. Embed คำถาม + + const qVector \= await this.embedding.embed(question); + + // 2\. Retrieve top-5 chunks จาก Qdrant + + const hits \= await this.qdrant.search(qVector, 5, filter); + + // 3\. Build context + + const context \= hits.map(h \=\> + + \`\[${h.payload.doc\_type} \- ${h.payload.doc\_number}\]\\n${h.payload.content}\` + + ).join("\\n\\n---\\n\\n"); + + // 4\. Generate คำตอบผ่าน Typhoon + + const prompt \= \`Context:\\n${context}\\n\\nQuestion: ${question}\`; + + return this.llm.generate(prompt); + +} + +// rag.controller.ts + +@Post("query") + +async query(@Body() dto: { question: string; doc\_type?: string }) { + + return this.ragService.query(dto.question, { doc\_type: dto.doc\_type }); + +} + +# **4\. Environment Variables** + +เพิ่มใน .env ของ NestJS: + +\# Typhoon API + +TYPHOON\_API\_KEY=your\_typhoon\_api\_key\_here + +\# Ollama (local) + +OLLAMA\_URL=http://localhost:11434 + +\# Qdrant + +QDRANT\_URL=http://localhost:6333 + +\# Redis (BullMQ) + +REDIS\_HOST=localhost + +REDIS\_PORT=6379 + +# **5\. ลำดับการ Rollout** + +แนะนำให้ implement เป็น phase เพื่อลดความเสี่ยง: + +| Phase | สิ่งที่ทำ | ผลลัพธ์ | +| :---- | :---- | :---- | +| Phase 1(1-2 วัน) | ติดตั้ง Qdrant \+ Redis, สร้าง DB table, ติดตั้ง packages | Infrastructure พร้อม | +| Phase 2(2-3 วัน) | สร้าง EmbeddingService \+ QdrantService \+ LlmService, ทดสอบแต่ละ service แยกกัน | Core services ทำงาน | +| Phase 3(2-3 วัน) | สร้าง ChunkerService \+ Ingestion Pipeline เริ่มจาก CORR และ RFI ก่อน | Indexing ทำงานอัตโนมัติ | +| Phase 4(1-2 วัน) | สร้าง RAG Query API \+ เชื่อมกับ NestJS route | Query API พร้อม | +| Phase 5(2-3 วัน) | สร้าง UI ใน Next.js: Search bar \+ Answer panel พร้อม source citation | ใช้งานได้จริง | + +# **6\. ข้อควรพิจารณาสำคัญ** + +| ⚠️ ความปลอดภัยของข้อมูล (สำคัญมาก) เนื้อหาของเอกสารจะถูกส่งไปยัง Typhoon API (cloud) เพื่อ generate คำตอบ แนะนำให้ตรวจสอบกับทีม PM / Security ว่าเอกสารชั้น Confidential สามารถส่งออกนอกได้หรือไม่ ทางเลือก: ใช้ Ollama local LLM แทน Typhoon สำหรับเอกสาร Confidential | +| :---- | + +| ℹ️ Rate Limit ของ Typhoon Free Tier อยู่ที่ 5 req/s และ 200 req/m — เพียงพอสำหรับ internal DMS หากมีผู้ใช้หลายคน query พร้อมกัน อาจต้องพิจารณา upgrade เป็น Together.ai plan | +| :---- | + +| ℹ️ Embedding ยังต้องใช้ Ollama Typhoon API ยังไม่มี embedding endpoint nomic-embed-text รันบน Ollama local ทำให้ข้อมูลที่ embed ไม่ออกนอกเครือข่าย | +| :---- | + diff --git a/specs/06-Decision-Records/ADR-022-/LCBP3 RAG Implementation Guide v1.1.0.md b/specs/06-Decision-Records/ADR-022-Retrieval-Augmented-Generation/LCBP3-RAG-Implementation-Guide-v1.1.0.md similarity index 100% rename from specs/06-Decision-Records/ADR-022-/LCBP3 RAG Implementation Guide v1.1.0.md rename to specs/06-Decision-Records/ADR-022-Retrieval-Augmented-Generation/LCBP3-RAG-Implementation-Guide-v1.1.0.md diff --git a/specs/06-Decision-Records/ADR-022-/LCBP3 RAG Implementation Guide v1.1.1.md b/specs/06-Decision-Records/ADR-022-Retrieval-Augmented-Generation/LCBP3-RAG-Implementation-Guide-v1.1.1.md similarity index 100% rename from specs/06-Decision-Records/ADR-022-/LCBP3 RAG Implementation Guide v1.1.1.md rename to specs/06-Decision-Records/ADR-022-Retrieval-Augmented-Generation/LCBP3-RAG-Implementation-Guide-v1.1.1.md