690601:2143 ADR-032-232 #09
CI / CD Pipeline / build (push) Successful in 4m34s
CI / CD Pipeline / deploy (push) Successful in 4m0s

This commit is contained in:
2026-06-01 21:43:19 +07:00
parent 69db07fe35
commit 2cc07ee2e5
6 changed files with 27 additions and 16 deletions
+11 -6
View File
@@ -86,6 +86,7 @@ import { AiEnabledGuard } from './guards/ai-enabled.guard';
import { InjectRedis } from '@nestjs-modules/ioredis'; import { InjectRedis } from '@nestjs-modules/ioredis';
import Redis from 'ioredis'; import Redis from 'ioredis';
import { FileStorageService } from '../../common/file-storage/file-storage.service'; import { FileStorageService } from '../../common/file-storage/file-storage.service';
import { SandboxOcrEngineType } from './services/sandbox-ocr-engine.service';
import { AiMigrationCheckpointService } from './ai-migration-checkpoint.service'; import { AiMigrationCheckpointService } from './ai-migration-checkpoint.service';
import { import {
MigrationErrorLogDto, MigrationErrorLogDto,
@@ -538,7 +539,7 @@ export class AiController {
}, },
engineType: { engineType: {
type: 'string', type: 'string',
enum: ['auto', 'tesseract', 'typhoon-ocr-3b'], enum: ['auto', 'tesseract', 'typhoon-ocr-3b', 'typhoon-ocr1.5-3b'],
description: 'OCR engine ที่ต้องการใช้ (default: auto)', description: 'OCR engine ที่ต้องการใช้ (default: auto)',
}, },
}, },
@@ -560,12 +561,16 @@ export class AiController {
const attachment = await this.fileStorageService.upload(file, user.user_id); const attachment = await this.fileStorageService.upload(file, user.user_id);
const requestPublicId = uuidv7(); const requestPublicId = uuidv7();
// ตรวจสอบและ normalize engineType ให้เป็นค่าที่ valid // ตรวจสอบและ normalize engineType ให้เป็นค่าที่ valid
const validEngineTypes = ['auto', 'tesseract', 'typhoon-ocr-3b'] as const; const validEngineTypes = [
const resolvedEngineType: 'auto' | 'tesseract' | 'typhoon-ocr-3b' = 'auto',
validEngineTypes.includes( 'tesseract',
engineType as 'auto' | 'tesseract' | 'typhoon-ocr-3b' 'typhoon-ocr-3b',
'typhoon-ocr1.5-3b',
] as const;
const resolvedEngineType: SandboxOcrEngineType = validEngineTypes.includes(
engineType as SandboxOcrEngineType
) )
? (engineType as 'auto' | 'tesseract' | 'typhoon-ocr-3b') ? (engineType as SandboxOcrEngineType)
: 'auto'; : 'auto';
const jobId = await this.aiQueueService.enqueueSandboxJob( const jobId = await this.aiQueueService.enqueueSandboxJob(
'sandbox-ocr-only', 'sandbox-ocr-only',
@@ -184,7 +184,7 @@ export class TyphoonOcrProcessor extends WorkerHost {
const log = this.auditLogRepo.create({ const log = this.auditLogRepo.create({
documentPublicId: params.documentPublicId, documentPublicId: params.documentPublicId,
aiModel: 'typhoon-ocr', aiModel: 'typhoon-ocr',
modelName: 'scb10x/typhoon-ocr-3b', modelName: 'scb10x/typhoon-ocr1.5-3b',
modelType: params.engineType, modelType: params.engineType,
status: params.status, status: params.status,
processingTimeMs: params.processingTimeMs, processingTimeMs: params.processingTimeMs,
@@ -319,7 +319,7 @@ export class OcrService {
new Blob([fileBuffer], { type: 'application/pdf' }), new Blob([fileBuffer], { type: 'application/pdf' }),
'upload.pdf' 'upload.pdf'
); );
form.append('engine', 'typhoon-ocr-3b'); form.append('engine', 'typhoon-ocr1.5-3b');
const response = await axios.post<OcrSidecarResponse>( const response = await axios.post<OcrSidecarResponse>(
`${this.ocrApiUrl}/ocr-upload`, `${this.ocrApiUrl}/ocr-upload`,
form, form,
@@ -332,7 +332,7 @@ export class OcrService {
await this.writeAuditLog({ await this.writeAuditLog({
documentPublicId: input.documentPublicId, documentPublicId: input.documentPublicId,
aiModel: 'typhoon-ocr', aiModel: 'typhoon-ocr',
modelName: 'typhoon-ocr-3b', modelName: 'typhoon-ocr1.5-3b',
modelType: 'typhoon-ocr', modelType: 'typhoon-ocr',
status: AiAuditStatus.SUCCESS, status: AiAuditStatus.SUCCESS,
processingTimeMs: durationMs, processingTimeMs: durationMs,
@@ -9,7 +9,11 @@ import axios from 'axios';
import * as fs from 'fs'; import * as fs from 'fs';
import { OcrService } from './ocr.service'; import { OcrService } from './ocr.service';
export type SandboxOcrEngineType = 'auto' | 'tesseract' | 'typhoon-ocr-3b'; export type SandboxOcrEngineType =
| 'auto'
| 'tesseract'
| 'typhoon-ocr-3b'
| 'typhoon-ocr1.5-3b';
interface SandboxOcrSidecarResponse { interface SandboxOcrSidecarResponse {
text?: string; text?: string;
@@ -7,6 +7,7 @@
# - 2026-05-30: เปลี่ยนจาก PaddleOCR เป็น Tesseract OCR เพื่อความเข้ากันได้กับ CPU เก่า # - 2026-05-30: เปลี่ยนจาก PaddleOCR เป็น Tesseract OCR เพื่อความเข้ากันได้กับ CPU เก่า
# - 2026-05-30: เพิ่ม OpenCV preprocessing (threshold, denoise) และ DPI 300 เพื่อเพิ่มความแม่นยำ # - 2026-05-30: เพิ่ม OpenCV preprocessing (threshold, denoise) และ DPI 300 เพื่อเพิ่มความแม่นยำ
# - 2026-06-01: เพิ่ม POST /ocr-upload รับ multipart file โดยตรง ไม่ต้องพึ่ง shared volume mount # - 2026-06-01: เพิ่ม POST /ocr-upload รับ multipart file โดยตรง ไม่ต้องพึ่ง shared volume mount
# - 2026-06-01: เปลี่ยน TYPHOON_OCR_MODEL default เป็น scb10x/typhoon-ocr1.5-3b
import os import os
import logging import logging
@@ -37,7 +38,7 @@ OCR_CHAR_THRESHOLD = int(os.getenv("OCR_CHAR_THRESHOLD", "100"))
MAX_PAGES = int(os.getenv("OCR_MAX_PAGES", "0")) # 0 = ทุกหน้า MAX_PAGES = int(os.getenv("OCR_MAX_PAGES", "0")) # 0 = ทุกหน้า
OCR_LANG = os.getenv("OCR_LANG", "tha+eng") # Tesseract language code (tha+eng = Thai + English) OCR_LANG = os.getenv("OCR_LANG", "tha+eng") # Tesseract language code (tha+eng = Thai + English)
OLLAMA_API_URL = os.getenv("OLLAMA_API_URL", "http://host.docker.internal:11434") OLLAMA_API_URL = os.getenv("OLLAMA_API_URL", "http://host.docker.internal:11434")
TYPHOON_OCR_MODEL = os.getenv("TYPHOON_OCR_MODEL", "scb10x/typhoon-ocr-3b") TYPHOON_OCR_MODEL = os.getenv("TYPHOON_OCR_MODEL", "scb10x/typhoon-ocr1.5-3b")
TYPHOON_OCR_TIMEOUT = int(os.getenv("TYPHOON_OCR_TIMEOUT", "120")) TYPHOON_OCR_TIMEOUT = int(os.getenv("TYPHOON_OCR_TIMEOUT", "120"))
# PSM 3 = Fully automatic page segmentation (เหมาะกับเอกสารที่มี layout หลายส่วน เช่น วันที่/เลขที่) # PSM 3 = Fully automatic page segmentation (เหมาะกับเอกสารที่มี layout หลายส่วน เช่น วันที่/เลขที่)
# OEM 1 = LSTM only (ดีกว่า legacy engine) # OEM 1 = LSTM only (ดีกว่า legacy engine)
@@ -146,7 +147,7 @@ def _process_pdf_doc(doc: fitz.Document, selected_engine: str, max_pages: int) -
engineUsed="fast-path", engineUsed="fast-path",
) )
if selected_engine == "typhoon-ocr-3b": if selected_engine in ("typhoon-ocr-3b", "typhoon-ocr1.5-3b"):
typhoon_text_parts = [] typhoon_text_parts = []
for i in pages_to_process: for i in pages_to_process:
page = doc[i] page = doc[i]
@@ -162,7 +163,7 @@ def _process_pdf_doc(doc: fitz.Document, selected_engine: str, max_pages: int) -
ocrUsed=True, ocrUsed=True,
pageCount=page_count, pageCount=page_count,
charCount=len(typhoon_text), charCount=len(typhoon_text),
engineUsed="typhoon-ocr-3b", engineUsed="typhoon-ocr1.5-3b",
) )
logger.info(f"Slow path (Tesseract): {total_chars} chars too few") logger.info(f"Slow path (Tesseract): {total_chars} chars too few")
@@ -9,6 +9,7 @@
# - 2026-05-30: Revert volumes กลับไปใช้ Windows Z: drive bind mount (แทน CIFS volume driver ที่พัง) # - 2026-05-30: Revert volumes กลับไปใช้ Windows Z: drive bind mount (แทน CIFS volume driver ที่พัง)
# - 2026-06-01: ลบ volumes ออกทั้งหมด — backend ส่ง file content ผ่าน multipart /ocr-upload แทน # - 2026-06-01: ลบ volumes ออกทั้งหมด — backend ส่ง file content ผ่าน multipart /ocr-upload แทน
# ไม่ต้องการ shared storage อีกต่อไป # ไม่ต้องการ shared storage อีกต่อไป
# - 2026-06-01: เปลี่ยน TYPHOON_OCR_MODEL เป็น scb10x/typhoon-ocr1.5-3b
# #
# วิธีรัน: # วิธีรัน:
# docker compose up -d --build # docker compose up -d --build
@@ -36,7 +37,7 @@ services:
# ─── Typhoon OCR via Ollama (ADR-032) ─────────────────────────────────── # ─── Typhoon OCR via Ollama (ADR-032) ───────────────────────────────────
# ชี้ไปที่ Ollama ที่รันบน Desk-5439 ผ่าน LAN IP (ไม่ใช่ host.docker.internal) # ชี้ไปที่ Ollama ที่รันบน Desk-5439 ผ่าน LAN IP (ไม่ใช่ host.docker.internal)
OLLAMA_API_URL: "http://192.168.10.100:11434" OLLAMA_API_URL: "http://192.168.10.100:11434"
TYPHOON_OCR_MODEL: "scb10x/typhoon-ocr-3b" TYPHOON_OCR_MODEL: "scb10x/typhoon-ocr1.5-3b"
# Timeout 120 วินาที/หน้า (budget สำหรับ 3B model บน RTX 2060 Super) # Timeout 120 วินาที/หน้า (budget สำหรับ 3B model บน RTX 2060 Super)
TYPHOON_OCR_TIMEOUT: "120" TYPHOON_OCR_TIMEOUT: "120"
logging: logging: