214 lines
12 KiB
Markdown
214 lines
12 KiB
Markdown
# Implementation Plan: OCR & AI Extraction Prompt Management
|
|
|
|
**Branch**: `[238-ocr-ai-prompt-separation]` | **Date**: 2026-06-17 | **Spec**: `/specs/200-fullstacks/238-ocr-ai-prompt-separation/spec.md`
|
|
**Input**: Feature specification from `/specs/200-fullstacks/238-ocr-ai-prompt-separation/spec.md`
|
|
|
|
## Summary
|
|
|
|
แยกการจัดการ OCR system prompt และ AI Extraction prompt ให้ชัดเจนใน AI Admin Console พร้อม Full 3-Step Pipeline (OCR → AI Extract → RAG Prep) ตาม ADR-037 โดย:
|
|
1. สร้าง prompt_type ใหม่ 'ocr_system' สำหรับเก็บ OCR system prompt (Step 1)
|
|
2. รองรับ 'ocr_extraction' สำหรับ AI metadata extraction (Step 2)
|
|
3. รองรับ 'rag_prep_prompt' สำหรับ semantic chunking (Step 3)
|
|
4. แก้ไข sidecar (app.py) ให้รับ system prompt และเพิ่ม /embed endpoint
|
|
5. สร้าง UI แยก tab พร้อม 3-Step Sandbox ที่แสดง vector preview
|
|
6. รองรับ versioning และ optimistic locking สำหรับ concurrent edits
|
|
|
|
## Technical Context
|
|
|
|
**Language/Version**: TypeScript 5.x (Frontend), NestJS 10.x + TypeScript 5.x (Backend), Python 3.11 (Sidecar)
|
|
|
|
**Primary Dependencies**:
|
|
- Frontend: Next.js 14, React Hook Form, Zod, TanStack Query, shadcn/ui
|
|
- Backend: NestJS, TypeORM, BullMQ, class-validator
|
|
- Sidecar: FastAPI, typhoon_ocr (SCB10X), httpx
|
|
|
|
**Storage**: MariaDB (ai_prompts table — มีอยู่แล้ว), Redis (cache TTL 60s)
|
|
|
|
**Testing**: Jest (backend), Vitest (frontend), pytest (sidecar)
|
|
|
|
**Package manager**: pnpm workspace (`pnpm --filter backend`, `pnpm --filter lcbp3-frontend`) — ห้ามใช้ npm/yarn (ดู `package.json` → `packageManager: pnpm@10.33.0`)
|
|
|
|
**Target Platform**: On-premises (QNAP NAS + Admin Desktop)
|
|
|
|
**Project Type**: Web application (backend + frontend + sidecar)
|
|
|
|
**Performance Goals**:
|
|
- Save new prompt version: <500ms p95
|
|
- Load active prompt: <200ms p95
|
|
- Sandbox OCR with custom prompt: ไม่ช้ากว่า prompt default เกิน 10%
|
|
|
|
**Constraints**:
|
|
- Sidecar ต้องอยู่บน Admin Desktop (Desk-5439) ตาม ADR-023
|
|
- AI prompt validation ต้องทำที่ backend (ไม่ trust frontend)
|
|
- รองรับ optimistic locking สำหรับ concurrent edits
|
|
|
|
**Scale/Scope**:
|
|
- 10-20 prompt versions ต่อ prompt_type
|
|
- 5-10 admin users ที่อาจแก้ไขพร้อมกัน
|
|
|
|
## Constitution Check
|
|
|
|
_GATE: Must pass before Phase 0 research. Re-check after Phase 1 design._
|
|
|
|
| Gate | Status | Notes |
|
|
|------|--------|-------|
|
|
| 2 projects max | PASS | backend, frontend |
|
|
| Language aligned | PASS | TypeScript, Python |
|
|
| Storage aligned | PASS | MariaDB (existing) |
|
|
| Test coverage | PASS | Jest/Vitest/pytest |
|
|
|
|
## Project Structure
|
|
|
|
### Documentation (this feature)
|
|
|
|
```text
|
|
specs/200-fullstacks/238-ocr-ai-prompt-separation/
|
|
├── plan.md # This file
|
|
├── spec.md # Feature specification
|
|
├── checklists/
|
|
│ └── requirements.md # Quality checklist
|
|
├── research.md # Phase 0 (research findings)
|
|
├── data-model.md # Phase 1 (entity design)
|
|
├── quickstart.md # Phase 1 (setup guide)
|
|
├── contracts/
|
|
│ └── api.yaml # OpenAPI contracts
|
|
└── tasks.md # Phase 2 (generated by speckit-tasks)
|
|
```
|
|
|
|
### Source Code (repository root)
|
|
|
|
> **สำคัญ**: โมดูลนี้ **มีอยู่แล้ว** ที่ `backend/src/modules/ai/prompts/` (ADR-029) — งาน 238
|
|
> ต้อง **ขยายของเดิม** ไม่สร้างไฟล์ controller/service/entity ชุดใหม่ที่ map ตาราง `ai_prompts` ซ้ำ
|
|
|
|
```text
|
|
backend/
|
|
├── src/modules/ai/prompts/ # (มีอยู่แล้ว — แก้ไขที่นี่)
|
|
│ ├── ai-prompts.controller.ts # @Controller('ai/prompts') — เพิ่ม route ถ้าจำเป็น
|
|
│ ├── ai-prompts.service.ts # CRUD + versioning + validation (เพิ่ม case 'ocr_system')
|
|
│ ├── ai-prompts.entity.ts # AiPrompt (มี @VersionColumn แล้ว)
|
|
│ ├── ai-prompts.service.spec.ts # unit tests (เพิ่ม test 'ocr_system')
|
|
│ └── dto/
|
|
│ ├── create-ai-prompt.dto.ts # body = { template, contextConfig } (ไม่มี promptType)
|
|
│ ├── update-prompt-note.dto.ts
|
|
│ └── ai-prompt-response.dto.ts
|
|
├── src/modules/ai/services/
|
|
│ └── sandbox-ocr-engine.service.ts # ส่ง systemPrompt ไป sidecar (Step 1)
|
|
└── src/modules/ai/dto/
|
|
└── sandbox-rag-prep.dto.ts # (มีอยู่แล้ว — Step 3 RAG Prep)
|
|
|
|
frontend/
|
|
├── components/admin/ai/
|
|
│ ├── PromptManagementTabs.tsx # Two-tab layout + Sandbox tab
|
|
│ ├── OcrPromptTab.tsx # OCR system prompt editor (textarea, no placeholders)
|
|
│ ├── AiExtractionPromptTab.tsx # AI extraction template editor (with {{ocr_text}} validation)
|
|
│ ├── SystemPromptEditor.tsx # Reusable textarea for system prompts (no placeholder validation)
|
|
│ ├── PromptVersionHistory.tsx # Version list + rollback
|
|
│ ├── SandboxStepIndicator.tsx # 3-step pipeline status (OCR → Extract → RAG Prep)
|
|
│ ├── RagPrepResultPanel.tsx # Chunk list + vector preview (5 dimensions)
|
|
│ └── SandboxWorkflow.tsx # Full 3-step workflow container
|
|
├── lib/services/
|
|
│ └── admin-ai-prompt.service.ts # API client for prompt endpoints
|
|
└── tests/
|
|
└── prompt-management.spec.ts
|
|
|
|
specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/
|
|
└── app.py # Modified: accept systemPrompt parameter ใน /ocr-upload
|
|
# (NOTE: /embed + /rerank มีอยู่แล้วตั้งแต่ 2026-06-11)
|
|
```
|
|
|
|
**Structure Decision**: Web application with backend (NestJS), frontend (Next.js), and sidecar (FastAPI). Sidecar modifications:
|
|
1. Add `systemPrompt` parameter to `/ocr-upload` endpoint (Step 1)
|
|
- **ยืนยันแล้ว (จาก app.py)**: inject โดย **append text item เข้า `messages[0]["content"]`** (pattern เดียวกับ DMS-tags injection ที่ใช้งานได้จริงแล้ว app.py:194-203) — **ไม่** insert `{"role":"system"}` แยก (typhoon OCR = single-message format)
|
|
- ต้อง thread `systemPrompt` ผ่าน `_process_pdf_doc()` → `process_ocr(..., system_prompt=...)`
|
|
2. `/embed` endpoint **มีอยู่แล้ว** (Step 3 - RAG Prep) — ไม่ใช่งานใหม่
|
|
|
|
## Complexity Tracking
|
|
|
|
> No complexity violations detected. Feature fits within standard project boundaries.
|
|
|
|
## Phase 0: Research
|
|
|
|
### Research Findings (research.md)
|
|
|
|
**Decision**: ใช้ optimistic locking ด้วย version/timestamp field ใน ai_prompts table
|
|
**Rationale**: ลด lock contention ใน database, user experience ดีกว่า (แจ้งเตือนแทน block), ง่ายต่อการ implement กับ TypeORM @VersionColumn
|
|
**Alternatives considered**:
|
|
- Pessimistic locking: ไม่เลือกเพราะอาจ block admin คนอื่นนาน
|
|
- Last-write-wins: ไม่เลือกเพราะเสี่ยงสูญเสียการแก้ไข
|
|
|
|
**Decision**: Sidecar รับ systemPrompt ผ่าน multipart/form-data field 'systemPrompt'
|
|
**Rationale**: สอดคล้องกับรูปแบบที่ sidecar รับ file upload อยู่แล้ว, ไม่ต้องเปลี่ยน content-type
|
|
**Alternatives considered**:
|
|
- JSON payload: ไม่เลือกเพราะต้องเปลี่ยน endpoint structure มาก
|
|
|
|
**Decision**: Hardcoded default OCR system prompt ใช้ข้อความ minimal เช่น "Extract all text from this PDF page accurately."
|
|
**Rationale**: Simple, language-agnostic, ทำงานได้กับทุกประเภทเอกสาร
|
|
**Alternatives considered**:
|
|
- ไม่มี default (fail): ไม่เลือกเพราะจะทำให้ OCR ใช้ไม่ได้ถ้าลืมสร้าง prompt
|
|
|
|
## Phase 1: Design
|
|
|
|
### Data Model (data-model.md)
|
|
|
|
**Entity: AiPrompt** (มีอยู่แล้วที่ `backend/src/modules/ai/prompts/ai-prompts.entity.ts` — ดูโครงสร้างจริงครบใน `data-model.md`)
|
|
|
|
จุดที่ต้องระวัง (ของจริง ต่างจากร่างเดิม):
|
|
- PK = `@PrimaryGeneratedColumn()` INT (`id`, @Exclude)
|
|
- `createdBy: number` (INT FK → users.user_id) — **ไม่ใช่** `createdByPublicId`
|
|
- มี `fieldSchema`, `testResultJson`, `manualNote`, `lastTestedAt`, `activatedAt` ที่ร่างเดิมตกหล่น
|
|
- มี `@VersionColumn({ name: 'version' })` แล้ว (delta 2026-06-15)
|
|
|
|
**prompt_type values**:
|
|
- `ocr_system`: OCR system prompt สำหรับ np-dms-ocr model (**ใหม่ — งาน 238**)
|
|
- `ocr_extraction`, `rag_query_prompt`, `rag_prep_prompt`, `classification_prompt`: มี validation ใน `create()` อยู่แล้ว
|
|
|
|
**Validation rules**:
|
|
- `ocr_extraction` template ต้องมี placeholder `{{ocr_text}}`
|
|
- `ocr_extraction` template อาจมี placeholder `{{master_data_context}}` (optional)
|
|
- `ocr_system` template ไม่มี required placeholders (free-form system prompt)
|
|
|
|
### API Contracts (contracts/api.yaml)
|
|
|
|
**Endpoints** (route จริง — `@Controller('ai/prompts')` + global prefix `/api`):
|
|
|
|
```yaml
|
|
GET /api/ai/prompts/{promptType} # List versions ของ type
|
|
POST /api/ai/prompts/{promptType} # Create version (header Idempotency-Key)
|
|
DELETE /api/ai/prompts/{promptType}/{versionNumber} # Delete version (ห้ามลบ active)
|
|
POST /api/ai/prompts/{promptType}/{versionNumber}/activate # Activate (header Idempotency-Key)
|
|
PATCH /api/ai/prompts/{promptType}/{versionNumber}/note # Update manual note
|
|
GET /api/ai/prompts/{promptType}/{versionNumber}/context-config # Get context config
|
|
PUT /api/ai/prompts/{promptType}/{versionNumber}/context-config # Update context config
|
|
|
|
# Sandbox (มีอยู่แล้วใน AiController @Controller('ai'))
|
|
POST /api/ai/admin/sandbox/ocr # Step 1 OCR
|
|
POST /api/ai/admin/sandbox/extract # Step 2 Extract
|
|
POST /api/ai/admin/sandbox/rag-prep # Step 3 RAG Prep (มีอยู่แล้ว)
|
|
```
|
|
|
|
**Request/Response DTOs** (ตรงกับของจริง):
|
|
|
|
```typescript
|
|
// CreateAiPromptDto (promptType เป็น path param ไม่ใช่ body)
|
|
{
|
|
template: string; // @MaxLength(4000)
|
|
contextConfig?: object;
|
|
}
|
|
|
|
// activate(): ของจริงใช้ pessimistic_write lock — ไม่รับ expectedVersion และยังไม่มี 409 flow
|
|
// ถ้าจะทำ optimistic locking (expectedVersion + HTTP 409) ตาม spec ต้องแก้ signature activate() เพิ่ม
|
|
```
|
|
|
|
### Quick Start (quickstart.md)
|
|
|
|
**Setup Steps**:
|
|
1. Database delta (ADR-009: edit SQL directly, **ไม่ใช้ TypeORM migration**): seed default `ocr_system` prompt — คอลัมน์ `version` มีอยู่แล้ว
|
|
2. Backend: ขยาย `ai-prompts.service.ts`/`ai-prompts.controller.ts` ที่มีอยู่ (ไม่สร้างไฟล์ใหม่)
|
|
3. Frontend: เพิ่ม PromptManagementTabs component แล้ว build ด้วย `pnpm --filter lcbp3-frontend build`
|
|
4. Sidecar: Deploy updated app.py with systemPrompt parameter support (ต้อง spike ยืนยันก่อน)
|
|
5. Seed data: Insert default OCR system prompt if table empty (created_by = user_id ของ superadmin)
|
|
|
|
## Next Steps
|
|
|
|
Run `/speckit-tasks` เพื่อ generate tasks.md จาก plan นี้
|