Skip to content

AI Inference

@jennifersoft/apm-core/services의 LLM 서비스 문서입니다.
이 문서는 @jennifersoft/apm-core@^1.2.20 기준으로 정리했습니다.

현재 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;
    };
};

기본 경로는 아래와 같습니다.

항목기본값
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]입니다.

주요 메서드

메서드설명
supportImage (getter)현재 활성 엔진의 비전(이미지) 지원 여부를 반환합니다.
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?)타입별 다운로드를 수행합니다. (mediapipe 타입일 때 Gemma 웹 모델 다운로드)
clearCaches(type)타입별 캐시 삭제를 수행합니다. (LlmCacheDeleteResult 반환)
isOnnxModelCached(config?)ONNX 필수 자산이 모두 캐시에 있는지 확인합니다.
downloadOnnxModel(config?, onProgress?)ONNX 자산을 스트리밍 다운로드 후 Cache API에 저장합니다.
clearOnnxCaches()ONNX 관련 캐시를 정리합니다. (LlmCacheDeleteResult 반환)
isMediaPipeModelCached(config?)MediaPipe 모델 파일 캐시 여부를 확인합니다.
downloadMediaPipeModel(config?, onProgress?)MediaPipe 모델 파일을 다운로드합니다.
clearMediaPipeCaches()MediaPipe 관련 캐시를 정리합니다. (LlmCacheDeleteResult 반환)

LlmCacheDeleteResult

typescript
interface LlmCacheDeleteResult {
    success: boolean;
    successRate: number; // 0.0 ~ 1.0 (성공한 캐시 삭제 비율)
}

기본 사용 예제

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가 아니며, 필수 설정 파일과 토크나이저 자산까지 있어야 합니다.

Gemma 및 MediaPipe 모델 다운로드 범위

downloadMediaPipeModel()mediaPipeModelPath/modelName/modelFile URL의 단일 Gemma 웹 모델 파일(*web.litertlm)을 스트리밍 다운로드해 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 구성 존재실제 모델 파일(*web.litertlm)은 캐시에 먼저 준비되어 있어야 합니다.
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.20
text
@jennifersoft/apm-core/services
  ├── LlmInferenceService
  └── LlmModelService
패키지버전역할
@jennifersoft/apm-core^1.2.20LLM 서비스 및 타입 제공
@litert-lm/coretransitive dependencyLiteRT-LM 웹 LLM 런타임
@huggingface/transformerstransitive dependencyONNX 모델 로딩 및 추론
Chrome Built-in AI브라우저 제공Gemini Nano 브라우저 내장 모델