DeepEval: LLM 앱을 pytest처럼 평가하기
confident-ai/deepeval을 기준으로, LLM 애플리케이션을 단위 테스트처럼 평가하고 LangChain 실행 트레이스를 CI/CD 게이트로 연결하는 방법을 정리한다.
한 줄 요약
DeepEval은 LLM 애플리케이션을 위한 오픈소스 평가 프레임워크다. 느낌으로 말하면 “LLM 앱용 pytest”에 가깝다. 일반적인 테스트가 assert result == expected를 확인한다면, DeepEval은 답변 관련성, 환각, faithfulness, task completion, tool correctness 같은 LLM 품질 지표를 테스트 케이스로 만든다.
GitHub 저장소는 confident-ai/deepeval이고, 2026년 6월 기준 Python 기반 Apache-2.0 오픈소스 프로젝트다. GitHub API 기준으로 약 16k stars, 1.5k forks 규모까지 커졌다. LLM evaluation 영역에서는 꽤 실전 지향적인 도구라고 봐도 된다.
내가 보기에 DeepEval의 핵심은 세 가지다.
- 평가를 코드로 남긴다 — 프롬프트 품질을 감이 아니라 테스트 파일로 관리한다.
- CI/CD에 붙일 수 있다 — 품질이 떨어지면 빌드를 실패시킨다.
- LangChain 같은 프레임워크의 실행 트레이스를 평가 단위로 삼는다 — agent, LLM call, tool call, retriever call을 span으로 나눠 볼 수 있다.
왜 LLM 앱에는 다른 테스트가 필요한가
전통적인 소프트웨어 테스트는 대체로 결정적이다.
assert add(2, 3) == 5하지만 LLM 앱은 이렇게 단순하게 검증하기 어렵다.
answer = rag_chain.invoke("DeepEval은 무엇인가?")
assert answer == "..."이런 테스트는 거의 쓸모가 없다. 같은 의미의 좋은 답변도 문자열이 조금만 달라지면 실패하고, 반대로 그럴듯한 오답은 통과할 수도 있다. LLM 앱에서 진짜 확인해야 하는 것은 보통 이런 질문이다.
- 답변이 사용자의 질문과 관련 있는가?
- 검색된 context에 근거해서 답했는가?
- 없는 사실을 지어내지 않았는가?
- 에이전트가 목표를 달성했는가?
- 올바른 도구를 올바른 인자로 호출했는가?
- JSON schema나 role instruction을 지켰는가?
DeepEval은 이 질문들을 metric으로 제공한다. 내부적으로는 LLM-as-a-judge, 통계적 방법, 로컬 NLP 모델 등을 조합해 테스트 케이스를 채점한다.
DeepEval이 제공하는 주요 평가 축
DeepEval의 metric은 꽤 넓다. 단순 챗봇부터 RAG, agent, multi-turn conversation까지 커버하려는 방향이다.
1. 범용 LLM 평가
- G-Eval: 사용자가 정의한 기준으로 LLM-as-a-judge 평가를 수행한다.
- DAG: 그래프 기반으로 더 결정적인 judge 흐름을 구성한다.
- Summarization / Bias / Toxicity / JSON Correctness / Prompt Alignment: 요약 품질, 편향, 유해성, JSON 스키마 준수, 프롬프트 지시 준수 등을 본다.
여기서 중요한 점은 “정답 문자열”이 아니라 평가 기준을 코드화한다는 것이다.
2. RAG 평가
RAG에서는 답변 자체보다 retrieval과 grounding이 더 중요하다. DeepEval은 다음 류의 metric을 제공한다.
- Answer Relevancy: 답변이 질문에 얼마나 관련 있는가
- Faithfulness: 답변이 제공된 context에 근거하는가
- Contextual Recall: 필요한 정보가 retrieval context에 포함됐는가
- Contextual Precision: 관련 context가 상위에 잘 랭크됐는가
- Contextual Relevancy: 검색 context 전체가 질문과 관련 있는가
- RAGAS: 여러 RAG 지표를 묶은 평가
RAG 시스템을 운영해보면 “모델이 답을 못한다”보다 “검색이 틀렸다”가 더 많은 경우가 있다. 그래서 retrieval 단계와 generation 단계를 분리해 평가할 수 있는 도구가 필요하다.
3. Agent 평가
에이전트는 답변 한 줄보다 실행 과정이 중요하다. DeepEval은 agentic metric도 제공한다.
- Task Completion: 에이전트가 목표를 달성했는가
- Tool Correctness: 올바른 도구를 올바른 인자로 호출했는가
- Goal Accuracy: 의도한 목표에 얼마나 정확히 도달했는가
- Step Efficiency: 불필요한 단계를 밟지 않았는가
- Plan Adherence / Plan Quality: 계획을 잘 세우고 따랐는가
- Tool Use / Argument Correctness: 도구 사용 품질과 인자 정확성
이건 꽤 중요하다. 에이전트는 최종 답변만 보면 멀쩡해 보이는데 내부에서 쓸데없는 검색, 잘못된 tool call, 우연한 성공이 섞이는 경우가 많다. 평가 대상이 output에서 trace로 확장되어야 한다.
LangChain 통합: callback으로 실행 트레이스를 잡는다
DeepEval의 LangChain 통합은 CallbackHandler를 통해 동작한다. LangChain 호출 시 config에 callback을 넘기면, 해당 실행이 trace로 기록된다.
from langchain.agents import create_agent
from deepeval.integrations.langchain import CallbackHandler
from deepeval.dataset import EvaluationDataset, Golden
from deepeval.metrics import TaskCompletionMetric
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[multiply],
system_prompt="Be concise.",
)
dataset = EvaluationDataset(
goldens=[Golden(input="What is 8 multiplied by 6?")]
)
for golden in dataset.evals_iterator():
agent.invoke(
{"messages": [{"role": "user", "content": golden.input}]},
config={
"callbacks": [CallbackHandler(metrics=[TaskCompletionMetric()])]
},
)이렇게 하면 LangChain 실행 하나가 end-to-end trace가 되고, 내부에는 agent span, LLM span, tool span, retriever span 같은 하위 span이 생긴다.
Trace: user request
└── Agent span: math_agent
├── LLM span: choose tool
├── Tool span: multiply(a=8, b=6)
└── LLM span: final answer좋은 점은 애플리케이션을 대대적으로 갈아엎지 않아도 된다는 것이다. LangChain이 원래 제공하는 callback 경계에 DeepEval을 꽂으면 된다.
CI/CD에서 pytest처럼 쓰기
DeepEval 문서에서 가장 실용적인 부분은 CI/CD 예제다. pytest.mark.parametrize로 golden set을 돌리고, assert_test로 metric을 검증한다. 실패한 metric은 테스트 실패가 되고, 테스트 실패는 빌드 실패가 된다.
import pytest
from langchain.agents import create_agent
from deepeval import assert_test
from deepeval.integrations.langchain import CallbackHandler
from deepeval.dataset import EvaluationDataset, Golden
from deepeval.metrics import TaskCompletionMetric
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[multiply],
system_prompt="Be concise.",
)
dataset = EvaluationDataset(
goldens=[
Golden(input="What is 8 multiplied by 6?"),
Golden(input="What is 7 multiplied by 9?"),
]
)
@pytest.mark.parametrize("golden", dataset.goldens)
def test_langchain_agent(golden: Golden):
agent.invoke(
{"messages": [{"role": "user", "content": golden.input}]},
config={"callbacks": [CallbackHandler()]},
)
assert_test(
golden=golden,
metrics=[TaskCompletionMetric()],
)실행은 일반 pytest가 아니라 DeepEval CLI를 쓴다.
deepeval test run test_langchain_agent.py이 패턴의 의미는 단순하다. 프롬프트를 바꾸거나, 모델을 바꾸거나, retriever 설정을 바꿨을 때 품질 회귀를 PR 단계에서 잡을 수 있다.
처음부터 모든 LLM 품질을 CI에서 막으려 하면 운영이 힘들다. 먼저 “절대 깨지면 안 되는 대표 시나리오” 10~30개를 golden set으로 만들고, 낮은 비용의 metric부터 붙이는 편이 낫다.
Trace 단위 평가와 component 단위 평가
DeepEval의 LangChain 통합에서 흥미로운 부분은 trace 전체뿐 아니라 component 단위 평가도 가능하다는 점이다.
예를 들어 전체 agent run에는 TaskCompletionMetric을 붙이고, 첫 번째 LLM call에는 AnswerRelevancyMetric을 붙이는 식이다.
from deepeval.integrations.langchain import CallbackHandler
from deepeval.metrics import AnswerRelevancyMetric
from deepeval.tracing import next_llm_span
with next_llm_span(metrics=[AnswerRelevancyMetric()]):
agent.invoke(
{"messages": [{"role": "user", "content": golden.input}]},
config={"callbacks": [CallbackHandler()]},
)문서상 next_llm_span은 one-shot semantics를 가진다. 즉, 해당 block 안에서 처음 열리는 LLM span에만 metric이 붙는다. agent가 여러 번 LLM을 호출한다면 모든 호출이 자동 평가되는 것은 아니다. 이 부분은 설계할 때 주의해야 한다.
Retriever도 비슷하게 next_retriever_span(...)으로 첫 retriever span에 metric이나 metric collection을 붙일 수 있다. RAG에서는 이 기능이 특히 유용하다. 최종 답변이 나빴을 때 문제가 generator인지 retriever인지 분리해서 볼 수 있기 때문이다.
Confident AI 플랫폼과의 관계
DeepEval 자체는 오픈소스 프레임워크다. 동시에 만든 회사인 Confident AI는 평가 결과, trace, report, dashboard를 관리하는 플랫폼을 제공한다.
정리하면 이렇게 보면 된다.
- DeepEval: 로컬/CI에서 실행하는 평가 프레임워크
- Confident AI: 평가 데이터, trace, report를 팀 단위로 관리하는 UI/플랫폼
개인 프로젝트나 작은 팀에서는 DeepEval CLI와 pytest 통합만으로도 충분히 시작할 수 있다. 다만 평가 케이스가 늘어나고, 여러 사람이 prompt/model 변경 이력을 비교해야 한다면 dashboard의 가치가 커진다.
어디에 쓰면 좋은가
내 기준으로 DeepEval이 특히 잘 맞는 곳은 다음이다.
1. RAG 품질 회귀 방지
문서 chunking, embedding model, reranker, prompt를 바꾸면 품질이 좋아졌는지 나빠졌는지 애매하다. DeepEval로 대표 질문 set을 만들고 answer relevancy, faithfulness, contextual precision/recall을 돌리면 최소한의 regression gate를 만들 수 있다.
2. Agent tool call 검증
에이전트가 tool을 “썼다”는 사실만으로는 부족하다. 올바른 tool을 골랐는지, argument를 맞게 넣었는지, 불필요한 단계를 줄였는지를 봐야 한다. DeepEval의 tool correctness, argument correctness, step efficiency 계열 metric이 이 영역에 맞는다.
3. 모델/프롬프트 교체 실험
OpenAI에서 Claude로 바꾸거나, 작은 모델로 비용을 줄이거나, system prompt를 정리할 때 기존 동작이 유지되는지 확인할 수 있다. “체감상 괜찮다”보다 테스트 결과를 비교하는 편이 훨씬 안전하다.
4. CI/CD 품질 게이트
LLM 앱이 제품 기능의 일부라면 prompt 변경도 코드 변경과 똑같이 검증되어야 한다. deepeval test run을 GitHub Actions, GitLab CI, CircleCI 등에 붙이면 기본적인 품질 게이트가 된다.
도입할 때 주의할 점
DeepEval이 평가 문제를 마법처럼 해결해주지는 않는다. 특히 LLM-as-a-judge는 judge 모델의 편향과 비용을 그대로 가진다.
그래서 처음 도입할 때는 다음 원칙이 좋다.
- golden set을 작게 시작한다 — 대표 시나리오 10~30개면 충분하다.
- metric을 너무 많이 붙이지 않는다 — CI 비용과 flakiness가 커진다.
- 절대 기준보다 추세를 본다 — 점수 0.82 자체보다 변경 전후 차이가 중요하다.
- 결정적 검증과 섞는다 — JSON schema, tool argument, citation 존재 여부 같은 것은 가능하면 deterministic check로 먼저 잡는다.
- judge 실패를 인정한다 — 평가 결과도 리뷰 대상이다. metric threshold를 맹신하면 안 된다.
LLM 평가를 CI에 넣으면 “좋은 제품 품질”이 자동으로 보장되는 것이 아니라, 최소한의 회귀 방지 장치가 생기는 것이다. 평가 케이스와 metric 자체도 계속 관리해야 한다.
내 결론
DeepEval은 LLM 앱 개발이 “프롬프트를 고쳐보고 눈으로 확인하는 단계”에서 “품질 기준을 코드로 관리하는 단계”로 넘어갈 때 유용한 도구다.
특히 LangChain을 쓰고 있다면 callback 기반 통합이 실용적이다. agent run, LLM call, tool call, retriever call을 trace로 잡고, 그 trace를 metric으로 평가한 뒤, CI/CD에서 실패시킬 수 있다. 이 흐름은 앞으로 LLM 애플리케이션 개발에서 거의 기본기가 될 가능성이 높다.
내 추천은 거창하게 evaluation platform부터 만들지 말고, 다음 순서로 시작하는 것이다.
1. 대표 golden set 10개 작성
2. DeepEval로 1~2개 metric만 붙이기
3. 로컬에서 deepeval test run 실행
4. CI에 붙여 regression gate 만들기
5. 실패 케이스를 보며 dataset과 metric을 점진적으로 개선LLM 앱은 이제 “잘 되는 것 같다”로 운영하기에는 너무 중요해졌다. DeepEval 같은 도구의 가치는 바로 그 지점에 있다. 감으로 관리하던 품질을 테스트 가능한 자산으로 바꾸는 것.