기술

RAG 챗봇 1건당 LLM이 어디서 몇 번 호출되는지 분석했습니다

2026.05.0110분 읽기

"채팅 한 번에 LLM이 몇 번 호출되지?"

이게 처음 시작된 질문이었어요. RAG 챗봇이 답변을 만들어주는 동안, 안에서 LLM이 정확히 몇 번 호출되는지를 명확히 모른 채로 운영하고 있었거든요. "아마 답변 생성에서 한 번 부르겠지" 정도의 막연한 인식이었습니다.

비용 청구서를 받아보고 깜짝 놀랐어요. 생각보다 호출 수가 훨씬 많았거든요. "이거 어디서 다 부르고 있는 거지?" 싶어서 파이프라인을 처음부터 끝까지 따라가며 LLM 호출 지점을 모두 세어봤습니다.

결과는 9군데였습니다. 데이터 구축 단계 3곳, 채팅 파이프라인 5곳, 장애 분석 1곳. 한 번 정리하고 보니까 비용 구조가 훨씬 명확해졌고, 그 다음에 어디를 어떻게 줄일지 결정할 수 있게 됐어요.

1.jpeg


채팅 한 건당 LLM이 몇 번 호출되나

이 9군데를 한 번에 다 부르는 건 아니에요. 데이터 구축 쪽은 임포트할 때만 돌고, 분석 리포트는 분석 요청이 있을 때만 돌거든요. 일반 사용자가 채팅 한 건 보내면 호출되는 건 채팅 파이프라인 5곳 중 일부입니다.

호출 흐름을 정리하면 이렇게 돼요.

사용자 질문
  → [chitchat 규칙 필터] ── chitchat → 답변 생성 1회
  → [의도 분석] LLM 1회
    ├─ followup → 답변 생성 1회
    └─ new_query
         → [매니저 에이전트] LLM 1회
         → 에이전트 실행 (검색/그래프)
            └─ CRAG 발동 시 → [리라이팅] LLM 1회
            └─ Cypher 도구 시 → [Cypher 생성] LLM 1회
         → [답변 생성] LLM 1회

질문 유형별 호출 수를 정리하면 이렇습니다.

질문 유형

LLM 호출 수

chitchat ("안녕하세요")

1~2회

일반 질문

3회 (의도분석 + 매니저 + 답변생성)

CRAG 발동 (검색 결과 부족)

4회 (+리라이팅)

Cypher 질문

4회 (+Cypher 생성)

평균 3~4회로 안착됩니다. "답변 한 번 받는데 LLM이 한 번 돌겠지"라는 막연한 가정과는 꽤 거리가 있어요.

특히 CRAG와 Text-to-Cypher는 항상 도는 게 아니라 조건부 발동입니다. 검색 결과가 부실하거나, 일반 도구로 답할 수 없는 복잡한 질문일 때만 추가로 LLM이 한 번 더 들어가요. "필요할 때만 추가 비용"이라는 구조라서 평소엔 낭비가 안 됩니다.


모델을 둘로 나눠서 비용을 60% 절감했습니다

처음엔 모든 LLM 호출에 같은 모델을 썼어요. gemini-2.5-flash로 통일해서 운영했거든요. 그런데 호출 9군데를 들여다보니까 역할이 명확하게 두 갈래로 나뉘더라고요.

라우팅·판단용: 의도 분석, 매니저, CRAG 리라이팅, Text-to-Cypher

생성용: 답변 생성, 분석 리포트

라우팅·판단용 호출은 입력도 짧고, 출력도 짧아요. "이 질문은 chitchat인가 new_query인가?" 같은 분류 작업이거든요. 굳이 비싼 모델을 쓸 필요가 없습니다. 반면에 답변 생성은 사용자가 직접 보는 출력이라 품질이 중요해요.

그래서 모델을 분리했어요.

용도

모델

이유

의도 분석 / 매니저 / 리라이팅 / Cypher

gemini-2.0-flash

빠르고 저렴

답변 생성 / 분석 리포트

gemini-2.5-flash

품질 우선

리랭크

BGE-Reranker (로컬)

무료, 빠름

임베딩

Gemini Embedding

API 통합 편의

라우팅 호출에 2.0-flash를 쓰면서 라우팅 비용이 60%가량 줄었어요. 답변 품질은 그대로인데 보이지 않는 영역의 비용이 확 빠졌습니다.

이 발상은 옷 살 때 "겉옷은 좋은 걸 입고, 속옷은 적당한 걸 입는다"와 비슷해요. 사용자에게 노출되는 부분에는 비싼 모델을, 내부 판단·분류 부분에는 가벼운 모델을. 모델을 통일하는 게 코드 관리는 편한데, 비용 측면에서 손해가 큽니다.

