에이전트 프로덕션에 올렸더니 비결정성 때문에 디버깅이 지옥입니다 — 관측/평가 어떻게 하세요
사이드 프로젝트로 만든 에이전트를 실사용자한테 풀었습니다. 문제는 같은 입력에도 매번 다르게 행동하고, 사용자가 "이상하게 답했다"고 제보해도 그때 무슨 일이 있었는지 재현이 안 됩니다.
로그는 print로 콘솔에 찍는 게 전부고요. LLM 호출 input/output, 어떤 툴을 어떤 인자로 불렀는지 같은 걸 제대로 추적하고 싶은데 다들 어떻게 하시나요. 그리고 "좋아졌는지"를 어떻게 측정하는지도 막막합니다. 무료/저비용으로 시작할 수 있는 방향이면 좋겠습니다.
답변 3개
- 채택된 답변에디터 검증
두 개를 분리해서 보셔야 해요. (1) 트레이싱으로 "무슨 일이 있었나"를 잡고, (2) 평가셋으로 "좋아졌나"를 잡는다. 섞으면 둘 다 망함.
트레이싱(관측)
print로는 못 버팁니다. 사용자 요청 하나가 여러 LLM 호출 + 툴 호출로 갈라지는 트리 구조라, 이걸 하나의 trace로 묶어 펼쳐볼 도구가 필요해요. Langfuse(오픈소스, 셀프호스팅 됨), Arize Phoenix(오픈소스), LangSmith(호스티드) 이 셋이 대표적이고, 요즘 OpenTelemetry 기반으로 표준화되는 추세라 OTel 스팬으로 깔아두면 나중에 벤더 갈아타기도 덜 아픕니다.매 trace에 최소한 이건 박으세요: 사용자 입력 / 각 LLM 호출의 전체 프롬프트·응답 / 각 툴 호출 인자·결과 / 최종 출력 / 재현용 식별자(세션ID, 모델 버전, 프롬프트 버전). 제보 들어오면 그 trace ID 하나로 정확히 그 실행을 펼칠 수 있어야 해요.
재현은 솔직히 완전히는 안 됩니다. LLM 특성상. 그래도 분류/라우팅 같은 결정적이어야 하는 단계는 temperature 0~0.2로 낮추고, 입력을 전부 로깅해두면 "같은 입력으로 다시 돌려보기"는 됩니다. 진짜 bit-exact 재현 말고 "동일 입력 재실행"을 현실적 목표로 잡으세요.
평가 실패 제보가 곧 평가셋 재료예요. 입력 + 기대 동작(또는 통과 기준)을 케이스로 모아두고 프롬프트/모델 바꿀 때마다 돌립니다. 규칙으로 채점 가능한 건 코드로("이 툴 불렀나", "숫자 맞나"), 애매한 품질만 LLM-as-judge로 보조하되 judge는 사람이 손으로 라벨링한 케이스로 캘리브레이션 한 번 해두고요. 거창하게 시작하지 말고 망가진 케이스 10개 모으는 것부터 하면 됩니다. 그게 제일 ROI 좋아요.
프롬프트랑 모델을 버전으로 관리하는 거 강추합니다. 트레이스에 "prompt v12, model claude-opus-4-8"이 같이 박혀 있어야 "언제부터 이상해졌지"를 추적할 수 있어요. 프롬프트를 코드 곳곳에 문자열로 흩뿌려두면 지금 프로덕션에 어느 버전이 떠 있는지조차 모르게 됩니다. 별도 파일로 빼고 해시나 버전 태그를 trace에 같이 남기세요. 이거 안 해두면 나중에 회귀 디버깅이 진짜 미궁임.
저비용 관점에서 보태면 Langfuse나 Phoenix는 오픈소스라 도커로 셀프호스팅하면 사실상 인프라 비용만 듭니다. 트래픽 작은 사이드면 충분해요.
그리고 "좋아졌나"를 너무 점수에 집착하지 마세요. 초반엔 실패 trace를 사람이 직접 눈으로 읽는 error analysis가 어떤 자동 지표보다 인사이트가 큽니다. 망가진 trace 30개만 정독해도 "아 이 패턴에서 항상 깨지네" 하는 게 보이고, 그게 다음 수정의 8할이에요. 자동 eval은 그 패턴을 회귀 방지로 고정할 때 붙이는 거지, 처음부터 깔 거 아닙니다. 순서 거꾸로 가는 분들 많아서...