9. LLM 비용 최적화 & AI Observability

LLM API 비용은 일반 서버 비용과 달리 예측하기 어렵고 순식간에 폭발합니다. 비용을 통제하려면 어디서 토큰이 낭비되는지 보여야 하고, 그것이 AI Observability입니다 — AI 버전의 APM(Application Performance Monitoring).

1. 비용 최적화 4개 레이어

비용을 낮추는 4단계 — 위에서부터 차례대로 적용
① Semantic Cache
~40% 절감
의미가 같은 질문 재사용 — LLM 호출 자체를 건너뜀
② Prompt Cache
~25% 절감
반복 시스템 프롬프트 캐시 — 입력 토큰 90% 할인
③ Model Routing
~20% 절감
간단 질문은 저렴한 모델, 복잡한 것만 비싼 모델
④ Output Control
~10% 절감
max_tokens 제한, JSON 구조화 출력으로 장황함 제거

2. Semantic Caching — 의미가 같은 질문은 한 번만

Semantic Cache 구현 (Java + Redis + pgvector)
@Service
public class SemanticCacheService {
    private final EmbeddingService embedding;
    private final VectorCacheRepo cache; // pgvector
    private static final float THRESHOLD = 0.92f;

    public Optional<String> get(String query) {
        float[] vec = embedding.embed(query);

        // 유사도 0.92 이상인 캐시 조회
        return cache.findSimilar(vec, THRESHOLD)
                    .map(CacheEntry::getResponse);
    }

    public void put(String query, String response) {
        float[] vec = embedding.embed(query);
        cache.save(new CacheEntry(query, vec, response,
            Instant.now().plus(Duration.ofHours(24))));
    }
}

// 사용
public String chat(String userMessage) {
    // 1. 캐시 먼저 확인
    var cached = semanticCache.get(userMessage);
    if (cached.isPresent()) {
        metrics.recordCacheHit(); // 비용 0
        return cached.get();
    }

    // 2. 캐시 미스 → LLM 호출
    String response = llm.call(userMessage);
    semanticCache.put(userMessage, response);
    return response;
}
Model Routing — 복잡도 판단
@Service
public class ModelRouter {

    public String route(String prompt) {
        int complexity = assessComplexity(prompt);

        return switch (complexity) {
            case 1 -> "claude-haiku-4-5"; // $0.25/1M
            case 2 -> "claude-sonnet-4-6"; // $3/1M
            default -> "claude-opus-4-8";  // $15/1M
        };
    }

    private int assessComplexity(String prompt) {
        // 간단한 휴리스틱
        boolean isSimple =
            prompt.length() < 100 &&
            !prompt.contains("코드") &&
            !prompt.contains("분석");

        boolean isComplex =
            prompt.length() > 500 ||
            prompt.contains("아키텍처") ||
            prompt.contains("설계");

        if (isSimple) return 1;
        if (isComplex) return 3;
        return 2;
    }
}

3. AI Observability — Langfuse로 무엇을 보는가

측정 항목왜 중요한가이상 신호
토큰 사용량 / 비용 어느 엔드포인트가 비용을 많이 쓰는지 파악 특정 사용자가 일반의 10배 이상 사용
레이턴시 (Time to First Token) 사용자 체감 응답 속도 p99 > 10초 → 모델 변경 또는 캐싱 검토
Agent 스텝 수 루프 가능성 탐지 평균 3스텝인데 특정 요청이 8스텝 이상
Tool 호출 성공률 어느 Tool이 자주 오류 나는지 파악 특정 Tool 오류율 > 5%
Cache Hit Rate Semantic Cache 효과 측정 Hit Rate < 20% → 쿼리 다양성 높음, 캐시 TTL 조정
Langfuse 계측 (Java SDK)
// Langfuse: LLM Observability 오픈소스 툴
// 각 LLM 호출을 Trace로 기록

Langfuse langfuse = new Langfuse(
    System.getenv("LANGFUSE_PUBLIC_KEY"),
    System.getenv("LANGFUSE_SECRET_KEY"));

public String callLLM(String userId, String prompt) {
    // Trace 시작
    Trace trace = langfuse.trace(
        TraceParams.builder()
            .name("chat-completion")
            .userId(userId)
            .build());

    Generation gen = trace.generation(
        GenerationParams.builder()
            .name("llm-call")
            .model("claude-sonnet-4-6")
            .input(prompt)
            .build());

    String response = llm.call(prompt);

    // 토큰 사용량 기록
    gen.end(GenerationEndParams.builder()
        .output(response)
        .usage(Usage.of(inputTokens, outputTokens))
        .build());

    return response;
}
비용 알람 설정
// 일일 비용 초과 알람
@Scheduled(cron = "0 * * * * *") // 매분 체크
public void checkDailyCost() {
    double todayCost = metricsRepo
        .getDailyCost(LocalDate.now());

    // 일일 예산 80% 도달 시 슬랙 알람
    if (todayCost > DAILY_BUDGET * 0.8) {
        slack.send(
            "#ai-ops",
            String.format(
                "⚠️ AI 일일 비용 경고: $%.2f / $%.2f (%.0f%%)",
                todayCost, DAILY_BUDGET,
                todayCost / DAILY_BUDGET * 100
            )
        );
    }

    // 예산 초과 시 Circuit Breaker OPEN
    if (todayCost >= DAILY_BUDGET) {
        circuitBreaker.open();
        slack.send("#ai-ops", "🚨 일일 AI 예산 초과. 서비스 일시 중단.");
    }
}

4. 면접 대비 핵심 Q&A

Q1. "LLM 서비스의 비용을 어떻게 모니터링하고 통제하나요?"

두 단계로 나눕니다. 모니터링: Langfuse 같은 AI Observability 툴로 토큰 사용량·비용·레이턴시·Agent 스텝 수를 추적합니다. Grafana 대시보드에 일별 비용 그래프를 올리고, 예산 80%에서 슬랙 알람을 겁니다. 통제: Semantic Cache(40% 절감), Prompt Cache(25%), Model Routing(간단 쿼리는 Haiku), max_tokens 제한을 계층화합니다. 예산 초과 시 Circuit Breaker로 LLM 호출을 차단합니다.

Q2. "Semantic Cache의 threshold(임계값)를 어떻게 정하나요?"

트레이드오프 문제입니다. 임계값이 낮으면(0.80): 더 많은 질문이 캐시 히트 → 비용 절감 크지만 다른 의미의 질문에 엉뚱한 답변 반환 위험. 임계값이 높으면(0.98): 거의 동일한 질문만 히트 → 안전하지만 절감 효과 작음. 실무에서는 0.90~0.93 사이에서 시작해서 A/B 테스트로 조정합니다. 질문 유형에 따라 다르게 설정하기도 합니다 — FAQ처럼 정답이 정해진 경우는 낮게, 복잡한 분석 쿼리는 높게.

핵심 한 줄

비용 최적화 = Semantic Cache → Prompt Cache → Model Routing → Output Control 순서로 적용. Observability 없이는 어디서 돈이 새는지 모른다 — Langfuse로 토큰·레이턴시·스텝 수를 추적하라.