- 공유 링크 만들기
- X
- 이메일
- 기타 앱
Featured Post
작성자:
Iros
- 공유 링크 만들기
- X
- 이메일
- 기타 앱

안녕하세요. 얼마 전에 속초 바다 보면서 회 한 접시 먹고 왔거든요. 바닷바람 맞으면서 “아, 역시 사람은 가끔 멀리 나와야 숨이 트인다” 이러고 있었는데요. 회사로 돌아오자마자 LLM API 비용 경고 메일을 보고 그 여유가 싹 날아갔습니다. 뭐랄까, 여행 다녀온 카드값보다 더 무서운 게 OpenAI 청구서라는 걸 그때 다시 배웠네요.
요즘 회사마다 LLM 기반 서비스 하나쯤은 붙여보자는 분위기잖아요. 저도 비슷했습니다. 급하게 기능 만들고, FastAPI로 API 열고, OpenAI 붙이고, RAG도 얹고, “오, 생각보다 잘 되는데?” 싶었죠. 그런데 사용자가 늘어나니까 바로 현실이 오더라고요. 비용은 슬금슬금이 아니라 확 치솟고, 사용자는 “답변이 이상해요”, “아까랑 말이 달라요”, “왜 이렇게 느려요?”라고 하고, 저는 서버 로그를 뒤지면서 혼자 중얼거렸습니다. “대체 어디서부터 봐야 하지?”
솔직히 처음엔 그냥 print() 찍었습니다. 네, 40대 개발자도 급하면 그렇게 합니다. 예쁘게 말하면 바이브 코딩이고, 덜 예쁘게 말하면 눈감고 더듬는 거죠. 개발할 땐 그럭저럭 버팁니다. 그런데 운영 환경에서 하루 수천 건씩 요청이 들어오면, 그 방식은 금방 한계가 옵니다. 로그는 많은데 의미 있는 정보는 안 보이고, 성공 응답은 찍히는데 품질 문제는 잡히지 않고요.
그때 제대로 도움을 받은 도구가 Langfuse였습니다. 오늘은 제가 실제로 LLM 서비스에 Langfuse를 붙이면서 겪었던 일, 삽질했던 부분, 그래도 도입하고 나서 확실히 좋아졌던 지점들을 친구한테 얘기하듯이 풀어보려고 합니다. 공식 문서 요약보다는, 제가 운영하면서 “아, 이건 진짜 미리 알았으면 좋았겠다” 싶었던 것들 위주로 적어볼게요.
LLM 서비스는 왜 일반 로그만으로는 부족했을까
보통 웹 서비스는 에러 로그만 잘 봐도 어느 정도 감이 옵니다. DB 커넥션이 터졌는지, API 응답이 500을 냈는지, 특정 함수에서 예외가 났는지 말이죠. Sentry 같은 도구 붙여두면 꽤 많은 일이 해결됩니다.
그런데 LLM 서비스 디버깅은 성격이 조금 달랐습니다. API 호출은 분명 성공입니다. 상태 코드도 200 OK예요. 서버 입장에서는 아무 문제 없습니다. 그런데 사용자가 받은 답변은 이상합니다. 질문 의도를 엉뚱하게 해석했거나, RAG로 가져온 문서가 이상했거나, 프롬프트가 너무 길어져서 핵심 지시가 묻혔거나, 모델이 그냥 삐끗했거나. 이런 문제는 기존 로그만으로는 잘 안 잡힙니다.
제가 겪은 대표적인 상황은 이랬습니다.
- 사용자는 “환불 규정 알려줘”라고 물었는데, LLM은 엉뚱하게 배송 정책을 설명함
- RAG 검색 결과는 0.2초 만에 끝났는데, 전체 응답은 8초 넘게 걸림
- 같은 질문인데 어제는 괜찮았고 오늘은 이상해짐
- 프롬프트를 조금 고쳤을 뿐인데 토큰 사용량이 두 배 가까이 늘어남
- 비용이 늘었는데 어느 사용자, 어느 기능, 어느 프롬프트 때문인지 감이 안 옴
이런 상황이 쌓이면 개발자가 점점 예민해집니다. 저도 그랬어요. 아침에 커피 한 잔 들고 자리에 앉았는데 슬랙에 “AI 답변 이상합니다”라는 메시지가 떠 있으면, 그날 오전은 이미 날아간 겁니다. 로그 뒤지고, DB 뒤지고, 사용자의 원본 질문 찾고, 그때 조합된 프롬프트가 뭐였는지 추적하고. 어휴, 생각만 해도 피곤하네요.
Langfuse를 붙이고 나서 좋았던 건, 요청 하나가 어떤 흐름으로 흘러갔는지 Trace 단위로 볼 수 있다는 점이었습니다. 사용자의 입력, 프롬프트, 모델 호출, 토큰 사용량, 응답 시간, 최종 출력까지 이어서 볼 수 있으니까 “느낌상 이상한데?”가 아니라 “여기서 문제가 났네”라고 말할 수 있게 됐습니다.
| 상황 | Langfuse 없이 운영할 때 | Langfuse 붙인 뒤 |
|---|---|---|
| LLM API 비용 확인 | 월말 청구서 보고 조용히 한숨 쉼 | 기능별, 사용자별, 프롬프트별 토큰 사용량을 바로 확인 |
| 느린 응답 분석 | “오늘 서버가 좀 무거운가?” 하고 감으로 추측 | RAG 검색, 프롬프트 조립, LLM 호출 시간을 구간별로 비교 |
| 프롬프트 변경 관리 | 코드에 박힌 문자열 수정하고 배포했다가 롤백 반복 | Prompt Registry에서 버전별로 관리하고 필요한 버전만 호출 |
| 품질 문제 추적 | 사용자 질문과 실제 응답을 찾느라 로그 바다에서 허우적거림 | Trace 하나 열면 입력부터 출력까지 한 번에 확인 |
제가 실제로 구성했던 FastAPI 프로젝트 구조
말로만 하면 좀 심심하니까, 제가 실무에서 가져갔던 구조를 단순화해서 보여드릴게요. 저는 처음에 LLM 호출 코드를 여기저기 흩뿌려놨다가 나중에 꽤 고생했습니다. 서비스가 작을 때는 괜찮아 보여도, 운영하다 보면 “LLM 호출은 한 군데로 모아야 한다”는 생각이 강하게 들더라고요.
그래서 아래처럼 services 아래에 LLM 관련 로직을 최대한 모으고, 설정은 config.py에서 관리했습니다. 별거 아닌 것 같지만, 이렇게 해두면 나중에 모델 교체하거나 Langfuse 설정 바꿀 때 훨씬 덜 아픕니다.
my-llm-app/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── config.py
│ └── services/
│ ├── __init__.py
│ └── llm_service.py
├── requirements.txt
└── .env
.env에는 이런 값들이 들어갑니다. 운영 환경에서는 당연히 Secret Manager나 Parameter Store 같은 걸 쓰는 게 좋고요. 로컬 개발 때만 이렇게 두는 편이 마음 편합니다.
OPENAI_API_KEY=your-openai-api-key
LANGFUSE_PUBLIC_KEY=your-langfuse-public-key
LANGFUSE_SECRET_KEY=your-langfuse-secret-key
LANGFUSE_HOST=https://cloud.langfuse.com
설정 파일은 이렇게 간단하게 뒀습니다. 회사마다 방식은 다르겠지만, 저는 환경 변수를 코드 여기저기에서 직접 읽는 걸 별로 안 좋아합니다. 나중에 찾기 힘들거든요. 한 군데로 모아두면 장애 났을 때도 덜 당황합니다.
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
OPENAI_API_KEY: str
LANGFUSE_PUBLIC_KEY: str
LANGFUSE_SECRET_KEY: str
LANGFUSE_HOST: str = "https://cloud.langfuse.com"
class Config:
env_file = ".env"
settings = Settings()
Langfuse를 LLM 호출 코드에 붙이는 방식
핵심은 @observe 데코레이터입니다. 처음 봤을 때는 “이거 하나 붙인다고 뭐가 그렇게 달라져?” 싶었는데요. 실제로 붙여보면 생각보다 체감이 큽니다. 함수 실행 흐름이 Trace로 잡히고, 입력과 출력, 실행 시간, 오류 상황을 나중에 대시보드에서 확인할 수 있으니까요.
아래 코드는 여행 일정을 만들어주는 예시입니다. 제가 여행을 좋아해서 예시도 자꾸 이쪽으로 가네요. 그래도 LLM 서비스 구조를 설명하기엔 꽤 괜찮습니다. 사용자가 목적지와 기간을 넣으면, Langfuse Prompt Registry에서 프롬프트를 가져오고, OpenAI를 호출한 뒤, 결과를 Trace에 남기는 흐름입니다.
from openai import OpenAI
from langfuse.decorators import observe, langfuse_context
from app.config import settings
openai_client = OpenAI(api_key=settings.OPENAI_API_KEY)
class LLMService:
def __init__(self):
pass
@observe(name="generate_travel_itinerary")
def generate_itinerary(self, user_id: str, destination: str, duration: int) -> str:
langfuse_context.update_current_trace(
user_id=user_id,
tags=["travel", destination, f"{duration}days"],
metadata={
"environment": "production",
"feature": "itinerary_generator"
}
)
try:
prompt_template = langfuse_context.get_prompt("itinerary_generator")
prompt = prompt_template.compile(
destination=destination,
duration=duration
)
except Exception as e:
prompt = f"{destination} {duration}일 여행 코스를 오전, 오후, 저녁으로 나눠서 추천해줘."
langfuse_context.update_current_span(
level="WARNING",
status_message=f"Prompt registry failed: {str(e)}"
)
response = openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": "너는 실제 여행 경험이 많은 친절한 여행 가이드야."
},
{
"role": "user",
"content": prompt
}
],
temperature=0.7
)
result_text = response.choices[0].message.content
langfuse_context.update_current_trace(
output=result_text,
metadata={
"model": "gpt-4o-mini"
}
)
return result_text
이 정도만 붙여도 Langfuse 대시보드에서 요청 흐름이 꽤 잘 보입니다. 저는 특히 user_id, tags, metadata를 꼭 넣는 편입니다. 이걸 안 넣으면 나중에 필터링할 때 답답합니다. “어느 기능에서 비용이 많이 나갔지?”, “특정 사용자 요청만 이상한가?”, “production에서만 느린가?” 이런 걸 보려면 결국 태그와 메타데이터가 생명입니다.
작게 시작할 때는 대충 해도 됩니다. 그런데 운영에 올릴 거면 처음부터 태그 규칙을 정해두는 걸 추천합니다. 나중에 고치려면 이미 쌓인 Trace들이 제각각이라 보는 맛이 확 떨어지거든요.
운영하면서 진짜 도움이 됐던 관측 포인트
Langfuse를 붙였다고 모든 문제가 자동으로 해결되진 않습니다. 도구는 도구일 뿐이고, 뭘 봐야 하는지 정해야 합니다. 저는 처음에 대시보드가 신기해서 이것저것 눌러봤는데, 며칠 지나니까 자주 보는 지표가 정해지더라고요.
토큰 사용량은 기능 단위로 보는 게 좋았습니다
LLM 비용은 생각보다 조용히 샙니다. 무섭게 폭발하는 게 아니라, 작은 낭비가 계속 쌓입니다. 예를 들면 이런 식입니다.
- 시스템 프롬프트가 너무 길어서 모든 요청에 불필요한 토큰이 붙음
- RAG로 가져온 문서가 5개면 충분한데 15개씩 넣고 있음
- 사용자에게는 한 문단만 보여주는데 내부적으로는 긴 JSON을 생성함
- 테스트용 프롬프트가 production에 그대로 남아 있음
저희 팀도 비슷했습니다. 어느 날 특정 기능의 토큰 사용량이 유독 높게 찍히길래 Trace를 열어봤더니, RAG 검색 결과를 너무 많이 프롬프트에 넣고 있더군요. 사용자는 간단한 질문을 했는데, 내부 프롬프트에는 거의 문서 한 뭉치가 들어가고 있었습니다. 그걸 보고 검색 결과 개수를 줄이고, 문서 chunk를 다시 조정했더니 비용이 꽤 내려갔습니다. 아주 드라마틱한 마법은 아니지만, 이런 개선이 쌓이면 월말에 확실히 차이가 납니다.
| 개선 항목 | 적용 전 | 적용 후 | 체감 |
|---|---|---|---|
| RAG 문서 개수 | 상위 12개 chunk 삽입 | 상위 5개 chunk 삽입 | 응답 속도와 비용 모두 개선 |
| 시스템 프롬프트 | 모든 정책을 한 번에 설명 | 기능별로 필요한 지시만 분리 | 프롬프트 관리가 쉬워짐 |
| 출력 형식 | 긴 자연어 설명 | 필요한 필드 중심 JSON | 후처리 오류 감소 |
Latency는 전체 시간이 아니라 구간별로 봐야 합니다
사용자가 “느려요”라고 말하면 개발자는 본능적으로 서버 전체를 의심합니다. DB가 느린가, API 서버가 밀렸나, 모델이 느린가. 그런데 Langfuse로 Trace를 나눠서 보니까 생각보다 원인이 다양했습니다.
어떤 요청은 Vector DB 검색이 느렸고, 어떤 요청은 LLM 응답 생성이 오래 걸렸습니다. 또 어떤 경우는 프롬프트를 만들기 전에 내부 API를 여러 번 호출하는 구조가 문제였습니다. 그러니까 “LLM이 느리다”는 말이 항상 맞지는 않더라고요. 사실 사람도 그렇잖아요. 여행 가서 길이 막힌다고 무조건 운전자가 문제는 아니니까요. 신호가 문제일 수도 있고, 앞차가 문제일 수도 있고, 목적지를 이상하게 찍은 내비가 문제일 수도 있습니다.
저는 아래처럼 구간 이름을 명확히 넣는 걸 좋아합니다.
@observe(name="rag_search")
def search_documents(query: str):
return vector_store.similarity_search(query, k=5)
@observe(name="build_prompt")
def build_prompt(question: str, documents: list):
context = "\n\n".join([doc.page_content for doc in documents])
return f"문맥:\n{context}\n\n질문:\n{question}"
@observe(name="call_llm")
def call_llm(prompt: str):
return openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "문맥을 바탕으로 정확하게 답변해줘."},
{"role": "user", "content": prompt}
],
temperature=0.2
)
이렇게 잘게 쪼개두면 나중에 정말 편합니다. 운영 이슈가 생겼을 때 “전체가 느립니다”가 아니라 “검색은 빠른데 LLM 호출이 6초 걸렸습니다”, “프롬프트 빌드 전에 외부 API가 막힙니다”라고 말할 수 있으니까요. 팀 회의 때도 감정 소모가 줄어듭니다. 데이터가 있으면 목소리 큰 사람이 이기는 구조가 덜해지거든요.
바이브 코딩할 때 AI에게 이렇게 시키면 덜 망합니다
요즘 개발할 때 Cursor, Claude Code, ChatGPT 같은 도구 많이 쓰잖아요. 저도 씁니다. 안 쓰는 척하면 좀 거짓말이고요. 다만 LLM 관측성 붙이는 작업을 AI에게 맡길 때는 프롬프트를 대충 쓰면 결과가 묘하게 위험합니다. 코드는 돌아가는데 운영에는 애매한 코드가 나오기 쉽거든요.
예를 들어 “FastAPI에 Langfuse 붙여줘”라고만 하면, AI가 예제 코드는 잘 만들어줍니다. 그런데 개인정보 마스킹, 실패 시 fallback, 태그 규칙, flush 타이밍, 비동기 처리 같은 운영 포인트는 빠질 때가 많습니다. 이게 나중에 은근히 발목을 잡습니다.
제가 실제로 효과를 봤던 프롬프트는 조금 더 귀찮게, 대신 맥락을 많이 주는 방식이었습니다.
FastAPI 기반 LLM 서비스에 Langfuse를 붙이려고 해.
조건은 아래와 같아.
1. OpenAI chat completions를 사용한다.
2. user_id, feature_name, environment를 Langfuse trace metadata에 남긴다.
3. 사용자의 원문 질문에는 이메일, 전화번호, 주민등록번호가 포함될 수 있으니 Langfuse로 보내기 전에 마스킹한다.
4. Langfuse 서버 장애가 있어도 사용자 응답은 실패하면 안 된다.
5. LLM 호출 함수, RAG 검색 함수, 프롬프트 생성 함수를 각각 trace에서 구분해서 보고 싶다.
6. 운영 환경에서 유지보수하기 좋게 파일 구조도 함께 제안해줘.
단순 예제 말고 production에 올릴 때 문제가 될 만한 부분을 코드 주석으로 설명해줘.
이렇게 요청하면 결과물이 확 달라집니다. AI가 단순히 SDK 붙이는 수준을 넘어서, 운영에서 조심해야 할 지점까지 같이 고려해줍니다. 물론 그대로 믿고 복붙하면 안 됩니다. 저는 AI가 만들어준 코드를 보고 꼭 이런 질문을 한 번 더 던집니다.
방금 작성한 코드에서 운영 장애로 이어질 수 있는 위험 요소를 찾아줘.
특히 아래 관점으로 봐줘.
- Langfuse 서버가 느리거나 죽었을 때 사용자 응답에 영향이 있는지
- 개인정보가 외부로 전송될 가능성이 있는지
- trace metadata가 너무 커져서 비용이나 성능 문제가 생기지 않는지
- 프롬프트 버전이 의도치 않게 바뀔 가능성이 있는지
- 비동기 환경에서 flush 누락이나 race condition이 생길 수 있는지
사실 이 두 번째 질문이 진짜 중요합니다. AI에게 “만들어줘”만 시키면 근사한 코드를 줍니다. 그런데 “어디가 위험해?”라고 물어보면, 그때부터 운영자의 시선이 조금 들어갑니다. 저는 요즘 AI를 후배 개발자처럼 쓰려고 합니다. 빠르고 똑똑하지만, 아직 우리 서비스의 맥락은 모르는 친구요. 그래서 요구사항을 자세히 주고, 나온 결과는 의심하면서 같이 다듬습니다. 이 정도 거리감이 딱 좋더라고요.
Langfuse 도입하면서 실제로 조심했던 부작용들
좋은 도구라고 해서 무조건 편하기만 한 건 아닙니다. Langfuse도 마찬가지였습니다. 도입 자체는 어렵지 않은데, 운영에 제대로 녹이려면 몇 가지 조심할 부분이 있습니다. 이건 공식 문서만 보고는 잘 안 와닿을 수 있어서, 제가 겪은 것 중심으로 얘기해볼게요.
네트워크 지연이 은근히 신경 쓰입니다
Langfuse SDK는 기본적으로 이벤트를 모아서 전송합니다. 그래서 매 요청마다 엄청난 부담이 생기는 구조는 아니지만, 트래픽이 많거나 네트워크가 불안정하면 신경이 쓰입니다. 특히 외부 Langfuse Cloud로 보내는 구조라면, 회사 보안 정책이나 네트워크 상태에 따라 영향을 받을 수 있습니다.
저는 처음엔 Cloud로 붙여서 빠르게 검증했습니다. 이건 좋았습니다. 세팅이 간단하고 대시보드도 바로 볼 수 있으니까요. 그런데 운영 트래픽을 태우기 시작하니, 내부 보안 검토와 네트워크 정책 이야기가 나오더군요. 결국 일부 환경에서는 Self-Hosting도 검토했습니다.
- 빠른 검증 단계에서는 Langfuse Cloud가 편했습니다.
- 민감한 데이터가 많거나 트래픽이 큰 서비스에서는 Self-Hosting이 마음 편했습니다.
- 서비스 서버와 Langfuse 서버 위치가 너무 멀면 네트워크 지연을 꼭 확인해야 했습니다.
- 배치 전송과 flush 설정은 트래픽 패턴에 맞게 테스트해보는 게 좋았습니다.
개인정보는 생각보다 쉽게 섞여 들어옵니다
이건 정말 조심해야 합니다. 사용자는 우리가 기대한 대로만 질문하지 않습니다. 고객센터 챗봇이라면 이메일, 전화번호, 주문번호를 그냥 입력합니다. 내부 문서 검색 서비스라면 계약서 내용이나 인사 정보가 들어올 수도 있고요. 그 데이터가 그대로 Langfuse에 쌓이면 나중에 보안 감사에서 꽤 곤란해질 수 있습니다.
저는 최소한 아래 정도는 마스킹하는 미들웨어나 유틸 함수를 두는 편이 낫다고 봅니다. 완벽하진 않아도, 아예 안 하는 것보단 훨씬 낫습니다.
import re
def mask_sensitive_text(text: str) -> str:
if not text:
return text
text = re.sub(
r"[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+",
"[MASKED_EMAIL]",
text
)
text = re.sub(
r"01[016789]-?\d{3,4}-?\d{4}",
"[MASKED_PHONE]",
text
)
text = re.sub(
r"\d{6}-?[1-4]\d{6}",
"[MASKED_RRN]",
text
)
return text
물론 정규식 마스킹이 만능은 아닙니다. 회사마다 개인정보 기준도 다르고, 문서 종류도 다릅니다. 그래도 운영 로그와 관측 데이터에는 “필요한 만큼만 남긴다”는 원칙이 있어야 합니다. 개발자는 자꾸 더 많이 보고 싶어 하지만, 운영 데이터는 많이 쌓을수록 책임도 같이 쌓입니다. 이건 좀 무겁게 봐야 합니다.
Prompt Registry는 편하지만 팀 규칙이 없으면 위험합니다
Langfuse의 Prompt Registry는 꽤 편합니다. 프롬프트를 코드 밖에서 관리하고, 버전도 남길 수 있으니까요. 그런데 편한 도구일수록 팀 규칙이 없으면 금방 난장판이 됩니다.
제가 봤던 아찔한 상황은 이랬습니다. 누군가 대시보드에서 프롬프트를 살짝 수정했습니다. 의도는 좋았죠. 답변을 좀 더 친절하게 만들고 싶었던 겁니다. 그런데 그 프롬프트가 production에서 바로 사용되고 있었고, 출력 형식이 미묘하게 바뀌면서 후처리 파서가 실패했습니다. 서버 에러는 아닌데 사용자에게는 이상한 응답이 나가는 상황. 참 애매합니다.
그래서 저는 Prompt Registry를 쓸 때 아래 규칙은 꼭 두는 편입니다.
- production에서는 가능하면
version을 명시해서 호출합니다. dev,staging,prod라벨을 섞어 쓰지 않습니다.- 프롬프트 수정도 코드 변경처럼 리뷰 과정을 둡니다.
- 출력 형식이 바뀌는 프롬프트는 반드시 후처리 테스트를 같이 돌립니다.
- 프롬프트 변경 이력에는 “왜 바꿨는지”를 남깁니다.
프롬프트는 코드가 아니라고 생각하기 쉽지만, LLM 서비스에서는 거의 코드나 다름없습니다. 오히려 더 위험할 때도 있습니다. 코드처럼 보이지 않아서 사람들이 가볍게 고치거든요. 저는 요즘 팀원들에게 “프롬프트도 배포물이다”라고 자주 얘기합니다. 조금 꼰대 같지만, 이건 진짜입니다.
도입 전에 보면 좋은 체크리스트
Langfuse를 붙이기 전에 아래 항목은 한 번쯤 팀에서 이야기해보면 좋습니다. 도구 세팅보다 이런 합의가 더 중요할 때가 많습니다.
- 어떤 단위로 Trace를 볼 것인가: 사용자 요청 단위인지, 내부 작업 단위인지 정해두면 좋습니다.
- metadata에 무엇을 남길 것인가: user_id, feature, environment, model 정도는 꽤 유용했습니다.
- 민감정보는 어디서 마스킹할 것인가: API 진입점인지, LLM 호출 직전인지 팀 기준이 필요합니다.
- 프롬프트 버전 정책은 어떻게 할 것인가: production에서 latest를 무작정 쓰는 건 저는 별로 추천하지 않습니다.
- 비용을 어떤 기준으로 볼 것인가: 전체 비용만 보면 늦습니다. 기능별, 팀별, 사용자 그룹별로 나눠야 보입니다.
- Langfuse 장애 시 서비스는 어떻게 동작할 것인가: 관측 도구 장애 때문에 사용자 응답이 실패하면 주객이 전도됩니다.
저는 개인적으로 이 체크리스트를 도입 회의 때 같이 봅니다. 개발자끼리만 봐도 좋고, 기획자나 PM이 같이 봐도 좋습니다. 특히 비용과 프롬프트 변경 정책은 개발자 혼자 정하면 나중에 말이 나올 수 있거든요. LLM 서비스는 생각보다 여러 직군이 같이 책임져야 하는 영역입니다.
이런 팀이라면 Langfuse 도입을 진지하게 봐도 좋습니다
솔직히 말하면, 혼자 만드는 토이 프로젝트에 Langfuse까지 붙일 필요는 없을 수 있습니다. 주말에 재미로 만든 챗봇에 관측성 도구 붙이다가 정작 만들고 싶었던 기능을 못 만들면 그것도 좀 아깝잖아요. 도구는 필요할 때 쓰는 게 맞습니다.
그런데 아래 상황이라면 얘기가 달라집니다.
- LLM API 비용이 매달 늘어나는데 어디서 새는지 잘 안 보이는 팀
- RAG를 붙였는데 검색이 문제인지, 프롬프트가 문제인지, 모델이 문제인지 매번 헷갈리는 팀
- 프롬프트를 자주 바꾸는데 변경 이력과 품질 비교가 잘 안 되는 팀
- 사용자 불만이 들어왔을 때 실제 입력과 출력을 빠르게 확인해야 하는 운영팀
- A/B Test나 Evaluation을 제대로 해보고 싶은 LLM 서비스 개발팀
- “LLM이 가끔 이상해요”라는 말을 데이터로 바꾸고 싶은 리드 개발자
저한테 Langfuse는 대단히 화려한 마법 도구라기보다는, 야간 운전할 때 켜는 전조등 같은 느낌이었습니다. 길을 대신 가주진 않습니다. 대신 어디가 길이고 어디가 낭떠러지인지는 보여줍니다. 그 차이가 운영에서는 꽤 큽니다.
LLM 개발은 아직도 변수가 많습니다. 모델도 바뀌고, SDK도 바뀌고, 비용 정책도 바뀌고, 사용자는 늘 예상 밖의 질문을 합니다. 그러니 감으로만 운영하기엔 조금 위험합니다. 적어도 사용자의 요청이 어떤 프롬프트를 거쳐 어떤 모델로 들어갔고, 얼마나 비용이 들었고, 어디서 느려졌는지는 볼 수 있어야 마음이 놓입니다.
이 글은 LLM 기능을 이미 운영에 올렸거나, 곧 올릴 예정인 개발자분들이 읽으면 특히 도움이 될 것 같습니다. 리드 개발자, 백엔드 개발자, RAG 시스템 붙이고 있는 분들, 그리고 매달 LLM 비용 보고 가슴이 살짝 철렁하는 분들이요. 저도 아직 배우는 중이지만, 적어도 예전처럼 print 로그 붙잡고 밤새는 일은 많이 줄었습니다. 그거면 꽤 큰 진전 아닐까요.
우리 개발자들도 가끔은 로그 말고 바다를 봐야 하니까요. 관측할 건 도구에게 맡기고, 사람은 조금 더 중요한 판단에 시간을 쓰면 좋겠습니다.
👨💻
작성자: 20년 경력 IT 전문 아키텍트
실무 개발과 아키텍처 설계를 거쳐 현재는 AI 바이브 코딩과 개발 자동화를 연구하고 있습니다. 직접 삽질하며 깨달은 실전 꿀팁과 에러 극복 사례만 투명하게 공유합니다.
댓글
댓글 쓰기