2.jpeg


그 외에 비용을 줄인 다섯 가지 장치

모델 분리가 가장 큰 절감이었지만, 다른 작은 장치들도 같이 깔아두었어요. 한 번에 60%를 줄이는 게 아니라 1~5%씩 곳곳에서 절감하는 방식입니다.

Thinking budget 동적 조절 — Gemini 2.5의 thinking 모드는 추가 토큰을 소비합니다. "안녕하세요" 같은 단순 질문에 thinking 2048을 쓸 이유가 없거든요. 질문 복잡도에 따라 0/512/1024/2048로 동적 조절합니다. 단순 질문은 0으로 끄고, 복잡한 분석 질문에만 높게 풀어줘요.

캐시 (스레드/커뮤니티별) — 데이터 구축 단계의 LLM 호출은 같은 데이터에 두 번 돌릴 이유가 없어요. 처리한 스레드 ID와 커뮤니티 ID를 기록해두고, 재임포트 시에는 이미 처리된 건 건너뜁니다. 이메일을 다시 임포트할 일이 종종 있는데, 캐시 덕분에 LLM 호출이 0회로 끝나는 경우가 많아요.

리랭크 로컬화 — 원래 Gemini로 리랭크를 돌렸는데, BGE-Reranker-v2-m3로 갈아탔어요. 로컬 모델이라 API 비용이 0원이고, 정확도는 더 높았습니다. 리랭크는 호출 빈도가 높은 영역이라 절감 효과가 컸어요.

chitchat 규칙 필터 — "안녕", "고마워" 같은 인사말은 의도 분석 LLM을 부르지 않고 규칙 기반 필터로 먼저 걸러요. LLM 호출 1회를 절감합니다. 단순한 패턴은 LLM에 보내지 않는다는 원칙입니다.

CRAG 3단계 판단 — CRAG가 검색 결과를 평가할 때 LLM이 아니라 BGE rerank score 기준으로 판단해요. score가 일정 임계값 이상이면 통과, 부족하면 리라이팅. 판단 자체에 LLM을 안 쓰니까 호출 0회로 끝납니다.

전략

효과

모델 분리

라우팅 비용 60% ↓

Thinking budget 동적 조절

단순 질문 토큰 ↓

캐시 (스레드/커뮤니티)

재실행 시 호출 0

리랭크 로컬화

리랭크 비용 0

chitchat 규칙 필터

호출 1회 절감

CRAG 3단계 판단

불필요한 재검색 방지


"어디서 부르는지" 먼저 알아야 줄일 수 있습니다

이번 작업에서 가장 큰 깨달음은 비용 최적화의 첫 단계가 호출 지점 매핑이라는 거였어요. 줄일 곳을 찾기 전에 "지금 어디서 얼마나 부르고 있나"를 알아야 하거든요.

저는 이 매핑을 1년 가까이 운영하면서도 안 했어요. "비용 청구서가 너무 많이 나온다"는 인지는 있었지만, 어디서 새는지를 모르니까 손을 못 댔습니다. 막연히 "답변 생성이 비싸니까 그쪽을 줄여야 하나" 정도의 추측만 했어요.

호출 9군데를 표로 정리한 다음에 비로소 결정이 명확해졌습니다.

채팅 5곳 중 4곳이 라우팅·판단용 → 모델 분리로 60% 절감

데이터 구축 3곳은 재실행 빈도가 높음 → 캐시로 호출 0

임베딩과 리랭크는 LLM이 아님 → 로컬 대체로 비용 0

각 호출의 역할을 분류하고 나니 적용할 절감 전략이 자명했어요. "어디서 부르고 있는지"를 모르면, "어디를 줄일지"도 정할 수가 없습니다.


마무리

채팅 한 건당 LLM은 평균 3~4회 호출됩니다. CRAG나 Cypher 도구가 발동하면 한 번 더 들어가요. 9군데 호출 지점을 매핑한 뒤 라우팅용은 가벼운 모델로, 답변 생성용은 품질 모델로 분리했더니 라우팅 비용이 60% 줄었습니다. 모델 분리 외에도 thinking budget 동적 조절, 캐시, 로컬 리랭크, chitchat 규칙 필터, BGE 기반 CRAG 판단 등 다섯 가지 장치를 같이 깔아두었어요.

LLM 비용이 부담된다고 느낀다면, 가장 먼저 해볼 일은 호출 지점을 표로 정리하는 거예요. 9군데일지 5군데일지 모르지만, 매핑을 하고 나면 어디서 부르는지 처음으로 보이고, 그 다음에 어디를 어떻게 줄일지가 정해집니다.

#LLM비용#RAG비용#Gemini#모델분리#thinkingbudget#비용최적화#AI전환기