Skip to content

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 수명주기를 담당합니다.

아키텍처 및 엔진 선택 흐름

내부 엔진 우선순위

우선순위내부 엔진타입런타임특징
1ServerLlmEngineserverWebSocket서버 프록시가 있으면 최우선으로 사용합니다.
2MediaPipeLlmEnginemediapipeWASMCache API에 저장된 MediaPipe 모델을 로드합니다.
3OnnxLlmEngineonnxWASM/WebGPUTransformers.js 기반 ONNX 실행, WebGPU 우선 후 WASM 폴백입니다.
4BrowserLlmEnginebrowserChrome Built-in AIGemini Nano 기반 브라우저 내장 AI 폴백입니다.

선택 로직

  1. LlmInferenceService 생성 시 내부적으로 기본 엔진 4개를 고정 순서로 생성합니다.
  2. autoSelectEngine(preferredType)에 타입을 넘기면 해당 엔진을 우선 current engine으로 선택하려 시도합니다.
  3. config가 있으면 server -> mediapipe -> onnx -> browser 순서로 설정 기반 선택을 먼저 시도합니다.
  4. 설정 기반 선택이 실패하면 각 엔진의 initialize()를 순서대로 호출해 사용 가능한 엔진을 찾습니다.
  5. 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 키워드를 포함한 캐시를 삭제합니다.

엔진별 동작 요약

타입활성 조건주의 사항
serverconfig.server.url이 있고 WebSocket 초기화에 성공serverWsPath 기본값은 /ws/chat입니다.
mediapipemediaPipeModelPath + modelName + modelFile 구성 존재실제 모델 파일은 캐시에 먼저 준비되어 있어야 합니다.
onnxonnxModelPath + modelName + modelFile 구성 존재WebGPU 우선, 실패 시 WASM으로 폴백합니다.
browserLanguageModel.availability()available 또는 after-download데스크톱 Chrome 127+ 권장입니다.

NOTE

services 공개 export는 현재 LlmInferenceService, LlmModelService만 제공합니다.
엔진 클래스는 내부 구현으로 취급하고, 문서와 앱 코드는 서비스 레벨 API 기준으로 사용하는 것을 권장합니다.

브라우저 요구 사항

엔진권장 환경비고
ServerLlmEngineWebSocket 연결이 가능한 브라우저백엔드 프록시 필요
MediaPipeLlmEngine데스크톱 Chrome 121+, Edge 121+, Firefox 139+WebGPU/WebAssembly 필요, 모바일/iOS 미지원
OnnxLlmEngineChrome 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에서 아래 플래그를 먼저 켜야 할 수 있습니다.

  1. chrome://flags/#optimization-guide-on-device-modelEnabled
  2. Gemini Nano Prompt API 플래그 → 현재 Chrome 공식 문서가 두 이름을 함께 사용합니다.
  3. chrome://flags/#prompt-api-for-gemini-nano-multimodal-input
  4. 일부 문서/빌드에서는 chrome://flags/#prompt-api-for-gemini-nano 또는 Enabled multilingual
  5. 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)}%`);
            });
        },
    });
}

참고:

주의 사항

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.9
text
@jennifersoft/apm-core/services
  ├── LlmInferenceService
  └── LlmModelService
패키지버전역할
@jennifersoft/apm-core^1.2.9LLM 서비스 및 타입 제공
@mediapipe/tasks-genaitransitive dependencyMediaPipe LLM 런타임
@huggingface/transformerstransitive dependencyONNX 모델 로딩 및 추론
Chrome Built-in AI브라우저 제공Gemini Nano 브라우저 내장 모델