LLM 추상화 계층, 프로바이더 어댑터, 가격표, 예산 가드 등 LLM 관련 작업이면 무조건 이 스킬. 모델 변경, 프로바이더 추가, 비용 계산, 토큰 카운트 등 모든 LLM 인프라를 다룬다. Triggers: "LLM 모델 변경", "프로바이더 추가", "비용 추정", "예산 가드", "pricing table", "모델 비교", "LLM 비용", "토큰 사용량", "모델 추가", "LLM 설정", "Gemini", "OpenAI", "API key", "model pricing", "budget cap", "token count", "adapter", "factory".
packages/core/src/llm/ 디렉토리 (6 파일):
| 파일 | 역할 |
|---|---|
types.ts | 공유 타입 정의 (LLMProvider, LLMModelId, TokenUsage, CostEstimate, ActualCost) |
factory.ts | 팩토리 함수 (createLLMClient), 기본 모델 결정, 가용 프로바이더 탐색 |
gemini.ts | Gemini 어댑터 (GeminiAdapter) - @google/genai SDK 사용 |
openai.ts | OpenAI 어댑터 (OpenAIAdapter) - Responses API + JSON 안정성 보호 |
pricing.ts | 가격표 (MODEL_PRICING_TABLE), 비용 계산, 예산 가드레일 |
index.ts | Barrel export |
types.ts에서 정의. 모든 어댑터가 구현하는 공통 인터페이스:
interface LLMProvider {
readonly name: LLMProviderName; // "gemini" | "openai"
generateContent(prompt: string, options: LLMGenerationOptions): Promise<LLMGenerationResult>;
countTokens(text: string, model?: string): Promise<number>;
}
LLMGenerationOptions: systemPrompt, responseMimeType, maxOutputTokens, modelLLMGenerationResult: text, tokenUsage, modelId, actualCost?LLMModelId: { provider: LLMProviderName, model: string }factory.ts의 createLLMClient(provider):
adapterCache (Map)로 어댑터 싱글톤 캐싱"gemini" -> GeminiAdapter, "openai" -> OpenAIAdapterError throw기본 모델 결정 (getDefaultModelId()):
ANKI_SPLITTER_DEFAULT_LLM_PROVIDER 환경변수 확인ANKI_SPLITTER_DEFAULT_LLM_MODEL 환경변수 또는 provider별 기본 모델가용 프로바이더 확인 (getAvailableProviders()):
GEMINI_API_KEY 설정 여부 -> geminiOPENAI_API_KEY 설정 여부 -> openaigemini.ts)@google/genai (GoogleGenAI)genAI 변수)gemini-3-flash-previewcountTokens: SDK 네이티브 client.models.countTokens() 사용response.usageMetadata에서 추출openai.ts)openai (dynamic import)client.responses.create())gpt-5.4-minideveloper (OpenAI Responses API에서 system 대신 developer 사용)text.format.type = "json_object" + markdown code fence 제거content.type === "refusal" 감지 시 에러 throwcountTokens: 휴리스틱 추정 (한국어 보정 x1.5, safety x1.3)getClient() 차이: GeminiAdapter의
getClient()는 동기(sync), OpenAIAdapter의getClient()는 비동기(async) -- dynamic import로 번들 최적화하기 때문.
pricing.ts)가격표, 비용 계산, 예산 가드 상세는 references/pricing.md 참조.
핵심 함수:
getModelPricing(provider, model) -- 가격 조회estimateCost() / computeCost() -- 사전/사후 비용 계산checkBudget(estimatedCostUsd, clientBudgetCapUsd?) -- 이중 예산 가드getServerBudgetCapUsd() -- 서버 사이드 예산 캡 조회 (env 기본 $1.0)GET /api/llm/models (packages/server/src/routes/llm.ts)응답:
{
"models": [
{
"provider": "gemini",
"model": "gemini-3-flash-preview",
"displayName": "Gemini 3 Flash Preview",
"inputPricePerMillionTokens": 0.15,
"outputPricePerMillionTokens": 0.6
}
],
"defaultModelId": { "provider": "gemini", "model": "gemini-3-flash-preview" },
"budgetCapUsd": 1.0,
"availableProviders": ["gemini", "openai"]
}
budgetCapUsd는 서버에서 getServerBudgetCapUsd() 호출 결과packages/web/src/components/ui/model-badge.tsx)provider, model?, className?SplitWorkspace.tsx, SplitHistory.tsxmodel-badge.tsx에서 export)< $0.001 -> 소수점 6자리< $0.01 -> 소수점 4자리| 변수 | 용도 | 기본값 |
|---|---|---|
GEMINI_API_KEY | Gemini API 인증 | (필수) |
OPENAI_API_KEY | OpenAI API 인증 | (선택) |
ANKI_SPLITTER_DEFAULT_LLM_PROVIDER | 기본 프로바이더 | gemini |
ANKI_SPLITTER_DEFAULT_LLM_MODEL | 기본 모델 | provider별 기본값 |
ANKI_SPLITTER_BUDGET_CAP_USD | 서버 예산 상한 | 1.0 |
references/architecture.md -- LLM 모듈 아키텍처 상세, 새 프로바이더 추가 가이드references/pricing.md -- MODEL_PRICING_TABLE 전체, 비용 계산/예산 가드 로직references/troubleshooting.md -- API 키 미설정, 예산 초과, 지원되지 않는 모델 시 동작