feat(ai-runtime): complete ai runtime policy refactor (ADR-035)
CI / CD Pipeline / build (push) Successful in 4m16s
CI / CD Pipeline / deploy (push) Successful in 11m51s

This commit is contained in:
2026-06-12 08:07:15 +07:00
parent 71c5e88181
commit 0227b7b982
63 changed files with 3566 additions and 451 deletions
+25 -5
View File
@@ -56,9 +56,16 @@ function normalizeLoadedModels(value: unknown): VramLoadedModelView[] {
}
return value.map((item, index) => {
if (typeof item === 'string') {
const name = item.toLowerCase();
let normName = item;
if (name.includes('ocr') || name.includes('typhoon-np-dms-ocr')) {
normName = 'np-dms-ocr';
} else if (name.includes('typhoon') || name.includes('np-dms-ai')) {
normName = 'np-dms-ai';
}
return {
modelId: `${item}-${index}`,
modelName: item,
modelName: normName,
};
}
if (item && typeof item === 'object') {
@@ -68,10 +75,17 @@ function normalizeLoadedModels(value: unknown): VramLoadedModelView[] {
name?: string;
vramUsageMB?: number;
};
const modelName = model.modelName ?? model.name ?? `model-${index + 1}`;
const rawName = model.modelName ?? model.name ?? `model-${index + 1}`;
const name = rawName.toLowerCase();
let normName = rawName;
if (name.includes('ocr') || name.includes('typhoon-np-dms-ocr')) {
normName = 'np-dms-ocr';
} else if (name.includes('typhoon') || name.includes('np-dms-ai')) {
normName = 'np-dms-ai';
}
return {
modelId: model.modelId ?? modelName,
modelName,
modelId: model.modelId ?? rawName,
modelName: normName,
vramUsageMB: model.vramUsageMB,
};
}
@@ -122,7 +136,13 @@ export default function AiAdminConsolePage() {
return res as SandboxProject[];
},
});
const healthOllamaModels = ensureArray<string>(health?.ollama?.models);
const rawHealthOllamaModels = ensureArray<string>(health?.ollama?.models);
const healthOllamaModels = Array.from(new Set(rawHealthOllamaModels.map((m) => {
const name = m.toLowerCase();
if (name.includes('ocr') || name.includes('typhoon-np-dms-ocr')) return 'np-dms-ocr';
if (name.includes('typhoon') || name.includes('np-dms-ai')) return 'np-dms-ai';
return m;
})));
const healthQdrantCollections = ensureArray<string>(health?.qdrant?.collections);
const vramLoadedModels = normalizeLoadedModels(vramStatus?.loadedModels);
const sandboxProjects = ensureArray<SandboxProject>(projects);
@@ -592,7 +592,7 @@ export default function OcrSandboxPromptManager() {
</CardTitle>
<Badge variant="outline" className="text-xs">
{ocrResult.engineUsed === 'typhoon-np-dms-ocr'
? 'Typhoon OCR'
? 'np-dms-ocr'
: ocrResult.ocrUsed
? 'Tesseract'
: 'Fast Path (Text Layer)'}
@@ -601,7 +601,7 @@ export default function OcrSandboxPromptManager() {
<CardContent className="pt-4">
{ocrResult.fallbackUsed && (
<div className="mb-3 rounded-md border border-amber-500/20 bg-amber-500/5 px-3 py-2 text-xs text-amber-600 dark:text-amber-400">
Typhoon OCR unavailable. Fallback to Tesseract was used for this run.
np-dms-ocr unavailable. Fallback to Tesseract was used for this run.
</div>
)}
<div className="relative rounded-md bg-muted p-4 font-mono text-xs overflow-auto max-h-[200px] border border-border/10">
+18
View File
@@ -15,6 +15,7 @@
// - 2026-06-02: normalize VRAM response ให้รองรับ field names จาก backend ปัจจุบันและรูปแบบ loadedModels แบบเดิม
import api from '../api/client';
import { AiJobResponse } from '../../types/ai';
export interface AiAdminSettings {
aiFeaturesEnabled: boolean;
@@ -315,6 +316,23 @@ export const adminAiService = {
const { data } = await api.post(`/ai/ocr-engines/${encodeURIComponent(engineId)}/select`, {});
return extractData<{ activeEngineName: string }>(data);
},
submitAiJob: async (
type: string,
documentPublicId?: string,
attachmentPublicId?: string,
payload?: Record<string, unknown>,
projectPublicId?: string
): Promise<AiJobResponse> => {
const { data } = await api.post('/ai/jobs', {
type,
documentPublicId,
attachmentPublicId,
payload,
projectPublicId,
});
return extractData<AiJobResponse>(data);
},
};
export interface OcrEngineResponse {
+9
View File
@@ -44,5 +44,14 @@
"delete_confirm": "Delete this pattern?",
"loading": "Loading...",
"not_found": "Intent not found"
},
"ai_runtime_policy": {
"error_model_key_forbidden": "model.key is not allowed. The system selects the model automatically.",
"error_execution_profile_forbidden": "executionProfile is not allowed in the request payload.",
"error_temperature_forbidden": "temperature override is not allowed. Runtime parameters are managed by policy.",
"error_top_p_forbidden": "top_p override is not allowed. Runtime parameters are managed by policy.",
"error_max_tokens_forbidden": "maxTokens override is not allowed. Runtime parameters are managed by policy.",
"error_cpu_timeout": "Retrieval operation timed out on CPU fallback. Please retry later.",
"error_large_context_unauthorized": "The large-context profile requires administrator privileges."
}
}
+9 -1
View File
@@ -76,6 +76,14 @@
"processing": "กำลังประมวลผลด้วย Typhoon LLM...",
"error_vram": "VRAM ไม่เพียงพอสำหรับโหลดโมเดล Typhoon LLM",
"error_timeout": "หมดเวลาการประมวลผล LLM (120 วินาที)"
},
"ai_runtime_policy": {
"error_model_key_forbidden": "ไม่อนุญาตให้ระบุ model.key ระบบจะเลือกโมเดลให้อัตโนมัติ",
"error_execution_profile_forbidden": "ไม่อนุญาตให้ระบุ executionProfile ใน payload",
"error_temperature_forbidden": "ไม่อนุญาตให้ override ค่า temperature พารามิเตอร์ถูกควบคุมโดย Runtime Policy",
"error_top_p_forbidden": "ไม่อนุญาตให้ override ค่า top_p พารามิเตอร์ถูกควบคุมโดย Runtime Policy",
"error_max_tokens_forbidden": "ไม่อนุญาตให้ override ค่า maxTokens พารามิเตอร์ถูกควบคุมโดย Runtime Policy",
"error_cpu_timeout": "การดึงข้อมูลหมดเวลาขณะใช้ CPU fallback กรุณาลองใหม่อีกครั้ง",
"error_large_context_unauthorized": "Profile large-context ต้องการสิทธิ์ผู้ดูแลระบบ"
}
}
+10
View File
@@ -74,3 +74,13 @@ export interface AiPaginatedResult<T> {
limit: number;
totalPages: number;
}
export type ExecutionProfile = 'interactive' | 'standard' | 'quality' | 'deep-analysis';
export interface AiJobResponse {
jobId: string;
status: 'queued' | 'completed' | 'failed';
modelUsed: 'np-dms-ai' | 'np-dms-ocr';
effectiveProfile: ExecutionProfile;
queueName: 'ai-realtime' | 'ai-batch';
}