14 KiB
Feature Specification: RAG Pipeline Enhancements
Feature Branch: 234-rag-pipeline-enhancements
Created: 2026-06-05
Status: Draft
ADR Reference: ADR-035 (AI Pipeline Flow Architecture)
User Scenarios & Testing (mandatory)
User Story 1 — Document Q&A with Accurate Context (Priority: P1)
ผู้ใช้งานใน Project ต้องการถามคำถามเกี่ยวกับเนื้อหาของเอกสาร Correspondence/RFA ที่อยู่ในระหว่างดำเนินการ (IN_REVIEW ขึ้นไป) โดยระบบจะค้นหาข้อความที่เกี่ยวข้องจากเอกสารทั้งหมดใน Project เดียวกัน แล้วตอบคำถามพร้อมระบุเลขเอกสารและวันที่อ้างอิง
Why this priority: RAG Q&A คือ core value ของ AI integration — ถ้าไม่มี embedding และ retrieval ที่ถูกต้อง Chat Q&A ทำงานไม่ได้
Independent Test: สร้างเอกสาร 2 ฉบับในโปรเจกต์เดียวกัน → submit workflow (IN_REVIEW) → ถามคำถามที่เนื้อหาอยู่ในเอกสาร → ระบบตอบได้พร้อมอ้างอิงเลขเอกสาร
Acceptance Scenarios:
- Given เอกสาร Correspondence ที่ status = IN_REVIEW ใน Project A, When User ถามคำถามที่เนื้อหาอยู่ในเอกสารนั้น, Then ระบบตอบได้พร้อมระบุเลขเอกสารและวันที่อ้างอิงภายใน 30 วินาที
- Given User ใน Project A ถามคำถาม, When เนื้อหาที่เกี่ยวข้องอยู่ใน Project B, Then ระบบต้องไม่ดึงข้อมูลจาก Project B มาตอบ (project isolation)
- Given เอกสารที่ยัง DRAFT (ยังไม่ submit), When User ถามคำถาม, Then ระบบต้องไม่นำเนื้อหา DRAFT มาตอบ
User Story 2 — Automatic RAG Preparation on Workflow Submit (Priority: P2)
เมื่อผู้ใช้ submit เอกสาร Correspondence/RFA ผ่าน Workflow Engine (status เปลี่ยนจาก DRAFT เป็น IN_REVIEW/SUBOWN) ระบบจะเตรียม RAG embedding อัตโนมัติในพื้นหลัง โดยไม่กระทบ response time ของการ submit
Why this priority: ถ้าไม่มี auto-trigger embedding User Story 1 จะไม่มีข้อมูลให้ค้นหา
Independent Test: Submit เอกสาร → ตรวจสอบว่า job rag-prepare ถูก enqueue ใน BullMQ → รอ job เสร็จ → verify Qdrant มี points ของเอกสารนั้น
Acceptance Scenarios:
- Given เอกสาร status = DRAFT, When User กด Submit workflow, Then ระบบ enqueue
rag-preparejob ใน BullMQai-batchภายใน 1 วินาที โดยไม่ block response - Given
rag-preparejob ทำงาน, When เสร็จสมบูรณ์, Then Qdrant มี chunks ของเอกสารนั้นพร้อมproject_public_id,doc_number,status_code,chunk_topicใน payload - Given เอกสารมี Revision ใหม่ถูก submit, When
rag-prepareทำงาน, Then Qdrant ลบ points เก่าของ revision ก่อนหน้าก่อน upsert points ใหม่
User Story 3 — Semantic Chunking for Better Retrieval (Priority: P2)
เนื้อหาเอกสารแต่ละฉบับถูกแบ่ง chunk ตามความหมาย (Semantic Chunking) โดย LLM ใส่ <chunk topic="..."> tag ก่อน embed แทนการแบ่งแบบ fixed-size ทำให้ค้นหาได้ตรงหัวข้อมากขึ้น
Why this priority: Semantic chunking ช่วยให้ retrieval accuracy สูงกว่า fixed-size chunking อย่างมีนัยสำคัญ โดยเฉพาะกับเอกสารภาษาไทยที่มีโครงสร้างหัวข้อชัดเจน
Independent Test: embed เอกสารที่มีหลายหัวข้อ → ตรวจสอบ Qdrant payload ว่า chunk_topic แต่ละ chunk ตรงกับเนื้อหา → ถามคำถามเฉพาะหัวข้อ → ตรวจสอบว่า reranked chunk ตรงหัวข้อ
Acceptance Scenarios:
- Given OCR text ของเอกสาร, When
rag-preparejob ทำงาน, Then typhoon2.5-np-dms แบ่ง text ออกเป็น chunks พร้อม<chunk topic="...">tag อย่างน้อย 1 chunk - Given Semantic chunks ถูกสร้าง, When upsert ไป Qdrant, Then แต่ละ point มี
chunk_topicที่อธิบายหัวข้อของ chunk นั้น - Given ไม่พบ
<chunk>tag ใน LLM output, When fallback triggered, Then ระบบ fallback ไปใช้ fixed-size chunking (512 chars) แทนโดยไม่ error
User Story 4 — Hybrid Search with Reranking (Priority: P3)
ระบบใช้ BGE-M3 embedding (Dense 1024 dims + Sparse vectors) และ BGE-Reranker-Large เพื่อให้ retrieval accuracy สูงกว่า dense-only search โดยเฉพาะกับ keyword-heavy queries ภาษาไทย
Why this priority: ปรับปรุง search quality — มี impact แต่ระบบทำงานได้ก่อนจะ implement หากยังใช้ dense-only ก่อน
Independent Test: ส่ง query ที่มีทั้ง semantic และ keyword → ตรวจสอบว่า reranked top-5 มีความเกี่ยวข้องสูงกว่า top-5 จาก dense-only
Acceptance Scenarios:
- Given User query, When ระบบ embed ด้วย BGE-M3, Then ได้ทั้ง dense vector (1024 dims) และ sparse vector
- Given BGE-M3 vectors, When ค้นหาใน Qdrant, Then ใช้ Hybrid search (dense + sparse) ได้ top-15 candidates
- Given top-15 candidates, When ส่งไป BGE-Reranker-Large, Then ได้ top 3-5 chunks ที่ reranked score สูงสุดส่งให้ LLM
Edge Cases
- เอกสารที่ไม่มี attachment (ไม่มีไฟล์ PDF) →
rag-prepareข้ามการ embed โดยไม่ error แต่ log warning - OCR text ว่างเปล่า / สั้นเกินไป (< 50 chars) → ข้าม semantic chunking + embedding
- BGE-M3 Sidecar ไม่พร้อม →
rag-preparejob fail + retry 3 ครั้ง (ADR-008) - Qdrant ไม่พร้อม →
rag-preparejob fail + retry - เอกสาร REJECTED กลับเป็น DRAFT → ไม่ trigger
rag-prepareซ้ำ (status ลดลง ไม่ใช่ขึ้น) - หลาย users submit พร้อมกัน → idempotency key ป้องกัน duplicate
rag-preparejobs
Requirements (mandatory)
Functional Requirements
OCR Sidecar (app.py)
- FR-001: Sidecar MUST expose
POST /embedendpoint รับ{"text": string}และคืน{"dense": number[], "sparse": {indices: number[], values: number[]}} - FR-002: Sidecar MUST expose
POST /rerankendpoint รับ{"query": string, "chunks": string[]}และคืน scores เรียงลำดับ - FR-003: BGE-M3 และ BGE-Reranker-Large MUST โหลดบน CPU RAM เมื่อ Sidecar start (ไม่ใช้ VRAM)
Semantic Chunking
- FR-004: ระบบ MUST ใช้ typhoon2.5-np-dms วิเคราะห์ OCR text และใส่
<chunk topic="...">tag ก่อน embed โดยดึง prompt จากai_promptsที่prompt_type = 'rag_chunking'(ADR-029) - FR-004a: ระบบ MUST seed
ai_promptsrecord สำหรับprompt_type = 'rag_chunking'ผ่าน SQL delta (ADR-009) พร้อม placeholder{{ocr_text}} - FR-005: ระบบ MUST fallback ไปใช้ fixed-size chunking (512 chars / 64 overlap) หากไม่พบ
<chunk>tag ใน LLM output - FR-006: chunk topic MUST บันทึกไว้ใน Qdrant payload field
chunk_topic
Qdrant Collection
- FR-007: Qdrant collection
lcbp3_vectorsMUST ถูก drop + recreate ใหม่รองรับ Hybrid search (Dense 1024 dims + Sparse SPLADE) — collection เดิม (768 dims dense-only) จะถูกแทนที่ทันทีที่ feature นี้ deploy - FR-008: Qdrant payload MUST มีครบ 11 fields:
doc_public_id,project_public_id,doc_number,doc_type,status_code,revision_number,subject,document_date,chunk_topic,chunk_index,chunk_text - FR-009: Qdrant MUST มี payload index บน
project_public_id(tenant),doc_public_id,status_code,doc_type - FR-009a:
AI_VECTOR_SIZEconstant MUST เปลี่ยนจาก768เป็น1024และ collection name constant คงเป็นlcbp3_vectors
RAG Prepare Pipeline
- FR-010: ระบบ MUST enqueue
rag-preparejob ในai-batchqueue เมื่อCorrespondenceRevisionstatus เปลี่ยนจาก DRAFT เป็น IN_REVIEW (SUBOWN) โดย job payload ต้องรวมcachedOcrTextถ้ามีใน DB; หากไม่มี job MUST เรียก OCR sidecar ใหม่โดยดึง PDF attachment จาก storage - FR-011:
rag-preparejob MUST ลบ Qdrant points เก่าของdoc_public_idก่อน upsert ใหม่เสมอ (delete + re-embed) - FR-012:
rag-preparejob MUST ไม่ block workflow submission response
RAG Query Pipeline
- FR-013: RAG query MUST embed คำถามด้วย BGE-M3 ผ่าน Sidecar
/embed - FR-014: RAG query MUST ค้นหา Qdrant ด้วย Hybrid search topK=15 กรอง
project_public_idเป็น mandatory - FR-015: RAG query MUST rerank ด้วย BGE-Reranker ผ่าน Sidecar
/rerankเพื่อได้ top 3-5 chunks ก่อนส่ง LLM
Key Entities
- EmbeddedChunk: ข้อมูลที่เก็บใน Qdrant —
doc_public_id,project_public_id,doc_number,doc_type,status_code,revision_number,subject,document_date,chunk_topic,chunk_index,chunk_text - RagPrepareJob: BullMQ job payload —
documentPublicId,projectPublicId,correspondenceNumber,docType,statusCode,revisionNumber,subject,documentDate,ocrText - RagQueryJob: BullMQ job payload —
requestPublicId,userPublicId,projectPublicId,query
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: เอกสารที่ submit workflow ถูก embed และพร้อมค้นหาใน Qdrant ภายใน 5 นาทีหลัง submit สำเร็จ
- SC-002: Chat Q&A ตอบคำถามที่เนื้อหาอยู่ในเอกสาร IN_REVIEW ขึ้นไปได้ถูกต้องอย่างน้อย 80% ของกรณีทดสอบ
- SC-003: ไม่มีข้อมูลจาก Project อื่นรั่วไหลมาในคำตอบ (0% cross-project leak)
- SC-004:
rag-preparejob ไม่ delay workflow submission response เกิน 500ms - SC-005: ระบบรองรับการ embed เอกสารขนาดสูงสุด 50 หน้าโดยไม่ timeout
- SC-006: เมื่อมี revision ใหม่ ข้อมูลเก่าในระบบค้นหาถูกแทนที่ด้วยข้อมูลใหม่ทั้งหมด (0 stale chunks)
Clarifications
Session 2026-06-05
- Q: OCR text source สำหรับ
rag-preparejob → A: ใช้ cached OCR text ถ้ามี, fallback เรียก OCR sidecar ใหม่ถ้าไม่มี (Option C) - Q: Qdrant collection migration strategy → A: ใช้ชื่อ collection เดิม
lcbp3_vectorsแต่ drop + recreate schema ใหม่ (1024 dims Hybrid) ทันที (Option C) - Q: Semantic Chunking prompt type ใน
ai_prompts→ A: เพิ่ม prompt type ใหม่rag_chunkingแยกต่างหาก (Option A)
Assumptions
- BGE-M3 (BAAI/bge-m3 ~2.3GB) และ BGE-Reranker-Large (~1.5GB) รันบน CPU RAM บน Desk-5439 ได้โดยไม่กระทบ VRAM ของ Ollama
- OCR text อาจมีหรือไม่มีใน DB —
rag-preparejob จัดการทั้งสองกรณี (cached + fallback OCR) - Qdrant collection
lcbp3_vectorsเดิม (768 dims) จะถูก drop + recreate เป็น Hybrid (1024 dims) เมื่อ deploy — ข้อมูล vector เดิมทั้งหมดจะหายไป ซึ่งยอมรับได้เพราะยังไม่มีข้อมูล production ที่ embed ด้วยระบบใหม่ - Sidecar service (port 8765) ยังคงใช้ container เดิม เพียงเพิ่ม endpoints ใหม่
Out of Scope
- Flow 3 (Auto-fill) RAG trigger — จะทำใน feature แยก
- การ embed เอกสารชนิดอื่น (RFA, Transmittal) — scope เฉพาะ Correspondence ก่อน
- Frontend Chat UI (ADR-026) — มีแผน implement แยกใน feature 226
- Migration/re-embedding เอกสาร DRAFT ที่มีอยู่เดิม