Appearance
AI Inference
@jennifersoft/apm-core/services의 LLM 서비스 문서입니다.
이 문서는 @jennifersoft/apm-core@^1.2.9 기준으로 정리했습니다.
현재 services 서브패스의 공개 export는 아래 두 가지입니다.
| export | 역할 |
|---|---|
LlmInferenceService | 엔진 생성, 선택, 로컬 모델 로드, 스트리밍 추론 |
LlmModelService | 로컬 모델 옵션 해석, 캐시 확인, 다운로드, 캐시 삭제 |
LlmInferenceService는 내부적으로 Server -> MediaPipe -> ONNX -> Browser 순서의 엔진을 만들고 선택합니다.LlmModelService는 새로 추가된 모델 자산 전용 서비스로, MediaPipe/ONNX 모델의 Cache API 수명주기를 담당합니다.
아키텍처 및 엔진 선택 흐름
내부 엔진 우선순위
| 우선순위 | 내부 엔진 | 타입 | 런타임 | 특징 |
|---|---|---|---|---|
| 1 | ServerLlmEngine | server | WebSocket | 서버 프록시가 있으면 최우선으로 사용합니다. |
| 2 | MediaPipeLlmEngine | mediapipe | WASM | Cache API에 저장된 MediaPipe 모델을 로드합니다. |
| 3 | OnnxLlmEngine | onnx | WASM/WebGPU | Transformers.js 기반 ONNX 실행, WebGPU 우선 후 WASM 폴백입니다. |
| 4 | BrowserLlmEngine | browser | Chrome Built-in AI | Gemini Nano 기반 브라우저 내장 AI 폴백입니다. |
선택 로직
LlmInferenceService생성 시 내부적으로 기본 엔진 4개를 고정 순서로 생성합니다.autoSelectEngine(preferredType)에 타입을 넘기면 해당 엔진을 우선 current engine으로 선택하려 시도합니다.config가 있으면server -> mediapipe -> onnx -> browser순서로 설정 기반 선택을 먼저 시도합니다.- 설정 기반 선택이 실패하면 각 엔진의
initialize()를 순서대로 호출해 사용 가능한 엔진을 찾습니다. - MediaPipe/ONNX가 선택되더라도 모델 자산이 캐시에 없으면 실제
loadModel()또는predict()시점에Please download it first.오류가 발생할 수 있습니다.
activeEngine getter로 현재 선택된 엔진 메타데이터를 확인할 수 있습니다.
NOTE
preferredType를 넘긴 autoSelectEngine()은 현재 구현상 strict availability 보장용 API가 아닙니다.
실제 사용 가능 여부를 엄격히 확인해야 할 때는 activate(type)를 먼저 호출하는 편이 안전합니다.
공개 설정 타입
typescript
type LlmServiceOptions = {
serverWsPath?: string;
onnxWasmPath?: string;
mediaPipeWasmPath?: string;
};
type LlmServiceConfig = {
server?: {
url?: string;
};
onnx?: {
onnxModelPath?: string;
modelName?: string;
modelFile?: string;
fileSize?: number;
dtype?: string;
useGpu?: boolean;
maxTokens?: number;
};
mediapipe?: {
mediaPipeWasmPath?: string;
mediaPipeModelPath?: string;
modelName?: string;
modelFile?: string;
fileSize?: number;
maxTokens?: number;
temperature?: number;
forceF32?: boolean;
};
};기본 경로는 아래와 같습니다.
| 항목 | 기본값 |
|---|---|
serverWsPath | /ws/chat |
onnxWasmPath | /script/onnx-wasm |
mediaPipeWasmPath | /script/mediapipe-wasm |
| 기본 ONNX 모델 API 경로 | /api-v2/onnx |
| 기본 MediaPipe 모델 API 경로 | /api-v2/mediapipe |
LlmInferenceService
생성 방법
설정을 직접 넘기거나, ILlmContextAdapter 기반으로 생성할 수 있습니다.
typescript
import { LlmInferenceService } from '@jennifersoft/apm-core/services';
const service = new LlmInferenceService(
{
server: {
url: 'https://llm.example.com',
},
onnx: {
onnxModelPath: '/api-v2/onnx',
modelName: 'Qwen3',
modelFile: 'onnx/model_q4.onnx',
fileSize: 1_234_567_890,
useGpu: true,
maxTokens: 8192,
},
mediapipe: {
mediaPipeModelPath: '/api-v2/mediapipe',
modelName: 'gemma-3n',
modelFile: 'model.task',
fileSize: 987_654_321,
maxTokens: 8192,
temperature: 0.8,
},
},
{
serverWsPath: '/ws/chat',
onnxWasmPath: '/script/onnx-wasm',
mediaPipeWasmPath: '/script/mediapipe-wasm',
}
);typescript
import { LlmInferenceService } from '@jennifersoft/apm-core/services';
const service = LlmInferenceService.fromAdapter(adapter, {
serverWsPath: '/ws/chat',
onnxWasmPath: '/script/onnx-wasm',
mediaPipeWasmPath: '/script/mediapipe-wasm',
});fromAdapter()는 adapter.getServerConfig()와 저장소 키(llmLocalModelCacheV4, llmLocalModelCacheV4-mediapipe)를 읽어 내부 LlmServiceConfig를 자동 생성합니다.
저장 포맷은 modelName:modelFile[:fileSize]입니다.
주요 메서드
| 메서드 | 설명 |
|---|---|
activate(type) | 특정 엔진의 경량 가용성 체크 후 활성화합니다. |
autoSelectEngine(preferredType?) | 기본적으로 설정과 가용성 기준으로 엔진을 선택합니다. preferredType 지정 시 해당 엔진을 우선 current engine으로 선택하려 시도합니다. |
loadOnnxModel(config?, onProgress?) | ONNX 모델을 실제 로드합니다. 결과로 { device, gpuFallback }를 반환합니다. |
loadMediaPipeModel(config?, onProgress?) | MediaPipe 모델을 실제 로드합니다. |
predict(options) | 현재 엔진으로 스트리밍 추론을 수행합니다. |
abort() | 현재 추론을 중단합니다. |
destroy() | 모든 엔진 리소스를 정리합니다. |
기본 사용 예제
typescript
import { LlmInferenceService } from '@jennifersoft/apm-core/services';
const service = LlmInferenceService.fromAdapter(adapter, {
serverWsPath: '/ws/chat',
onnxWasmPath: '/script/onnx-wasm',
mediaPipeWasmPath: '/script/mediapipe-wasm',
});
const engine = await service.autoSelectEngine();
console.log(engine.metadata.type, engine.metadata.modelName);
let outputText = '';
for await (const chunk of service.predict({
systemPrompt: '간결하게 답변해줘.',
userPrompt: '현재 클러스터 상태를 요약해줘.',
extraParams: {
maxNewTokens: 1024,
},
})) {
outputText += chunk;
}
console.log(outputText);
service.destroy();로컬 모델 로드 예제
typescript
const onnxResult = await service.loadOnnxModel(undefined, (progress) => {
console.log(progress.stage, progress.progress, progress.currentFile);
});
console.log(onnxResult.device, onnxResult.gpuFallback);
await service.loadMediaPipeModel(undefined, (progress) => {
console.log(progress.stage, progress.progress, progress.currentFile);
});IMPORTANT
LlmInferenceService가 MediaPipe/ONNX를 선택했다고 해서 모델 다운로드가 끝났다는 뜻은 아닙니다.
로컬 모델 자산은 먼저 LlmModelService 또는 별도 앱 로직으로 캐시에 준비해야 합니다.
LlmModelService
LlmModelService는 새로 추가된 모델 자산 전용 서비스입니다.LlmInferenceService가 추론에 집중하도록, 로컬 모델 캐시 확인/다운로드/정리 책임을 분리합니다.
생성 방법
typescript
import { LlmModelService } from '@jennifersoft/apm-core/services';
const modelService = new LlmModelService(
{
onnx: {
onnxModelPath: '/api-v2/onnx',
modelName: 'Qwen3',
modelFile: 'onnx/model_q4.onnx',
fileSize: 1_234_567_890,
},
mediapipe: {
mediaPipeModelPath: '/api-v2/mediapipe',
modelName: 'gemma-3n',
modelFile: 'model.task',
fileSize: 987_654_321,
},
},
{
onnxWasmPath: '/script/onnx-wasm',
mediaPipeWasmPath: '/script/mediapipe-wasm',
}
);typescript
import { LlmModelService } from '@jennifersoft/apm-core/services';
const modelService = LlmModelService.fromAdapter(adapter);주요 메서드
| 메서드 | 설명 |
|---|---|
getOnnxLoadOptions() | 설정에서 ONNX 로드 옵션을 해석합니다. |
getMediaPipeLoadOptions() | 설정에서 MediaPipe 로드 옵션을 해석합니다. |
isModelCached(type) | onnx 또는 mediapipe 캐시 준비 여부를 확인합니다. |
downloadModel(type, onProgress?) | 타입별 다운로드를 수행합니다. |
clearCaches(type) | 타입별 캐시 삭제를 수행합니다. |
isOnnxModelCached(config?) | ONNX 필수 자산이 모두 캐시에 있는지 확인합니다. |
downloadOnnxModel(config?, onProgress?) | ONNX 자산을 스트리밍 다운로드 후 Cache API에 저장합니다. |
clearOnnxCaches() | ONNX 관련 캐시를 정리합니다. |
isMediaPipeModelCached(config?) | MediaPipe 모델 파일 캐시 여부를 확인합니다. |
downloadMediaPipeModel(config?, onProgress?) | MediaPipe 모델 파일을 다운로드합니다. |
clearMediaPipeCaches() | MediaPipe 관련 캐시를 정리합니다. |
기본 사용 예제
typescript
import { LlmModelService } from '@jennifersoft/apm-core/services';
const modelService = LlmModelService.fromAdapter(adapter);
if (!(await modelService.isModelCached('onnx'))) {
const downloaded = await modelService.downloadModel(
'onnx',
({ progress, currentFile }) => {
console.log(progress, currentFile);
}
);
if (!downloaded) {
throw new Error('ONNX 모델 다운로드에 실패했습니다.');
}
}ONNX 다운로드 범위
downloadOnnxModel()은 단일 .onnx 파일만 받지 않습니다. 아래 자산을 함께 처리합니다.
- 필수 루트 파일:
config.json - 선택 루트 파일:
tokenizer_config.json,generation_config.json,special_tokens_map.json - 토크나이저 파일:
tokenizer.json또는tokenizer.model, 없으면vocab.json + merges.txt - 실제 모델 파일: 예)
onnx/model_q4.onnx - 추가 데이터 파일: 모델 파일이
.onnx로 끝나면${modelFile}_data를 선택적으로 시도합니다.
캐시 확인도 동일한 규칙을 사용합니다.
즉 ONNX는 모델 파일 하나만 있어서는 cached=true가 아니며, 필수 설정 파일과 토크나이저 자산까지 있어야 합니다.
MediaPipe 다운로드 범위
downloadMediaPipeModel()은 mediaPipeModelPath/modelName/modelFile URL의 단일 모델 파일을 스트리밍 다운로드해 mediapipe-llm-models-v1 캐시에 저장합니다.
다운로드/캐시 동작 메모
- 다운로드는
fetch스트림 기반이며, 브라우저가 지원하면 OPFS를 임시 버퍼로 사용한 뒤 Cache API에 저장합니다. - 진행률 콜백은 주 모델 파일 기준으로만 전달됩니다. ONNX의 보조 자산 다운로드는 내부적으로 처리됩니다.
- 런타임에
caches또는fetch가 없으면 다운로드는 실패(false)합니다. clearOnnxCaches()는transformers,xenova,onnx,huggingface키워드를 포함한 캐시를 삭제합니다.clearMediaPipeCaches()는mediapipe,genai,llm키워드를 포함한 캐시를 삭제합니다.
엔진별 동작 요약
| 타입 | 활성 조건 | 주의 사항 |
|---|---|---|
server | config.server.url이 있고 WebSocket 초기화에 성공 | serverWsPath 기본값은 /ws/chat입니다. |
mediapipe | mediaPipeModelPath + modelName + modelFile 구성 존재 | 실제 모델 파일은 캐시에 먼저 준비되어 있어야 합니다. |
onnx | onnxModelPath + modelName + modelFile 구성 존재 | WebGPU 우선, 실패 시 WASM으로 폴백합니다. |
browser | LanguageModel.availability()가 available 또는 after-download | 데스크톱 Chrome 127+ 권장입니다. |
NOTE
services 공개 export는 현재 LlmInferenceService, LlmModelService만 제공합니다.
엔진 클래스는 내부 구현으로 취급하고, 문서와 앱 코드는 서비스 레벨 API 기준으로 사용하는 것을 권장합니다.
브라우저 요구 사항
| 엔진 | 권장 환경 | 비고 |
|---|---|---|
ServerLlmEngine | WebSocket 연결이 가능한 브라우저 | 백엔드 프록시 필요 |
MediaPipeLlmEngine | 데스크톱 Chrome 121+, Edge 121+, Firefox 139+ | WebGPU/WebAssembly 필요, 모바일/iOS 미지원 |
OnnxLlmEngine | Chrome 121+ 권장 | WebAssembly 필요, WebGPU 사용 시 성능 향상 |
BrowserLlmEngine | 데스크톱 Chrome 127+ | Chrome Built-in AI(window.ai.languageModel) 사용 가능 상태 필요 |
LlmInferenceOptions
typescript
interface LlmInferenceOptions {
userPrompt: string;
systemPrompt?: string;
imageContent?: string;
extraParams?: {
maxNewTokens?: number;
temperature?: number;
topK?: number;
topP?: number;
[key: string]: unknown;
};
signal?: AbortSignal;
}인터랙티브 데모 (Gemini Nano)
Gemini Nano 설정 및 다운로드
현재 데모는 LlmInferenceService.activate('browser')로 내부 browser 엔진을 활성화하며,
브라우저 내장 API 접근은 내부적으로window.ai.languageModel을 우선 보고, 없으면 window.LanguageModel을 사용합니다.
로컬 개발 환경에서 위 데모를 직접 확인할 때는 Chrome 공식 Prompt API 가이드 기준으로localhost에서 아래 플래그를 먼저 켜야 할 수 있습니다.
chrome://flags/#optimization-guide-on-device-model→Enabled- Gemini Nano Prompt API 플래그 → 현재 Chrome 공식 문서가 두 이름을 함께 사용합니다.
chrome://flags/#prompt-api-for-gemini-nano-multimodal-input- 일부 문서/빌드에서는
chrome://flags/#prompt-api-for-gemini-nano또는Enabled multilingual - Chrome를
Relaunch
NOTE
위 2개 Prompt API 플래그 이름 차이는 Chrome 공식 문서 페이지 간 표기 차이입니다.
현재 사용 중인 Chrome 빌드에서 실제로 보이는 항목을 사용하면 됩니다.
Gemini Nano 모델은 별도 파일을 직접 내려받는 방식이 아니라,
브라우저가 첫 LanguageModel.create() 호출 시 자동으로 다운로드합니다.
- 사전 확인:
await LanguageModel.availability()또는await window.ai.languageModel.availability() - 상태 값:
unavailable,downloadable,downloading,available apm-core내부 browser 엔진은 브라우저별 호환을 위해 legacy 상태값after-download도 사용 가능 상태로 처리합니다.- 최초 다운로드 시작 조건: 사용자의 의미 있는 상호작용 이후
create()호출 - 진행률 확인:
create({ monitor(...) })의downloadprogress이벤트 사용 - 설치/로그 확인:
chrome://on-device-internals - 디스크 여유 공간이 다운로드 후 10GB 미만으로 내려가면 모델이 제거될 수 있으며, 이후 다시
create()가 호출되면 재다운로드됩니다.
javascript
const LM = window.LanguageModel ?? window.ai?.languageModel;
const availability = await LM.availability();
if (availability === 'downloadable' || availability === 'downloading') {
await LM.create({
monitor(m) {
m.addEventListener('downloadprogress', (e) => {
console.log(`Downloaded ${Math.round(e.loaded * 100)}%`);
});
},
});
}참고:
- Prompt API
- Get started with built-in AI
- Inform users of model download
- Understand built-in model management in Chrome
- Debug Gemini Nano
주의 사항
NOTE
스트리밍 응답은 chunk 단위 문자열이므로 outputText += chunk 형태로 누적해야 합니다.
IMPORTANT
MediaPipe/ONNX 추론에는 WASM 경로와 모델 경로 정보가 필요하며, 모델 자산은 사전에 캐시에 적재되어 있어야 합니다.
Browser 엔진만 별도 모델 파일 없이 브라우저 환경만으로 동작합니다.
IMPORTANT
VitePress 환경에서는 <ClientOnly> 내부에서만 브라우저 API에 접근해야 합니다.
SSR 중 window, navigator, window.ai를 직접 참조하면 빌드가 실패할 수 있습니다.
의존성
bash
pnpm add @jennifersoft/apm-core@^1.2.9text
@jennifersoft/apm-core/services
├── LlmInferenceService
└── LlmModelService| 패키지 | 버전 | 역할 |
|---|---|---|
@jennifersoft/apm-core | ^1.2.9 | LLM 서비스 및 타입 제공 |
@mediapipe/tasks-genai | transitive dependency | MediaPipe LLM 런타임 |
@huggingface/transformers | transitive dependency | ONNX 모델 로딩 및 추론 |
| Chrome Built-in AI | 브라우저 제공 | Gemini Nano 브라우저 내장 모델 |