690618:1444 237 #02
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
# Research Findings: OCR & AI Extraction Prompt Management
|
||||
|
||||
**Date**: 2026-06-17
|
||||
**Feature**: 238-ocr-ai-prompt-separation
|
||||
|
||||
## Research Areas
|
||||
|
||||
### 1. Concurrent Edit Handling Strategy
|
||||
|
||||
**Decision**: ใช้ optimistic locking ด้วย version/timestamp field ใน ai_prompts table
|
||||
|
||||
**Rationale**:
|
||||
- ลด lock contention ใน database - ไม่ block admin คนอื่น
|
||||
- User experience ดีกว่า - แจ้งเตือนแทนการ block
|
||||
- ง่ายต่อการ implement กับ TypeORM @VersionColumn
|
||||
- สอดคล้องกับ pattern ที่ใช้ใน LCBP3-DMS อยู่แล้ว (ADR-002 document numbering)
|
||||
|
||||
**Alternatives considered**:
|
||||
- **Pessimistic locking (SELECT FOR UPDATE)**: ไม่เลือกเพราะอาจ block admin คนอื่นนาน, ไม่เหมาะกับ admin UI
|
||||
- **Last-write-wins**: ไม่เลือกเพราะเสี่ยงสูญเสียการแก้ไขของ admin คนแรกโดยไม่รู้ตัว
|
||||
- **Operational Transform (like Google Docs)**: ไม่เลือกเพราะ complex เกินความจำเป็น, prompt editing ไม่ต้องการ real-time collaboration
|
||||
|
||||
**⚠️ สถานะของจริง (codebase ปัจจุบัน)**:
|
||||
- `@VersionColumn({ name: 'version' })` ถูกเพิ่มใน entity **แล้ว** (delta 2026-06-15) — คอลัมน์ DB มีแล้ว
|
||||
- แต่ `activate()` ของจริง **ใช้ pessimistic_write lock** ใน transaction และ **ไม่รับ `expectedVersion`** จึงยังไม่มี HTTP 409 flow
|
||||
- `@VersionColumn` ปัจจุบันทำงานตอน `save()` เท่านั้น (ดัก lost update ระหว่าง read→write)
|
||||
|
||||
**ถ้าจะทำ flow optimistic 409 ตาม spec ต้องแก้เพิ่ม**:
|
||||
- แก้ signature `activate(promptType, versionNumber, userId, expectedVersion)` ให้รับ `expectedVersion`
|
||||
- เทียบ `version` ก่อน save → ถ้าไม่ตรงโยน BusinessException/409 พร้อม current data
|
||||
- หมายเหตุ: อ้าง ADR-002 จริงๆใช้ Redlock/pessimistic — คำว่า "สอดคล้อง pattern ADR-002" ใน rationale จึงคลาดเคลื่อน
|
||||
|
||||
### 2. Sidecar System Prompt Parameter Format
|
||||
|
||||
**Decision**: Sidecar รับ systemPrompt ผ่าน multipart/form-data field 'systemPrompt'
|
||||
|
||||
**Rationale**:
|
||||
- สอดคล้องกับรูปแบบที่ sidecar รับ file upload อยู่แล้ว
|
||||
- ไม่ต้องเปลี่ยน content-type หรือ endpoint structure
|
||||
- ง่ายต่อการ integrate กับ existing form data
|
||||
|
||||
**Alternatives considered**:
|
||||
- **JSON payload with base64 PDF**: ไม่เลือกเพราะต้องเปลี่ยน endpoint structure มาก, file size limit ของ JSON
|
||||
- **Separate endpoint**: ไม่เลือกเพราะซับซ้อนเกินไป, ควรรวมอยู่ใน /ocr-upload
|
||||
|
||||
**✅ ยืนยันแล้ว (จาก app.py จริง)**: OCR engine ใช้ `prepare_ocr_messages(pdf_path, task_type="structure", page_num=N)` จาก typhoon_ocr ซึ่งคืน messages array ที่มี **user message เดียว** โดย `messages[0]["content"]` เป็น list (image + prompt). โค้ดปัจจุบันที่ `process_ocr()` (app.py:194-203) **inject ข้อความเพิ่มได้สำเร็จอยู่แล้ว** ด้วยการ `messages[0]["content"].append({"type": "text", "text": ...})` (DMS tags) → ใช้ pattern เดียวกันนี้กับ systemPrompt ได้ทันที
|
||||
|
||||
**Decision (ปรับให้ตรงของจริง)**: inject systemPrompt ด้วยการ **append text item เข้า `messages[0]["content"]`** — **ไม่** insert `{"role":"system"}` แยก (typhoon OCR เป็น single-message format; system role แยกยังไม่พิสูจน์ และเสี่ยงกระทบ structured extraction)
|
||||
|
||||
**Implementation approach (ยืนยันตาม app.py)**:
|
||||
```python
|
||||
# process_ocr() — เพิ่มพารามิเตอร์ system_prompt และ append ก่อน DMS tags
|
||||
def process_ocr(pdf_path, page_num=1, options_override={}, system_prompt: Optional[str] = None) -> str:
|
||||
messages = prepare_ocr_messages(pdf_path, task_type="structure", page_num=page_num)
|
||||
if system_prompt:
|
||||
messages[0]["content"].append({"type": "text", "text": system_prompt})
|
||||
# DMS tags injection เดิม (ยังคงไว้)
|
||||
messages[0]["content"].append({"type": "text", "text": "Additionally: ..."})
|
||||
# ...payload เดิม → /v1/chat/completions
|
||||
|
||||
# /ocr-upload — รับ systemPrompt แล้วส่งต่อ (ต้อง X-API-Key)
|
||||
@app.post("/ocr-upload", dependencies=[Depends(get_api_key)])
|
||||
def ocr_upload(file=File(...), engine=Form("auto"), systemPrompt: Optional[str] = Form(None), ...):
|
||||
# ต้อง thread systemPrompt → _process_pdf_doc(...) → process_ocr(..., system_prompt=systemPrompt)
|
||||
...
|
||||
```
|
||||
|
||||
> หมายเหตุ: ต้อง thread `systemPrompt` ผ่าน `_process_pdf_doc()` (เพิ่มพารามิเตอร์) ไปยัง `process_ocr()` ด้วย เพราะปัจจุบัน `_process_pdf_doc` ไม่รับ systemPrompt
|
||||
|
||||
### 3. Default OCR System Prompt Content
|
||||
|
||||
**Decision**: ใช้ hardcoded default minimal system prompt
|
||||
|
||||
**Content**: "Extract all text from this PDF page accurately."
|
||||
|
||||
**Rationale**:
|
||||
- Simple and language-agnostic
|
||||
- ทำงานได้กับทุกประเภทเอกสาร (Thai/English/mixed)
|
||||
- ไม่มี bias ต่อ specific document type
|
||||
- Vision model (np-dms-ocr) ถูกฝึกมาให้เข้าใจ instruction แบบนี้อยู่แล้ว
|
||||
|
||||
**Alternatives considered**:
|
||||
- **No default (fail fast)**: ไม่เลือกเพราะจะทำให้ OCR ใช้ไม่ได้ถ้าลืมสร้าง prompt หรือ database error
|
||||
- **Complex multi-language prompt**: ไม่เลือกเพราะอาจทำให้ model confused, minimal prompt มีประสิทธิภาพดีกว่า
|
||||
- **Template with placeholders**: ไม่เลือกเพราะ OCR system prompt ไม่มี context อื่นให้ inject
|
||||
|
||||
### 4. Placeholder Validation for AI Extraction Prompt
|
||||
|
||||
**Decision**: ตรวจสอบ required placeholders ตอน save (backend validation)
|
||||
|
||||
**Required placeholders**:
|
||||
- `{{ocr_text}}` - mandatory (OCR text to extract from)
|
||||
- `{{master_data_context}}` - optional (project/contract context)
|
||||
|
||||
**Rationale**:
|
||||
- ป้องกัน runtime error เมื่อ prompt ถูกใช้
|
||||
- ให้ admin รู้ทันทีว่า template ไม่ถูกต้อง
|
||||
- สอดคล้องกับ ADR-007 error handling strategy
|
||||
|
||||
**Validation approach**:
|
||||
```typescript
|
||||
validateTemplate(template: string, promptType: string): ValidationResult {
|
||||
if (promptType === 'ocr_extraction') {
|
||||
if (!template.includes('{{ocr_text}}')) {
|
||||
return { valid: false, error: 'Template must include {{ocr_text}} placeholder' };
|
||||
}
|
||||
}
|
||||
// ocr_system has no required placeholders
|
||||
return { valid: true };
|
||||
}
|
||||
```
|
||||
|
||||
### 5. AI Prompts Table Schema Compatibility
|
||||
|
||||
**Decision**: ใช้ schema ที่มีอยู่แล้วจาก ADR-029, เพิ่มแค่ @VersionColumn
|
||||
|
||||
**Existing fields (sufficient)**:
|
||||
- `prompt_type` (string): รองรับ 'ocr_system', 'ocr_extraction'
|
||||
- `version_number` (int): Version tracking
|
||||
- `template` (text): Prompt content
|
||||
- `context_config` (json): Metadata
|
||||
- `is_active` (tinyint(1)): Active flag (MariaDB ส่งกลับเป็น 0/1)
|
||||
- `created_at` (datetime): Timestamp
|
||||
- `created_by` (int FK → users.user_id): Creator — ไม่ใช่ created_by_public_id
|
||||
|
||||
**คอลัมน์จริงที่ต้องระวัง**:
|
||||
- `version` (int, @VersionColumn): **มีอยู่แล้ว** (delta `2026-06-15-fix-ai-prompts-columns.sql`)
|
||||
- `created_by` เป็น **INT FK → users(user_id)** — ไม่ใช่ `created_by_public_id`; seed ต้องใช้ `(SELECT user_id FROM users WHERE username='superadmin')`
|
||||
|
||||
**SQL delta (idempotent — version มีแล้ว)**:
|
||||
```sql
|
||||
ALTER TABLE ai_prompts ADD COLUMN IF NOT EXISTS `version` INT NOT NULL DEFAULT 1;
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
ทุก research area มีทางเลือกที่ชัดเจนและสอดคล้องกับ:
|
||||
1. LCBP3-DMS patterns (optimistic locking, backend validation)
|
||||
2. ADR-007 error handling strategy
|
||||
3. ADR-029 dynamic prompt management
|
||||
4. ADR-037 unified prompt management UX
|
||||
|
||||
ไม่มี technical blockers พร้อม proceed ไป Phase 1 design
|
||||
Reference in New Issue
Block a user