15 KiB
Tasks: RAG Pipeline Enhancements
Input: Design documents from specs/200-fullstacks/234-rag-pipeline-enhancements/
Prerequisites: plan.md ✅, spec.md ✅
Branch: 234-rag-pipeline-enhancements
Format: [ID] [P?] [Story] Description
- [P]: Can run in parallel (different files, no dependencies)
- [Story]: US1=Chat Q&A, US2=Auto RAG Trigger, US3=Semantic Chunking, US4=Hybrid Search
Phase 1: Setup (Shared Infrastructure)
Purpose: เพิ่ม dependency และ SQL delta ที่จำเป็นสำหรับทุก story
- T001 [P] เพิ่ม
FlagEmbedding>=1.2.0ในspecs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/requirements.txt - T002 [P] สร้าง SQL delta
specs/03-Data-and-Storage/deltas/2026-06-05-add-rag-chunking-prompt.sql— INSERTai_prompts(prompt_type='rag_chunking', placeholder{{ocr_text}})
Checkpoint: dependencies + SQL delta พร้อม — สามารถเริ่ม Phase 2 ได้
Phase 2: Foundational (Blocking Prerequisites)
Purpose: OCR Sidecar + AiQdrantService ใหม่ — ทุก story ต้องพึ่งพิงสองส่วนนี้
⚠️ CRITICAL: Phase 3+ ทั้งหมดต้องรอ Phase 2 เสร็จก่อน
2A — OCR Sidecar: BGE-M3 + Reranker endpoints
- T003 โหลด
BGEM3FlagModelและFlagRerankerเป็น global singleton ตอน startup ในspecs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/app.py(CPU mode,use_fp16=False) - T004 [P] เพิ่ม
POST /embedendpoint ในapp.py— รับ{"text": string}คืน{"dense": list[float], "sparse": {"indices": list[int], "values": list[float]}} - T005 [P] เพิ่ม
POST /rerankendpoint ในapp.py— รับ{"query": string, "chunks": list[str]}คืน{"scores": list[float], "ranked_indices": list[int]}
2B — AiQdrantService: Hybrid Schema
- T006 แก้
AI_VECTOR_SIZE = 1024(เดิม 768) ในbackend/src/modules/ai/qdrant.service.ts - T007 แก้
ensureCollection()→ drop collection เก่าก่อน แล้ว recreate เป็น Hybrid (bge_densesize=1024 +bge_sparse) ในbackend/src/modules/ai/qdrant.service.ts - T008 แก้
upsert()→ รับ interface ใหม่ที่มีdenseVector: number[],sparseVector: {indices: number[], values: number[]}, และ payload ครบ 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) ในbackend/src/modules/ai/qdrant.service.ts - T009 แก้
deleteByDocumentPublicId()→ filter บน payload fielddoc_public_id(ไม่ใช่ point id) ในbackend/src/modules/ai/qdrant.service.ts - T010 เพิ่ม payload indexes สำหรับ
doc_public_id,status_code,doc_typeในensureCollection()ในbackend/src/modules/ai/qdrant.service.ts
Checkpoint: Sidecar /embed + /rerank พร้อม; Qdrant collection ใหม่พร้อม — เริ่ม Phase 3+ ได้
Phase 3: User Story 1 — Document Q&A with Accurate Context (P1) 🎯 MVP
Goal: Chat Q&A ใช้ BGE-M3 embed query + Hybrid search + Reranker ก่อนส่ง LLM
Independent Test: ส่ง RAG query → ตรวจสอบ Sidecar /embed ถูกเรียก → Qdrant Hybrid search → /rerank ถูกเรียก → LLM ตอบพร้อม citation
Tests for User Story 1
- T011 [P] [US1] เขียน unit test
backend/src/modules/ai/ai-rag.service.spec.ts— mock Sidecar/embedและ/rerank; ตรวจสอบว่าprocessQuery()เรียก embed ก่อน search ก่อน rerank
Implementation for User Story 1
- T012 [US1] เพิ่ม method
embedViaSidecar(text: string)ในbackend/src/modules/ai/services/ocr.service.ts— POST Sidecar/embed, คืน{dense, sparse} - T013 [US1] แก้
search()/searchByProject()ในbackend/src/modules/ai/qdrant.service.ts→ ใช้ Hybrid query (RRF fusion) แทน dense-only, รับdenseVector+sparseVector - T014 [US1] เพิ่ม method
rerankViaSidecar(query: string, chunks: string[])ในbackend/src/modules/ai/services/ocr.service.ts— POST Sidecar/rerank - T015 [US1] แก้
processQuery()ในbackend/src/modules/ai/ai-rag.service.ts:- เรียก
embedViaSidecar()แทนollamaService.generateEmbedding() - เรียก
searchByProject()ด้วย Hybrid vectors, topK=15 - เรียก
rerankViaSidecar()→ เลือก top 3-5 chunks - ประกอบ context จาก payload ใหม่ (
chunk_text,doc_number,document_date,status_code)
- เรียก
Checkpoint: US1 — RAG query ทำงานครบ pipeline ใหม่
Phase 4: User Story 2 — Automatic RAG Preparation on Workflow Submit (P2)
Goal: submit Correspondence → trigger rag-prepare job อัตโนมัติ ไม่ block response
Independent Test: Submit workflow → ตรวจ BullMQ ai-batch queue มี rag-prepare job → job เสร็จ → Qdrant มี points ของเอกสาร
Tests for User Story 2
- T016 [P] [US2] เขียน unit test
backend/src/modules/ai/processors/ai-batch.processor.spec.ts— ตรวจสอบcase 'rag-prepare'ถูก dispatch และเรียก OCR/embed/upsert ตามลำดับ - T017 [P] [US2] เขียน unit test
backend/src/modules/correspondence/correspondence-workflow.service.spec.ts— ตรวจสอบว่าsyncStatus()เรียกenqueueRagPrepare()เมื่อ targetCode ≠ 'DRAFT'
Implementation for User Story 2
- T018 [US2] เพิ่ม interface
RagPrepareJobPayloadและ methodenqueueRagPrepare(payload)ในbackend/src/modules/ai/ai-queue.service.ts - T019 [US2] เพิ่ม
case 'rag-prepare':ในprocess()ของbackend/src/modules/ai/processors/ai-batch.processor.ts— dispatch ไปprocessRagPrepare(data) - T019a [US2] ตรวจสอบและตั้งค่าให้
ai-batchQueue Processor มีconcurrency: 1ใน@Processordecorator เพื่อป้องกัน VRAM overflow ตาม ADR-035 - T020a [US2] implement OCR text resolution ใน
processRagPrepare()ในai-batch.processor.ts: ถ้าcachedOcrTextมีให้ใช้เลย; ถ้าไม่มีและมีattachmentPathเรียกOcrService.extractText(attachmentPath); ถ้าไม่มีทั้งคู่ →this.logger.warn('rag-prepare: ไม่มี OCR text และไม่มี attachment path')แล้ว return early - T020b [US2] implement skip-guard ใน
processRagPrepare(): ถ้าocrText.trim().length < 50→this.logger.warn('rag-prepare: OCR text สั้นเกินไป — skip embedding')แล้ว return early (ไม่ error, ไม่ fail job) - T020c [US2] implement embed + upsert pipeline ใน
processRagPrepare(): เรียกEmbeddingService.embedDocument()(refactor ใน US3) → เรียกAiQdrantService.deleteByDocumentPublicId(projectPublicId, documentPublicId)→ เรียกAiQdrantService.upsert()ด้วย chunks + payload ครบ 11 fields รวมstatus_codeจากdata.statusCode - T021 [US2] inject
AiQueueServiceเข้าCorrespondenceWorkflowServiceconstructor ในbackend/src/modules/correspondence/correspondence-workflow.service.ts - T022 [US2] แก้
syncStatus()ในcorrespondence-workflow.service.ts— หลัง save ถ้าtargetCode !== 'DRAFT'→ เรียกaiQueueService.enqueueRagPrepare()แบบ fire-and-forget พร้อมข้อมูลจากrevision+correspondence - T023 [US2] อัปเดต
CorrespondenceModuleimports ให้CorrespondenceWorkflowServiceเข้าถึงAiQueueServiceได้ ในbackend/src/modules/correspondence/correspondence.module.ts
Checkpoint: US2 — submit → BullMQ job → Qdrant embed ทำงานครบ
Phase 5: User Story 3 — Semantic Chunking (P2)
Goal: EmbeddingService ใช้ Semantic Chunking ด้วย typhoon2.5 + prompt rag_chunking แทน fixed-size
Independent Test: embed เอกสาร → ตรวจ Qdrant points มี chunk_topic ที่ไม่ว่างเปล่า → fallback: ลบ <chunk> tag ออกจาก LLM output → ตรวจว่าใช้ fixed-size แทน
Tests for User Story 3
- T024 [P] [US3] เขียน unit test
backend/src/modules/ai/services/embedding.service.spec.ts:semanticChunkText()— mock LLM output พร้อม<chunk>tag → ตรวจ parse ถูกต้อง- fallback case — LLM output ไม่มี
<chunk>tag → ตรวจใช้ fixed-size chunking
Implementation for User Story 3
- T025 [US3] เพิ่ม method
semanticChunkText(ocrText: string)ในbackend/src/modules/ai/services/embedding.service.ts:- โหลด prompt จาก
ai_prompts(prompt_type='rag_chunking') ผ่านPromptServiceหรือ query โดยตรง - เรียก
OllamaServiceด้วย typhoon2.5-np-dms + prompt +ocrText - คืน LLM output string
- โหลด prompt จาก
- T026 [US3] เพิ่ม method
parseChunkTags(llmOutput: string)ในembedding.service.ts:- parse
<chunk topic="...">...</chunk>tags ด้วย regex - ถ้าไม่มี tag → fallback
fixedSizeChunk(ocrText, 512, 64) - คืน
Array<{topic: string, text: string}>
- parse
- T027 [US3] แก้
embedDocument()ในembedding.service.ts:- เรียก
semanticChunkText()→parseChunkTags() - สำหรับแต่ละ chunk: เรียก
OcrService.embedViaSidecar(chunk.text)→ ได้{dense, sparse} - upsert ผ่าน
AiQdrantService.upsert()ด้วย payload ครบ 11 fields (รวมchunk_topicและchunk_text)
- เรียก
Checkpoint: US3 — Semantic Chunking พร้อม; US2 processRagPrepare() ใช้ path ใหม่นี้โดยอัตโนมัติ
Phase 6: User Story 4 — Hybrid Search with Reranking (P3)
Goal: ตรวจสอบว่า Hybrid search (RRF) ทำงานถูกต้องใน production-like scenario
Independent Test: ส่ง query ที่มี keyword เฉพาะ → ตรวจสอบ sparse vector ถูกส่งไป Qdrant → reranked top-5 scores มีค่า > dense-only baseline
Implementation for User Story 4
- T028 [US4] เพิ่ม payload index สำหรับ
doc_typeและstatus_codeใน QdrantensureCollection()ถ้ายังไม่มี (ต่อจาก T010) ในbackend/src/modules/ai/qdrant.service.ts - T029 [US4] ตรวจสอบ
searchByProject()ในqdrant.service.tsรองรับ optionalstatusFilterparameter สำหรับกรณีที่ต้องการ approved-only ในอนาคต - T030 [US4] เพิ่ม logging ใน
processQuery()ในai-rag.service.ts— log จำนวน candidates ก่อน/หลัง rerank และ top scores (ใช้ NestJSLogger, ไม่ใช้console.log)
Checkpoint: US4 — Hybrid search + reranking verified
Phase 7: Polish & Cross-Cutting Concerns
- T031 [P] deprecate หรือ remove
backend/src/modules/rag/qdrant.service.ts(เก่า — replaced โดยai/qdrant.service.ts) หลังตรวจสอบว่าไม่มี import อื่นใช้อยู่ - T032 [P] อัปเดต
backend/src/modules/ai/ai.module.tsimports/providers ถ้ามีการเปลี่ยนแปลง dependencies ใหม่ - T033 อัปเดต ADR-035
Implementation Statustable — Flow 2B และ Flow 4 เป็น ✅ ในspecs/06-Decision-Records/ADR-035-ai-pipeline-flow-architecture.md - T034 [P] ตรวจสอบ TypeScript strict — ไม่มี
any, ไม่มีconsole.logใน files ที่แก้ไขทั้งหมด
Dependencies & Execution Order
Phase Dependencies
- Phase 1 (Setup): เริ่มได้ทันที — ขนานกันได้
- Phase 2 (Foundational): ต้องรอ Phase 1 — blocks Phase 3+
- Phase 3 (US1 — Chat Q&A): ต้องรอ Phase 2 — T012-T015 ต้องการ Sidecar + Qdrant ใหม่
- Phase 4 (US2 — RAG Trigger): ต้องรอ Phase 2 — T018-T023 ต้องการ Qdrant ใหม่
- Phase 5 (US3 — Semantic Chunking): ต้องรอ Phase 2 + T018 (ใช้
enqueueRagPreparetype) - Phase 6 (US4 — Hybrid): ต้องรอ Phase 3 (ใช้
searchByProjectใหม่) - Phase 7 (Polish): ต้องรอ Phase 3-6 ครบ
User Story Dependencies
- US1 ⬅️ Phase 2 (Sidecar /embed, /rerank; Qdrant Hybrid)
- US2 ⬅️ Phase 2 (Qdrant Hybrid) + T018 type definition
- US3 ⬅️ Phase 2 + T018 + SQL delta T002
- US4 ⬅️ US1 (T013 searchByProject Hybrid)
Parallel Opportunities
- T001 + T002 ขนานกัน (Phase 1)
- T003 (Sidecar init) → T004 + T005 ขนานกัน
- T006-T010 ขนานกันได้ (ไฟล์เดียวกันแต่ methods ต่างกัน — ทำตามลำดับเพื่อความปลอดภัย)
- T011 + T016 + T017 + T024 ขนานกัน (เขียน tests คนละไฟล์)
- T012 + T014 ขนานกัน (methods ต่างกันใน ocr.service.ts)
- T018 + T025 ขนานกัน (คนละไฟล์)
Implementation Strategy
MVP First (User Story 1 + 2)
- Phase 1: Setup (T001-T002)
- Phase 2: Foundational — Sidecar + Qdrant (T003-T010)
- Phase 3: US1 Chat Q&A (T011-T015) → ทดสอบ RAG query ทำงาน
- Phase 4: US2 RAG Trigger (T016-T023) → ทดสอบ submit → embed
- STOP & VALIDATE: ระบบ embed + query ใหม่ทำงานครบ end-to-end
- Deploy รอบแรก
Incremental Addition
- Phase 5: US3 Semantic Chunking → re-embed เอกสารที่มี
- Phase 6: US4 Hybrid verification
- Phase 7: Polish + deprecate code เก่า
Notes
[P]= ขนานกันได้ (different files หรือ independent methods)- ทุก task ต้องไม่มี
anytype และไม่มีconsole.log(ใช้ NestJSLogger) - Qdrant drop collection เกิดขึ้นตอน
ensureCollection()ถูกเรียกครั้งแรกหลัง deploy — Chat Q&A จะ return empty ชั่วคราวจนกว่า documents จะถูก re-embed - commit message format:
feat(ai): <description>หรือrefactor(ai): <description>