# ADR-030: Context-Aware Prompt Templates for OCR Metadata Extraction **Status:** Accepted **Date:** 2026-05-27 **Decision Makers:** Development Team, System Architect **Related Documents:** - [ADR-029: Dynamic Prompt Management](./ADR-029-dynamic-prompt-management.md) - [ADR-023A: Unified AI Architecture — Model Revision](./ADR-023A-unified-ai-architecture.md) - [ADR-019: Hybrid Identifier Strategy](./ADR-019-hybrid-identifier-strategy.md) - [ADR-007: Error Handling Strategy](./ADR-007-error-handling-strategy.md) --- ## บริบทและปัญหา (Context and Problem Statement) ADR-029 แก้ปัญหา hardcoded prompt โดยเก็บ prompt template ในตาราง `ai_prompts` แต่ยังมีข้อจำกัด: 1. **ไม่มี Context Awareness:** Prompt template ไม่สามารถระบุ master data context (projects, organizations, disciplines, etc.) ที่ AI ต้องใช้ในการ match ข้อมูล 2. **Context Size ไม่ถูกควบคุม:** ไม่มีกลไก filter master data ตาม project/contract scope ทำให้ context ใหญ่เกินไป 3. **Language Hardcoded:** Prompt template เดิมใช้ภาษาอังกฤษ แต่ LCBP3-DMS เป็นระบบภาษาไทย 4. **Output Schema ไม่ครบ:** สกัดเฉพาะ 8 fields แต่ยังขาด fields สำคัญสำหรับ correspondence creation (เช่น recipient organizations, UUID matching) --- ## ปัจัยขับเคลื่อนการตัดสินใจ (Decision Drivers) - **Context Filtering:** ต้อง filter master data ตาม project/contract scope เพื่อลด context size และป้องกัน cross-project data leak (ADR-023A) - **UUID Matching:** AI ต้องส่งคืน UUID (ไม่ใช่ INT ID) ตาม ADR-019 - **Thai Language Support:** Prompt และ output ต้องเป็นภาษาไทย - **Flexible Configuration:** Admin ต้องกำหนด filter criteria และ context config ได้ผ่าน AI Admin Console - **Backward Compatibility:** ต้องรองรับ prompt template เดิมที่ไม่มี context_config --- ## ทางเลือกที่ถูกพิจารณา (Considered Options) ### Option 1: เพิ่มคอลัมน์แยกต่างหาก (project_id, contract_id) - **ข้อดี:** Query ง่าย, type-safe - **ข้อเสีย:** ไม่ flexible, ถ้าอนาคตต้องการ filter criteria อื่นต้อง alter table อีก ### Option 2: เก็บ filter criteria ใน field_schema (JSON) - **ข้อดี:** ไม่ต้อง alter table - **ข้อเสีย:** ผสม output schema กับ filter config, สับสน, query ซับซ้อน ### Option 3: เพิ่ม context_config JSON column (ตัวเลือกที่ได้รับเลือก) - **ข้อดี:** Flexible, แยก concern ชัด (field_schema vs context_config), รองรับ config อนาคต - **ข้อเสีย:** ต้อง alter table ครั้งเดียว --- ## ผลการตัดสินใจ (Decision Outcome) **ทางเลือกที่ได้รับเลือก:** Option 3 — เพิ่ม `context_config` JSON column --- ## ข้อตกลงหลัก (Core Decisions — Grilling Session 2026-05-27) | # | ประเด็น | การตัดสินใจ | |---|---------|-------------| | 1 | Page Limit | **3 หน้า** (ตาม ADR-023A — classification/tagging rule) | | 2 | Database Query Strategy | **Option A** — Backend ดึง master data แล้วส่งเป็น context ใน prompt (AI ไม่ query DB โดยตรงตาม ADR-023) | | 3 | JSON Output Schema | **11 fields**: projectPublicId, correspondenceTypeCode, disciplineCode, originatorOrganizationPublicId, recipientOrganizationPublicIds, recipientTypes, subject, documentDate, tags, summary, confidence | | 4 | Context Format | **Option A** — List format (array of objects) สำหรับ AI scan ง่าย | | 5 | Filter Strategy | กำหนดใน prompt template โดย filter ด้วย projects และ contracts | | 6 | Filter Storage | **Option C** — เพิ่ม `context_config` JSON column | | 7 | Language | **ภาษาไทย** — Prompt instruction และ summary output | | 8 | UUID Handling | **ADR-019** — AI ส่งคืน UUID string (ไม่ใช่ INT ID) | | 9 | Fallback Strategy | ถ้า AI ไม่พบ match → ส่ง `null` และต้องการ human validation | --- ## รายละเอียดเชิงสถาปัตยกรรม (Implementation Details) ### 1. Schema Changes (ADR-009) ```sql -- Delta: เพิ่ม context_config สำหรับ filter criteria ALTER TABLE ai_prompts ADD COLUMN context_config JSON NULL COMMENT 'Configuration สำหรับ context ที่ backend ต้องส่งให้ AI (filter, pageSize, language, etc.)'; ``` ### 2. context_config Structure ```json { "filter": { "projectId": 123, "contractId": 456 }, "pageSize": 3, "language": "th", "outputLanguage": "th" } ``` **Field Descriptions:** - `filter.projectId`: INT หรือ null — Filter master data ตาม project scope - `filter.contractId`: INT หรือ null — Filter master data ตาม contract scope - `pageSize`: INT (default: 3) — จำนวนหน้า PDF ที่ OCR สกัด (ตาม ADR-023A) - `language`: string (default: "th") — ภาษา prompt instruction - `outputLanguage`: string (default: "th") — ภาษา output (summary) ### 3. JSON Output Schema (field_schema) ```json { "projectPublicId": "string|null", "correspondenceTypeCode": "string|null", "disciplineCode": "string|null", "originatorOrganizationPublicId": "string|null", "recipientOrganizationPublicIds": "string[]|null", "recipientTypes": "string[]|null", "subject": "string|null", "documentDate": "string:YYYY-MM-DD|null", "tags": "string[]|null", "summary": "string|null", "confidence": "float:0-1" } ``` **Mapping to Database:** - `projectPublicId` → `projects.uuid` (ADR-019) - `correspondenceTypeCode` → `correspondence_types.type_code` - `disciplineCode` → `disciplines.discipline_code` - `originatorOrganizationPublicId` → `organizations.uuid` (originator_id) - `recipientOrganizationPublicIds` → `organizations.uuid[]` (correspondence_recipients) - `recipientTypes` → `correspondence_recipients.recipient_type` ("TO", "CC ") - `subject` → `correspondence_revisions.title` - `documentDate` → `correspondence_revisions.document_date` - `tags` → `tags.tag_name[]` - `summary` — AI-generated summary (4-5 ประโยคภาษาไทย) - `confidence` — AI confidence score (0.0-1.0) ### 4. Context Format (List Format) Backend ส่ง master data context ในรูปแบบ: ```json { "availableProjects": [ {"code": "LCBP3", "uuid": "0195...", "name": "โครงการ LCBP3"} ], "availableOrganizations": [ {"code": "กทท.", "uuid": "0195...", "name": "การทางพิเศษแห่งประเทศไทย"}, {"code": "TEAM", "uuid": "0195...", "name": "TEAM Consulting"} ], "availableDisciplines": [ {"code": "GEN", "name": "General"}, {"code": "STR", "name": "Structural"} ], "availableCorrespondenceTypes": [ {"code": "RFA", "name": "Request for Approval"}, {"code": "RFI", "name": "Request for Information"} ], "availableTags": [ {"name": "Urgent", "color": "red"}, {"name": "Review", "color": "blue"} ] } ``` ### 5. Prompt Template Example (ภาษาไทย) ``` คุณเป็นเอนจิ้นสกัดข้อมูลเอกสารมืออาชีพ วิเคราะห์ข้อความ OCR จากเอกสารโครงการ (3 หน้าแรกเท่านั้น) และสกัดข้อมูลเมตาดาต้า ข้อความ OCR: {{ocr_text}} ข้อมูลอ้างอิงที่ใช้ได้: {{master_data_context}} สกัด fields ต่อไปนี้: 1. projectPublicId: UUID ของโครงการ (จาก availableProjects) 2. correspondenceTypeCode: รหัสประเภทเอกสาร (เช่น RFA, RFI) 3. disciplineCode: รหัสสาขางาน (เช่น GEN, STR) 4. originatorOrganizationPublicId: UUID ขององค์กรผู้ส่ง 5. recipientOrganizationPublicIds: UUID[] ขององค์กรผู้รับ (หลายองค์กรได้) 6. recipientTypes: string[] ("TO", "CC") 7. subject: หัวข้อเอกสาร 8. documentDate: วันที่เอกสาร (YYYY-MM-DD) 9. tags: string[] รายชื่อ tags 10. summary: สรุปเอกสาร 4-5 ประโยคภาษาไทย 11. confidence: ความมั่นใจ (0.0-1.0) ส่งคืนเฉพาะ JSON object ที่ถูกต้อง ไม่รวม markdown code blocks ``` ### 6. Backend Implementation **AiPromptsService.resolveContext()** ```typescript async resolveContext(activePrompt: AiPrompt): Promise> { const config = activePrompt.contextConfig || {}; const filter = config.filter || {}; const projectId = filter.projectId; const contractId = filter.contractId; // Query master data with filter const projects = await this.projectService.findAll({ projectId }); const organizations = await this.organizationService.findAll({ projectId, contractId }); const disciplines = await this.disciplineService.findAll({ contractId }); const correspondenceTypes = await this.correspondenceTypeService.findAll(); const tags = await this.tagsService.findAll({ projectId }); return { availableProjects: projects.map(p => ({ code: p.projectCode, uuid: p.uuid, name: p.projectName })), availableOrganizations: organizations.map(o => ({ code: o.organizationCode, uuid: o.uuid, name: o.organizationName })), availableDisciplines: disciplines.map(d => ({ code: d.disciplineCode, name: d.codeNameTh })), availableCorrespondenceTypes: correspondenceTypes.map(t => ({ code: t.typeCode, name: t.typeName })), availableTags: tags.map(t => ({ name: t.tagName, color: t.colorCode })), }; } ``` **AiBatchProcessor.processSandboxExtract()** ```typescript const activePrompt = await this.aiPromptsService.getActive('ocr_extraction'); const context = await this.aiPromptsService.resolveContext(activePrompt); const prompt = activePrompt.template .replace('{{ocr_text}}', ocrText) .replace('{{master_data_context}}', JSON.stringify(context, null, 2)); ``` ### 7. Frontend Changes (AI Admin Console) **Prompt Editor UI:** - เพิ่ม section "Context Configuration" - Dropdown เลือก Project (optional) - Dropdown เลือก Contract (optional) - Input field: Page Size (default: 3) - Select: Language (th/en) **n8n Workflow:** - เพิ่ม node "Select Project/Contract" ก่อน "OCR Extraction" - ส่ง `projectId` และ `contractId` ไปยัง DMS API `/ai/ocr-extract` - Backend resolve context จาก context_config หรือ override ด้วย input parameters --- ## ผลกระทบ (Consequences) ### ผลดี - Admin กำหนด filter criteria ได้ runtime ผ่าน AI Admin Console - Context size ถูกควบคุม ตาม project/contract scope - AI ส่งคืน UUID ตาม ADR-019 พร้อมนำเข้า - Prompt และ output เป็นภาษาไทย - Flexible สำหรับ config อนาคต (language, temperature, etc.) ### ผลเสีย / ข้อระวัง - ต้อง alter table `ai_prompts` เพิ่ม `context_config` column - Backend query master data เพิ่มขึ้นต่อ job (mitigate ด้วย Redis cache) - ต้อง migrate prompt template เดิม (seed data) ให้มี context_config - n8n workflow ต้องอัปเดตให้รองรับ filter criteria --- ## Migration Plan ### Phase 1: Database Schema 1. Run delta SQL: `ALTER TABLE ai_prompts ADD COLUMN context_config JSON NULL` 2. Update `AiPrompt` entity เพิ่ม `contextConfig` property 3. Update DTOs (CreateAiPromptDto, UpdateAiPromptDto) ### Phase 2: Backend Logic 1. Implement `AiPromptsService.resolveContext()` 2. Update `AiBatchProcessor` สำหรับส่ง context ไปให้ AI 3. Add Redis cache สำหรับ master data context (TTL: 300s) ### Phase 3: Seed Data 1. Update seed data ใน `2026-05-25-create-ai-prompts.sql` 2. เพิ่ม context_config สำหรับ version 1 (null = no filter) 3. สร้าง version 2 ใหม่ด้วย prompt template ภาษาไทย + context_config example ### Phase 4: Frontend 1. Update AI Admin Console UI ให้มี Context Configuration section 2. Update n8n workflow ให้รองรับ filter criteria ### Phase 5: Testing 1. Unit tests สำหรับ context resolution logic 2. Integration tests สำหรับ sandbox และ migration pipeline 3. Manual testing ด้วย prompt template ใหม่ --- ## Grilling Session Log ``` 2026-05-27 — grilling session ผ่าน Antigravity AI Q1: Page limit → 3 หน้า (ตาม ADR-023A) Q2: Database query strategy → Option A (Backend ดึง master data ส่ง context) Q3: JSON output schema → 11 fields (UUID-based ตาม ADR-019) พร้อมปรับปรุง recipients เป็น Object Array: Array<{ organizationPublicId: string, recipientType: "TO" | "CC" }> Q4: Context format → Option A (List format) Q5: Filter strategy → กำหนดใน prompt template โดย filter ด้วย projects/contracts Q6: Filter storage → Option C (เพิ่ม context_config JSON column) Q7: Tag Suggestion → Option A (ให้ Backend ทำการ Diff ระหว่าง tags ที่ได้มากับ availableTags เพื่อระบุสถานะ isNew: true เอง) Q8: Project Scope Priority → Option C (หาก Template มีการผูกโครงการไว้ใน context_config แต่ request พยายาม override ไปโครงการอื่น ระบบจะทำการ Reject ด้วย ForbiddenException ทันที) Q9: Database Typo Cleanup → Option C (อนุมัติล้าง whitespace typo ของตัวแปร 'CC ' ให้ถูกต้องเป็น 'CC' ทั้งระบบในระดับ Database Schema และอัปเดตไฟล์โครงสร้างหลัก) ``` --- ## Related ADRs - **ADR-029:** Dynamic Prompt Management (base architecture) - **ADR-023A:** Unified AI Architecture — Model Revision (3-page rule, AI boundary) - **ADR-019:** Hybrid Identifier Strategy (UUID handling) - **ADR-007:** Error Handling Strategy (layered error classification) - **ADR-009:** Database Migration Strategy (direct SQL edits)