- Add ADR-036 unified OCR architecture (typhoon-ocr via Ollama) - Extend AI execution profiles for OCR sandbox configuration - Add comprehensive frontend test coverage (components, hooks, services) - Add backend test coverage for document-numbering services - Update OCR sidecar with typhoon-ocr integration - Add AI policy service and execution profile management - Update AGENTS.md and architecture documentation
34 KiB
ADR-036: Unified AI Model Architecture — Sandbox-Production Parity for np-dms-ai and np-dms-ocr
Status: Proposed Date: 2026-06-13 Decision Makers: Development Team, AI Integration Lead Supersedes: — (New Architecture) Amends: AI model testing and parameter management layer Related Documents:
- ADR-034: AI Model Change
- ADR-033: Active Model & OCR Management
- ADR-029: Dynamic Prompt Management
- CONTEXT.md
Grilling resolution (2026-06-13): ADR นี้เป็น enhance ของ Profile-Only Parameter Governance ที่มีอยู่ (
AiPolicyService+ai_execution_profiles) ไม่ใช่ การสร้างsystem_settingsparam store ใหม่ และไม่ supersede ADR-029/033. การตัดสินที่ resolved แล้ว: (1) production setting store =ai_execution_profiles; (2) draft (sandbox) store =ai_sandbox_profiles(แยกต่างหาก) — admin iterate ลง draft แล้วกด Apply = UPSERT draft → production row + DEL cache; (3) คง Snapshot semantics (params แช่แข็งลง job payload ณ dispatch); (4) systemPrompt อยู่ในai_prompts(Active Prompt) เท่านั้น; (5) OCR params = rowocr-extract+ columncanonical_model; (6) "OCR Sandbox" = Production Pipeline Sandbox (รัน pipeline เดียวกับ production). ดูCONTEXT.md→ Flagged ambiguities + Glossary (from ADR-036).
Context and Problem Statement
ปัจจุบันระบบใช้งานโมเดล AI สองตัวบน Desk-5439:
np-dms-ai:latest— โมเดลหลักสำหรับงานทั่วไป (แทนtyphoon2.5-np-dmsที่ยกเลิกแล้ว)np-dms-ocr:latest— โมเดลสำหรับ OCR (แทนtyphoon-np-dms-ocrที่ยกเลิกแล้ว)
ปัญหาหลัก:
- ชื่อโมเดลไม่สอดคล้อง — Repository ยังใช้ชื่อเก่า
typhoon2.5-np-dmsและtyphoon-np-dms-ocrแต่ Desk-5439 ใช้np-dms-aiและnp-dms-ocr - ไม่มีกลไกทดสอบและบันทึกค่า — Admin ไม่สามารถทดสอบ parameters (temperature, system prompt, etc.) ใน sandbox แล้ว apply ไป production ได้
- Sandbox กับ Production ใช้ params คนละชุด — แม้ "OCR Sandbox" (
processSandboxExtract/processSandboxAiExtract) จะรันเส้น pipeline เดียวกับ production (processMigrateDocument: OCR → Active Prompt → Master Data → LLM) แต่ sandbox hardcode{ num_ctx: 16384, num_predict: 4096 }ส่วน production ใช้snapshotParamsจาก profile → ผลทดสอบไม่สะท้อน production จริง (parity gap)
Concept (grilling resolved): "OCR Sandbox" จริงๆ คือ Production Pipeline Sandbox — sandbox ของ production pipeline ทั้งเส้น (ต่างแค่ไม่ commit DB) ไม่ใช่เครื่องมือทดสอบ OCR อย่างเดียว ดู
CONTEXT.mdglossary.
Decision Drivers
- Sandbox-Production Parity: ผลการทดสอบ parameters ทั้ง
np-dms-aiและnp-dms-ocrใน sandbox ต้องสามารถนำไปใช้ใน production ได้ 100% - Unified Testing & Apply Mechanism: กลไกเดียวกันสำหรับการทดสอบและบันทึกค่า parameters ไปใช้ใน production ทั้งสองโมเดล
- Dynamic Parameter Control: Admin สามารถแก้ไข parameters (temperature, system prompt, etc.) ใน sandbox แล้ว apply ไป production ได้ทันที ทั้ง
np-dms-aiและnp-dms-ocr - Sidecar-Centric Architecture: ทุก AI operation ผ่าน sidecar (จัดการ model lifecycle เอง) ไม่ว่าจะเป็น
np-dms-aiหรือnp-dms-ocr
Decision Outcome
1. Calibration บน Profile/Prompt Store ที่มีอยู่ (enhance)
ไม่สร้าง AiModelService + system_settings store ใหม่ — เติม write/apply path บนกลไกที่มี:
Draft → Apply → Production (2-layer):
┌──────────────────────────────────────────────┐
│ Sandbox: admin แก้ draft → ai_sandbox_profiles │ → persisted (ไม่กระทบ production)
│ Production Pipeline Sandbox อ่าน draft รันทดสอบ│
└──────────────────────────────────────────────┘
↓ (พอใจ → กด Apply to Production)
┌──────────────────────────────────────────────┐
│ applyProfile(): UPSERT ai_sandbox_profiles │ → ai_execution_profiles row
│ (+ DEL redis cache) │
│ systemPrompt → AiPromptService.activate │ → ai_prompts (ADR-029)
└──────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────┐
│ Production job → createJobPayload() snapshot │ → params แช่แข็ง ณ dispatch (คงเดิม)
│ → processor → sidecar (np-dms-ai / np-dms-ocr) │
└──────────────────────────────────────────────┘
SoT: production = ai_execution_profiles (รวม row ocr-extract); draft = ai_sandbox_profiles; systemPrompt = ai_prompts — ไม่มี param store ใน system_settings
2. Data Flow — Test & Apply Pattern
สำหรับทั้ง np-dms-ai และ np-dms-ocr:
[Sandbox UI Testing]
↓
[เลือกโมเดล: np-dms-ai หรือ np-dms-ocr]
↓
[ปรับ parameters: temperature, systemPrompt, etc.]
↓
[ทดสอบ → ดูผลลัพธ์]
↓ (พอใจผล)
[กด "Apply to Production"]
↓
[runtime params → ai_execution_profiles (row ตาม canonical_model) + DEL redis cache]
[systemPrompt → ai_prompts (activate version, ADR-029)]
↓
[Production Job → createJobPayload() snapshot params ณ dispatch → ใช้ค่าที่แช่แข็ง]
3. Parameter Scope
| โมเดล | Runtime params → ai_execution_profiles |
systemPrompt → ai_prompts (ADR-029) |
|---|---|---|
np-dms-ai |
temperature, topP, repeatPenalty, numCtx, maxTokens, keepAliveSeconds — ต่อ ExecutionProfile ที่ apply | Active Prompt ต่อ prompt_type |
np-dms-ocr |
temperature, topP, repeatPenalty, keepAliveSeconds (row ocr-extract; numCtx/maxTokens = NULL) |
Active Prompt ocr_extraction ({{ocr_text}}) |
ลบทิ้ง: key AI_MODEL_NP_DMS_AI_DEFAULTS / AI_MODEL_NP_DMS_OCR_DEFAULTS / OCR_PRODUCTION_DEFAULTS — ไม่ใช้ system_settings เป็น param store (OCR param set อ้างอิง sidecar contract app.py: temperature/top_p/repeat_penalty/keep_alive)
4. Parameter Hierarchy (ทั้งสองโมเดล)
| Level | Source | ใช้เมื่อไหร |
|---|---|---|
| Runtime Override | Job payload | ส่งค่าพิเศษเฉพาะ job |
| Production Defaults | DB (ai_execution_profiles row, snapshot ณ dispatch) |
ค่าที่ admin apply จาก sandbox |
| Service Defaults | Hardcoded AiPolicyService.defaultProfiles / Modelfile |
Fallback ถ้าไม่มี row/cache |
Implementation Details
1. Backend — Enhance AiPolicyService (ไม่สร้าง AiModelService)
File: backend/src/modules/ai/services/ai-policy.service.ts (MODIFY)
เติม write/apply method ลงบน service เดิม (ที่มี getProfileParameters() read path + Redis cache อยู่แล้ว):
getSandboxParameters(profileName)— อ่าน draft จากai_sandbox_profiles; ถ้าไม่มี draft → seed (clone) จาก production row ในai_execution_profilesแล้ว return (ไม่ fallback hardcoded ก่อน)saveSandboxDraft(profileName, params, userId)— UPSERT draft ลงai_sandbox_profilesresetSandboxToProduction(profileName, userId)— overwrite draft ด้วยค่า production row ปัจจุบันapplyProfile(profileName, userId)— copy draft จากai_sandbox_profiles→ UPSERTai_execution_profiles+DEL ai_execution_profiles:{profile}cache (admin only)getCanonicalModelName()/getProfileParameters()/createJobPayload()— คงเดิม (snapshot semantics, อ่าน production)
File: backend/src/modules/ai/entities/ai-execution-profile.entity.ts (MODIFY)
- เพิ่ม column
canonicalModel: 'np-dms-ai' | 'np-dms-ocr' - ทำ
numCtx/maxTokensเป็น nullable (OCR ไม่ใช้)
File: backend/src/modules/ai/entities/ai-sandbox-profile.entity.ts (NEW)
- mirror columns ของ
ai_execution_profiles— เป็น Sandbox Draft Profile (persisted) ที่ admin iterate ก่อน Apply - ค่าตั้งต้น seed จาก production row เมื่อยังไม่มี draft (ดู
getSandboxParameters()) — ไม่เริ่มจากค่าว่าง/hardcoded
applyProfile(profileName, userId) อ่าน draft จาก ai_sandbox_profiles → UPSERT ลง ai_execution_profiles + DEL cache; SandboxOcrEngineService ที่มีอยู่ คงไว้ (รับ params ที่ resolve จาก draft); systemPrompt apply → ai_prompts ผ่าน prompt service (ADR-029) ไม่ เก็บใน profiles
2. Backend — Processor Updates
File: backend/src/modules/ai/processors/ai-batch.processor.ts (MODIFY)
คงพฤติกรรมเดิม: processor ใช้ payload.snapshotParams ที่ถูกแช่แข็งไว้ตอน dispatch (ไม่ lazy-read setting ตอน process) — ส่งต่อไป sidecar
สิ่งที่ต้องเพิ่ม: ให้ createJobPayload('ocr-extract') ดึง params จาก row ocr-extract (canonical_model = np-dms-ocr) แทนการยืม profile standard
ปิด parity gap (สำคัญ): processSandboxExtract / processSandboxAiExtract ต้องเลิก hardcode { num_ctx: 16384, num_predict: 4096 } แล้วสร้าง generateOptions จาก ai_sandbox_profiles (Sandbox Draft Profile, schema เดียวกับ ai_execution_profiles) เพื่อให้ admin เห็นผลของค่าที่กำลังปรับก่อน Apply — ส่วน processMigrateDocument (production) อ่านจาก ai_execution_profiles ผ่าน snapshot เหมือนเดิม. หลัง Apply ค่าทั้งสองตารางจะตรงกัน → parity จริง
Sidecar จะรวม parameters จาก request เข้ากับ defaults ใน Modelfile
2.1 Dual-Model Snapshot & OCR Param Flow (Gap 1–4 resolved)
migrate-document/auto-fill-document เป็น dual-model job (OCR np-dms-ocr + LLM np-dms-ai) แต่ createJobPayload เดิม snapshot params ชุดเดียว (LLM) → OCR step ไม่ได้รับ tunable params ที่ admin ปรับ. แก้ดังนี้:
- Gap 4 — OCR row แยกจาก
ExecutionProfile:ocr-extractเป็น model-defaults row (key ด้วยcanonical_model/profile_name='ocr-extract') ไม่ใช่ สมาชิกของExecutionProfileunion (คง Canonical Profile Set = interactive/standard/quality/deep-analysis). เพิ่ม accessorgetModelDefaults('np-dms-ocr')แยกจากgetProfileParameters(profile) - Gap 3 — snapshot 2 ชุด (backward-compat):
AiJobPayloadคงsnapshotParams(LLM, ไม่แตะ processor LLM path) + เพิ่มocrSnapshotParams?: OcrTyphoonOptions(reuse type ที่มีอยู่ ={ temperature, topP, repeatPenalty }). populateocrSnapshotParamsเมื่อ pipeline ของ job รัน OCR (migrate-document/auto-fill-document/ocr-extract) - Gap 1 — wire ไป production OCR:
OcrDetectionInputเพิ่มtyphoonOptions?: OcrTyphoonOptions;OcrService.processWithTyphoonappendtemperature/topP/repeatPenaltyลง form (sidecar/ocr-uploadรับอยู่แล้ว);processMigrateDocumentส่งtyphoonOptions: job.data.ocrSnapshotParams - Gap 2 — keep_alive ไม่ freeze: กฎ quality params freeze / resource params lazy — temperature/top_p/repeat/num_ctx/max_tokens แช่แข็ง ณ dispatch; keep_alive มาจาก
calculateOcrResidency()(Adaptive OCR Residency, ADR-033) ณ process time ไม่อยู่ใน tunable set (สอดคล้องOcrTyphoonOptionsที่ไม่มี keep_alive อยู่แล้ว) - Audit:
snapshotParamsJson = { ...llmParams, ocr: ocrSnapshotParams }ใน audit row เดียว (per-step error log คงเดิม)
2.2 Master Data Context Parity (Gap 5 resolved)
processSandboxExtract/processSandboxAiExtract ปัจจุบันใช้ projectPublicId='default' → ส่ง undefined ไป aiPromptsService.resolveContext → skip master data lookup (ai-batch.processor.ts:552-557, 758-762). ส่วน processMigrateDocument ส่ง projectPublicId + contractPublicId จริงเสมอ (:973-978).
→ {{master_data_context}} ใน prompt ต่างกัน แม้ params ถูกต้อง → Production Pipeline Sandbox ไม่สมบูรณ์
แก้:
- Sandbox UI ให้ admin เลือก
projectPublicId(และcontractPublicIdoptional) ก่อนรันทดสอบ — ไม่อนุญาต'default' processSandboxExtract/processSandboxAiExtractส่ง ID จริงไปresolveContextเสมอ — ไม่มี special case'default'→undefinedaiPromptsService.resolveContextจะคืนค่า empty context ({}) ถ้า project/contract ไม่มี master data (production-ready behavior)
2.3 Apply Guardrails (Gap 6 resolved)
Apply to Production เป็น critical config change (กระทบงานทั้งระบบ) ต้องมี guardrails ตาม AGENTS.md:
| Guardrail | Requirement | Implementation |
|---|---|---|
| Idempotency | POST /api/ai/profiles/:profileName/apply ต้อง validate Idempotency-Key header (mandatory per AGENTS.md) |
@Header('Idempotency-Key') + Redis เก็บ key ที่ใช้แล้ว 5 นาที |
| CASL Permission | API ใหม่ต้องมี CASL guard + 4-Level RBAC | @UseGuards(CaslGuard) + action ai.apply_profile (subject: SystemSettings) — ใช้ permission system.manage_ai (admin) |
| Param Validation | class-validator (backend) + Zod (frontend) | DTO ApplyProfileDto ใช้ @IsNumber(), @Min(0), @Max(1) สำหรับ temperature/topP; @IsOptional() สำหรับ nullable |
| Audit Trail | Log ใคร apply, อะไร, old→new | ai_audit_logs table (มีอยู่แล้ว) — เพิ่ม row action='APPLY_PROFILE', userPublicId, profileName, oldValuesJson, newValuesJson, appliedAt |
| Range Guard | Temperature/topP ต้อง 0–1 | Service layer validation: if (temp < 0 || temp > 1) throw BusinessException |
2.4 Entity & Service Canonical Model (Gap 7 resolved)
AiExecutionProfileEntity ปัจจุบันไม่มี mapping สำหรับ canonical_model column (ที่จะเพิ่มใน SQL delta); getProfileParameters (:125) hardcode canonicalModel: 'np-dms-ai' แทนการอ่านจาก column → ถ้า row ocr-extract (canonical_model='np-dms-ocr') ถูกอ่านผ่าน path เดิม จะได้ค่าผิด
แก้:
- Entity เพิ่ม
@Column({ name: 'canonical_model', length: 20 }) canonicalModel!: string; getProfileParametersเปลี่ยนเป็นอ่านdbProfile.canonicalModelจาก column แทน hardcode (หรือ default เป็น'np-dms-ai'ถ้า column null)- สร้าง accessor ใหม่
getModelDefaults(canonicalModel: 'np-dms-ai' | 'np-dms-ocr')ที่ query ตามcanonical_modelcolumn โดยตรง (สำหรับ model-defaults row ไม่ผ่าน ExecutionProfile)
API signature:
@Post(':profileName/apply')
@UseGuards(CaslGuard)
async applyProfile(
@Param('profileName') profileName: string,
@Body() dto: ApplyProfileDto, // optional: { reason?: string }
@Headers('Idempotency-Key') idempotencyKey: string,
@CurrentUser() user: RequestWithUser,
): Promise<ApplyResultDto>
3. Backend — API Endpoints
File: backend/src/modules/ai/controllers/ai.controller.ts (ADD)
เพิ่ม endpoints สำหรับการทดสอบและบันทึกค่า parameters:
GET /api/ai/sandbox-profiles/:profileName— ดึง draft; ถ้าไม่มี → seed จาก production row แล้ว returnPUT /api/ai/sandbox-profiles/:profileName— บันทึก draft ลงai_sandbox_profiles(admin only)POST /api/ai/sandbox-profiles/:profileName/reset— reset draft = ค่า production row ปัจจุบันPOST /api/ai/profiles/:profileName/apply— Apply to Production: UPSERT draft →ai_execution_profiles+ DEL cache (admin only, CASL-guarded)GET /api/ai/profiles/:profileName— ดึงค่า production defaults ปัจจุบัน (read-only panel)
systemPrompt apply → ใช้ endpoint ของ ADR-029 (ai_prompts) ที่มีอยู่ — ไม่ สร้าง prompt endpoint ซ้ำ
คงไว้: API submit job ยังปฏิเสธ (400) ถ้า caller แนบ executionProfile/model/temperature (Profile-Only Parameter Governance)
4. Backend — Service Wiring (ไม่ consolidate/ลบ)
Keep: backend/src/modules/ai/services/sandbox-ocr-engine.service.ts — ยังใช้รับ ephemeral OCR override (temperature/topP/repeatPenalty) ไป sidecar; ไม่ลบ
Modify: backend/src/modules/ai/ai.module.ts
- ไม่ลบ provider เดิม; AiPolicyService มีอยู่แล้ว
Modify: backend/src/modules/ai/controllers/ai-sandbox.controller.ts
- เพิ่ม apply endpoint ที่เรียก
AiPolicyService.applyProfile()(CASL admin) — ไม่ inject service ใหม่
5. Sidecar — Dynamic Params (คง endpoint เดิม)
File: specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/app.py (MODIFY ถ้าจำเป็น)
sidecar รับ override อยู่แล้ว (/ocr, /ocr-upload → temperature/top_p/repeat_penalty/keep_alive) — ไม่ต้องสร้าง /generate ใหม่
- ถ้า np-dms-ai ต้องการ dynamic params เพิ่มเติม → ขยาย contract ของ endpoint ที่มี ไม่สร้างใหม่
- model lifecycle (unload/load) ตาม ADR-033 Adaptive OCR Residency คงเดิม
6. Frontend — Admin AI Console
File: frontend/lib/services/admin-ai.service.ts (ADD)
เพิ่ม functions สำหรับการทดสอบและบันทึกค่า parameters:
testModel(modelName, options)— ทดสอบโมเดลด้วย parameters ที่กำหนดsaveModelDefaults(modelName, params)— บันทึกค่า parameters ไปใช้ใน productiongetModelDefaults(modelName)— ดึงค่า parameters ปัจจุบันที่ใช้ใน production
File: frontend/components/admin/ai/ModelTestingPanel.tsx (NEW) หรือปรับ OcrSandboxPromptManager.tsx
สร้าง UI สำหรับทดสอบและบันทึกค่า parameters รองรับทั้งสองโมเดล:
- Model Selector — Dropdown เลือก
np-dms-aiหรือnp-dms-ocr - Model Parameters — Inputs สำหรับ temperature, topP, repeatPenalty
- System Prompt — Textarea สำหรับแก้ไข system prompt
- Test Area — พื้นที่ทดสอบ input และดูผลลัพธ์
- Current Production Defaults — Read-only panel แสดงค่าที่ใช้ใน production
- Apply to Production — Button สำหรับบันทึกค่าปัจจุบันไป production
7. Database — Extend ai_execution_profiles (ไม่ใช้ system_settings)
Delta (ADR-009, edit SQL directly): specs/03-Data-and-Storage/deltas/2026-06-13-extend-ai-execution-profiles-ocr.sql
-- ADR-036: ขยาย ai_execution_profiles → รองรับ np-dms-ocr (canonical_model) + OCR row
ALTER TABLE ai_execution_profiles
ADD COLUMN canonical_model VARCHAR(20) NOT NULL DEFAULT 'np-dms-ai' AFTER profile_name;
-- OCR ไม่ใช้ num_ctx/max_tokens → ทำเป็น nullable
ALTER TABLE ai_execution_profiles MODIFY COLUMN num_ctx INT NULL;
ALTER TABLE ai_execution_profiles MODIFY COLUMN max_tokens INT NULL;
-- seed row OCR (params ตาม sidecar contract: temperature/top_p/repeat_penalty/keep_alive)
INSERT INTO ai_execution_profiles
(profile_name, canonical_model, temperature, top_p, max_tokens, num_ctx, repeat_penalty, keep_alive_seconds, is_active)
VALUES
('ocr-extract', 'np-dms-ocr', 0.100, 0.100, NULL, NULL, 1.100, 0, 1)
ON DUPLICATE KEY UPDATE canonical_model = VALUES(canonical_model);
-- draft (sandbox) store — mirror columns ของ production; admin iterate ก่อน Apply
CREATE TABLE ai_sandbox_profiles (
id INT AUTO_INCREMENT PRIMARY KEY,
profile_name VARCHAR(50) NOT NULL UNIQUE,
canonical_model VARCHAR(20) NOT NULL DEFAULT 'np-dms-ai',
temperature DECIMAL(4,3) NOT NULL,
top_p DECIMAL(4,3) NOT NULL,
max_tokens INT NULL,
num_ctx INT NULL,
repeat_penalty DECIMAL(5,3) NOT NULL,
keep_alive_seconds INT NOT NULL,
updated_by INT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
ไม่มี INSERT ลง system_settings สำหรับ parameter — systemPrompt จัดการผ่าน ai_prompts (ADR-029)
Migration Plan
Phase 1: Backend Enhance (ไม่มี breaking change)
- รัน delta
2026-06-13-extend-ai-execution-profiles-ocr.sql— เพิ่มcanonical_model+ rowocr-extract - แก้
ai-execution-profile.entity.ts— เพิ่มcanonicalModel, ทำnumCtx/maxTokensnullable - เติม
AiPolicyService.applyProfile()— write + invalidate cache - แก้
createJobPayload('ocr-extract')— ดึงจาก rowocr-extract(snapshot คงเดิม) - เพิ่ม apply/test/get endpoints ใน controller (CASL admin)
Phase 2: Sidecar (ถ้าจำเป็น)
- คง
/ocr,/ocr-uploadที่รับ override อยู่แล้ว — ขยาย contract เฉพาะถ้า np-dms-ai ต้องการ - model lifecycle ตาม ADR-033 คงเดิม
Phase 3: Frontend Update
- เพิ่ม API functions ใน
admin-ai.service.ts— apply/test/get profile - ปรับ
OcrSandboxPromptManager.tsxหรือเพิ่ม panel — รองรับ apply runtime params (np-dms-ai + ocr-extract) - systemPrompt ใช้ Prompt Version UI เดิม (ADR-029)
Phase 4: Data
- row
ocr-extractseed ผ่าน delta (Phase 1); ค่า np-dms-ai profiles เดิมไม่ต้อง migrate - ถ้าไม่มี row/cache → fallback
AiPolicyService.defaultProfiles
Rollback Strategy
หากพบปัญหา:
- Immediate: Revert commit — กลับไปใช้การเรียก Ollama โดยตรง (แต่จะสูญเสียความสามารถในการปรับ parameters แบบ dynamic)
- Sidecar: Rollback
app.pyไป version เดิมที่ไม่รองรับ dynamic parameters - Database: rollback delta — ลบ column
canonical_model+ rowocr-extract(rollback SQL คู่กัน); ค่า np-dms-ai profiles เดิมไม่กระทบ
Impact on Related ADRs
| ADR | Section | Impact |
|---|---|---|
| ADR-034 | Model Stack | ต้องแก้ — canonical names np-dms-ai/np-dms-ocr (runtime tag เป็น ops detail ใน Modelfile/ENV) |
| ADR-033 | Adaptive OCR Residency | คงเดิม — ไม่แตะ residency/model lifecycle; ADR-036 เติมแค่ write/apply path |
| ADR-032 | Typhoon OCR Integration | คงเดิม — sidecar contract เดิม (/ocr, /ocr-upload) |
| ADR-029 | Dynamic Prompt Management | คงเดิม — systemPrompt apply ผ่าน ai_prompts (Active Prompt) ที่มีอยู่ |
| Profile-Only Governance | AiPolicyService + ai_execution_profiles |
enhance — เติม write path, ไม่ supersede |
Glossary Updates (CONTEXT.md)
บันทึกแล้วใน CONTEXT.md → Glossary Updates (from ADR-036) + Flagged ambiguities:
| Term | Definition |
|---|---|
| Apply to Production | admin บันทึกค่าที่ทดสอบใน sandbox → runtime params ลง ai_execution_profiles (+invalidate Redis), systemPrompt ลง ai_prompts; มีผลกับงานใหม่เท่านั้น (snapshot) |
| Sandbox Parameter Override | ค่า ephemeral จาก testing ที่ไม่ persist จนกว่าจะกด Apply |
| Tunable Production Defaults | row ใน ai_execution_profiles (รวม ocr-extract) — ไม่ใช่ store แยกใน system_settings |
Files to Modify
| File | Change Type |
|---|---|
backend/src/modules/ai/services/ai-policy.service.ts |
MODIFY (เพิ่ม applyProfile()) |
backend/src/modules/ai/entities/ai-execution-profile.entity.ts |
MODIFY (+canonicalModel, nullable numCtx/maxTokens) |
backend/src/modules/ai/entities/ai-sandbox-profile.entity.ts |
NEW (draft store) |
backend/src/modules/ai/interfaces/execution-policy.interface.ts |
MODIFY (+ocrSnapshotParams? ใน AiJobPayload — ไม่ เพิ่ม ocr-extract ใน ExecutionProfile) |
backend/src/modules/ai/services/ocr.service.ts |
MODIFY (+typhoonOptions ใน OcrDetectionInput; processWithTyphoon ส่ง temp/topP/repeat) |
backend/src/modules/ai/processors/ai-batch.processor.ts |
MODIFY (createJobPayload OCR snapshot; processMigrateDocument ส่ง typhoonOptions; sandbox อ่าน draft) |
backend/src/modules/ai/controllers/ai.controller.ts |
MODIFY (apply/test/get endpoints, CASL admin, Gap 6: Idempotency-Key validation) |
backend/src/modules/ai/dto/apply-profile.dto.ts |
NEW (Gap 6: class-validator @Min(0) @Max(1) สำหรับ params) |
backend/src/modules/ai/dto/apply-result.dto.ts |
NEW (return applied profile + audit log id) |
backend/src/modules/ai/controllers/ai-sandbox.controller.ts |
MODIFY |
backend/src/modules/ai/services/sandbox-ocr-engine.service.ts |
KEEP (ephemeral override) |
backend/src/modules/ai/services/ollama.service.ts |
MODIFY (ENV/Modelfile tag เท่านั้น — runtime detail) |
frontend/lib/services/admin-ai.service.ts |
MODIFY |
frontend/components/admin/ai/OcrSandboxPromptManager.tsx |
MODIFY (เพิ่ม apply runtime params; Gap 5: เพิ่ม project/contract selector ไม่อนุญาต 'default') |
specs/03-Data-and-Storage/deltas/2026-06-13-extend-ai-execution-profiles-ocr.sql (+rollback) |
NEW |
CONTEXT.md |
MODIFY (Glossary + Flagged ambiguities — done) |
specs/06-Decision-Records/ADR-034-AI-model-change.md |
MODIFY (canonical names) |
AGENTS.md |
MODIFY (canonical names) |
Required Naming Alignment (Mandatory Before Implementation)
หมายเหตุ (grilling): การเปลี่ยนชื่อในส่วนนี้คือการ sync runtime tag (Ollama model name บน Desk-5439 + ENV
OLLAMA_MODEL_MAIN/OLLAMA_MODEL_OCR) ให้ตรงกับ Canonical Model Identity (np-dms-ai/np-dms-ocr) ตาม Single-Name Canonical Model Policy — ไม่ใช่การสร้าง canonical mapping ใหม่ เพราะAiPolicyService.getCanonicalModelName()map tag → canonical อยู่แล้ว (รองรับทั้ง tag เก่า/ใหม่). Mock ใน test ที่ใช้typhoon2.5-np-dms:latestเป็น runtime tag ของ/api/psไม่จำเป็นต้องแก้ (mapper รองรับอยู่).
ชื่อ model บน Desk-5439 (Ollama) ได้เปลี่ยนเป็น canonical names ใหม่แล้ว ต้องอัปเดต repository ให้สอดคล้อง:
Model Names (New Canonical)
| Role | Old Name | New Name (Desk-5439) | Status |
|---|---|---|---|
| Main AI | typhoon2.5-np-dms:latest |
np-dms-ai:latest |
ต้องแก้ |
| OCR | typhoon-np-dms-ocr:latest |
np-dms-ocr:latest |
ต้องแก้ |
| Embedding | nomic-embed-text |
(ไม่เปลี่ยน) | OK |
Files ที่ต้องแก้ไขชื่อ Model
Backend (Code)
| File | Line | แก้จาก | เป็น |
|---|---|---|---|
backend/src/modules/ai/services/ollama.service.ts |
58 | 'typhoon2.5-np-dms:latest' |
'np-dms-ai:latest' |
backend/src/modules/ai/services/ollama.service.ts |
62 | 'typhoon-np-dms-ocr:latest' |
'np-dms-ocr:latest' |
backend/src/modules/ai/services/ocr.service.ts |
86 | engineName: 'typhoon-np-dms-ocr:latest' |
engineName: 'np-dms-ocr:latest' |
backend/src/modules/ai/services/ai-settings.service.ts |
(ค้นหา) | typhoon2.5-np-dms |
np-dms-ai |
backend/src/modules/ai/processors/ai-batch.processor.spec.ts |
70 | 'typhoon2.5-np-dms:latest' |
'np-dms-ai:latest' |
backend/src/modules/ai/processors/ai-batch.processor.spec.ts |
70 | 'typhoon-np-dms-ocr:latest' |
'np-dms-ocr:latest' |
Frontend
| File | แก้จาก | เป็น |
|---|---|---|
frontend/components/admin/ai/OcrSandboxPromptManager.tsx |
typhoon-np-dms-ocr |
np-dms-ocr |
frontend/app/(admin)/admin/ai/page.tsx |
typhoon2.5-np-dms |
np-dms-ai |
Sidecar (OCR)
| File | แก้จาก | เป็น |
|---|---|---|
specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/app.py |
typhoon-np-dms-ocr |
np-dms-ocr |
specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/docker-compose.yml |
typhoon-np-dms-ocr |
np-dms-ocr |
Documentation
| File | แก้จาก | เป็น |
|---|---|---|
specs/06-Decision-Records/ADR-034-AI-model-change.md |
typhoon2.5-np-dms |
np-dms-ai |
specs/06-Decision-Records/ADR-034-AI-model-change.md |
typhoon-np-dms-ocr |
np-dms-ocr |
AGENTS.md |
typhoon2.5-np-dms |
np-dms-ai |
AGENTS.md |
typhoon-np-dms-ocr |
np-dms-ocr |
Migration Steps (Naming Alignment)
-
Desk-5439: สร้างโมเดลใหม่ (ถ้ายังไม่มี)
# สร้างจาก Modelfile ที่มีอยู่ ollama create np-dms-ai -f ./np-dms-ai.model.md ollama create np-dms-ocr -f ./np-dms-ocr.model.md # ลบโมเดลเก่า (optional — รอ deploy สำเร็จก่อน) ollama rm typhoon2.5-np-dms typhoon-np-dms-ocr -
Repository: แก้ไขทุกไฟล์ที่ระบุในตารางด้านบน
-
Deploy: ทดสอบว่า API เรียกโมเดลใหม่ได้
-
Cleanup: ลบโมเดลเก่าบน Desk-5439 (หลัง verify สำเร็จ)
สำหรับ Implementation: ดูไฟล์ใน specs/200-fullstacks/236-unified-ocr-architecture/ (สร้างเมื่อเริ่ม implement)