LiteLLMとDeepEvalでLangGraphエージェントの応答品質を自動評価してみる
はじめに
データ事業本部のkobayashiです。
前回はLangSmithで「実行履歴の可視化と一致度ベースの評価」を扱いました。前回の記事の中でも evaluate() に独自の llm_judge 関数を並べて LLM-as-a-judge を一部試していますが、今回は DeepEval を使って、回答関連性・忠実性・ハルシネーションといった LLM特有の品質指標 をライブラリ標準のメトリクスで網羅的に評価する方法を紹介します。
DeepEvalはLLM-as-a-judge型の評価メトリクスを多数提供する Python ライブラリで、LangGraphエージェントの応答品質を自動評価するのに最適です。
DeepEval の主要メトリクス
| メトリクス | 評価する内容 |
|---|---|
AnswerRelevancyMetric |
入力質問に関連した回答か |
FaithfulnessMetric |
RAGコンテキストに忠実か |
HallucinationMetric |
矛盾する情報が含まれていないか |
ContextualPrecisionMetric |
検索結果の関連性 |
ContextualRecallMetric |
検索結果の網羅性 |
BiasMetric / ToxicityMetric |
バイアス・有害性 |
これらは内部で別のLLMを「審判」として使うメトリクス(LLM-as-a-judge)です。
環境
Python 3.13
litellm 1.83.14
langgraph 1.1.10
langchain-litellm 0.6.4
deepeval 2.0.0
pytest 8.0.0
$ uv pip install litellm langgraph langchain-litellm deepeval pytest
$ export OPENAI_API_KEY="sk-..." # DeepEvalの審判モデル用
$ export ANTHROPIC_API_KEY="sk-ant-..."
エージェントの応答品質を評価
"""DeepEval で LangGraph エージェントの応答品質を評価する。
メトリクス:
- AnswerRelevancyMetric: 質問に関連した回答か
- FaithfulnessMetric: コンテキストに忠実か(ハルシネーションがないか)
- HallucinationMetric: 矛盾した情報を含まないか
"""
from deepeval import evaluate
from deepeval.metrics import (
AnswerRelevancyMetric,
FaithfulnessMetric,
HallucinationMetric,
)
from deepeval.test_case import LLMTestCase
from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_litellm import ChatLiteLLM
@tool
def search_doc(query: str) -> str:
"""社内ドキュメントを検索します。"""
docs = {
"有給": "有給休暇は入社6ヶ月後に10日付与されます。",
"リモート": "リモートワークは週3日まで可能です。",
}
for k, v in docs.items():
if k in query:
return v
return "情報が見つかりませんでした"
llm = ChatLiteLLM(model="openai/gpt-5-mini")
agent = create_agent(model=llm, tools=[search_doc])
def run_agent(question: str) -> tuple[str, list[str]]:
"""エージェントを実行し、(回答, 取得したコンテキスト) を返す。"""
result = agent.invoke({"messages": [{"role": "user", "content": question}]})
final = str(result["messages"][-1].content)
# ツール出力(コンテキスト)を抽出
contexts = [
str(m.content) for m in result["messages"] if getattr(m, "type", None) == "tool"
]
return final, contexts
# 評価対象のテストケース
test_cases: list[LLMTestCase] = []
for q, expected in [
("有給休暇のルールを教えて", "入社6ヶ月後に10日付与"),
("リモートワークの可能日数は?", "週3日まで可能"),
]:
answer, contexts = run_agent(q)
test_cases.append(
LLMTestCase(
input=q,
actual_output=answer,
expected_output=expected,
retrieval_context=contexts,
context=contexts,
)
)
# 評価実行: judge モデルは OPENAI_API_KEY を使った DeepEval デフォルト
# (高性能な GPT が自動選択される)。
# 別プロバイダーに切替えたい場合は LiteLLMModel を import して
# AnswerRelevancyMetric(model=LiteLLMModel(model="anthropic/claude-sonnet-4-6"))
# のように渡せる。
results = evaluate(
test_cases=test_cases,
metrics=[
AnswerRelevancyMetric(threshold=0.7),
FaithfulnessMetric(threshold=0.7),
HallucinationMetric(threshold=0.5),
],
)
print(results)
ポイントを整理します。
LLMTestCase
DeepEvalの評価単位はLLMTestCaseで、以下の情報を持ちます。
input: ユーザーの入力actual_output: エージェントの実際の出力expected_output: 期待される出力(任意)retrieval_context: RAGで検索したコンテキスト(FaithfulnessMetric用)context: 一般的な参照コンテキスト(HallucinationMetric用)
コンテキスト抽出
LangGraphエージェントの場合、messagesからtype == "tool"のメッセージを取り出すことで、エージェントが参照した情報(ツール出力)をretrieval_contextとして渡せます。これがあると Faithfulness を正しく評価できます。
閾値
threshold=0.7は0.7以上で合格扱い。CIで使う場合は失敗時にビルドを落とせます。
実行結果は以下のようになります。
$ python agent_eval.py
✨ You're running DeepEval's latest Answer Relevancy Metric! (using gpt-5.4, strict=False, async_mode=True)...
✨ You're running DeepEval's latest Faithfulness Metric! (using gpt-5.4, strict=False, async_mode=True)...
✨ You're running DeepEval's latest Hallucination Metric! (using gpt-5.4, strict=False, async_mode=True)...
======================================================================
Metrics Summary
- ✅ Answer Relevancy (score: 1.0, threshold: 0.7, evaluation model: gpt-5.4, reason: The score is 1.00 because the response appears fully relevant to the question, with no irrelevant statements noted.)
- ✅ Faithfulness (score: 1.0, threshold: 0.7, evaluation model: gpt-5.4, reason: The score is 1.00 because there are no contradictions, so the actual output appears fully consistent with the retrieval context.)
- ✅ Hallucination (score: 0.0, threshold: 0.5, evaluation model: gpt-5.4, reason: The score is 0.00 because the actual output fully aligns with the context, correctly stating that remote work is possible up to 3 days per week.)
For test case:
- input: リモートワークの可能日数は?
- actual output: 原則、週3日まで可能です。
- expected output: 週3日まで可能
- retrieval context: ['リモートワークは週3日まで可能です。']
======================================================================
Overall Metric Pass Rates
Answer Relevancy: 100.00% pass rate
Faithfulness: 100.00% pass rate
Hallucination: 100.00% pass rate
✓ Evaluation completed 🎉! (time taken: 30.32s | token cost: 0.1013725 USD)
» Test Results (2 total tests):
» Pass Rate: 100.0% | Passed: 2 | Failed: 0
判定モデルとして DeepEval が自動的に gpt-5.4 を選択しています(OpenAI APIキーが設定されているため)。Hallucination のスコアは「低いほど良い」指標なので0.0でPASSになっています。
DeepEval は最後に 総トークンコスト(今回は約 $0.10)まで表示してくれるため、CI で評価コストの予算管理がしやすい点が嬉しいポイントです。
pytest で CI に組み込む
DeepEvalはpytestと統合されており、assert_testでテスト関数として書くと CI に組み込めます。
"""pytest 経由で DeepEval を回す。CIに組み込みやすい。"""
import pytest
from deepeval import assert_test
from deepeval.metrics import AnswerRelevancyMetric, FaithfulnessMetric
from deepeval.test_case import LLMTestCase
from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_litellm import ChatLiteLLM
@tool
def search_doc(query: str) -> str:
"""社内ドキュメントを検索します。"""
return "有給休暇は入社6ヶ月後に10日付与されます。"
llm = ChatLiteLLM(model="openai/gpt-5-mini")
agent = create_agent(model=llm, tools=[search_doc])
@pytest.mark.parametrize(
("question", "expected_substr"),
[
("有給休暇は何日?", "10日"),
("入社後いつから有給?", "6ヶ月"),
],
)
def test_answer_quality(question: str, expected_substr: str) -> None:
result = agent.invoke({"messages": [{"role": "user", "content": question}]})
answer = str(result["messages"][-1].content)
contexts = [
str(m.content) for m in result["messages"] if getattr(m, "type", None) == "tool"
]
test_case = LLMTestCase(
input=question,
actual_output=answer,
expected_output=expected_substr,
retrieval_context=contexts,
)
assert_test(
test_case,
[
AnswerRelevancyMetric(threshold=0.7),
FaithfulnessMetric(threshold=0.7),
],
)
実行結果は以下のようになります。
$ pytest test_agent_pytest.py -v
============================= test session starts ==============================
plugins: deepeval-3.9.9, asyncio-1.3.0, langsmith-0.7.38, ...
collected 2 items
test_agent_pytest.py::test_answer_quality[有給休暇は何日?-10日] PASSED [ 50%]
test_agent_pytest.py::test_answer_quality[入社後いつから有給?-6ヶ月] PASSED [100%]
=================== 2 passed, 1 warning in 60.21s (0:01:00) ====================
AnswerRelevancyMetric と FaithfulnessMetric の両方を満たした2件がPASSしました。実行時間は約60秒(メトリクスごとに判定LLMを内部で呼ぶため、1テストあたり25秒前後)です。CIで実行する場合はこの時間と判定LLMのトークンコストを見越したビルド予算が必要です。
通常のpytestコマンドで動きますが、deepeval test run コマンドを使うとレポートが綺麗にまとまり、deepeval loginしておけば結果が Confident AI(DeepEvalのSaaS)にも送られます。
LangSmith との使い分け
| 観点 | LangSmith | DeepEval |
|---|---|---|
| 強み | トレース可視化、本番監視 | LLM特有メトリクスのライブラリ充実 |
| データセット | UIで管理 | コード中心 |
| 評価方法 | 任意関数(自由度高) | 既製メトリクスが豊富 |
| CI統合 | 可能 | pytestネイティブ |
両者は併用できます。LangSmithでトレース・監視、DeepEvalで品質評価という組み合わせが現実的です。
LiteLLMでのモデル使い分け
| ロール | モデル | 理由 |
|---|---|---|
| ReActエージェント(被評価対象) | openai/gpt-5-mini |
評価される側。安価モデルで動かすことで「安いモデルでも品質が出るか」を測る |
| DeepEval judge LLM | DeepEvalデフォルト(gpt-5.4) | 評価する側。判定品質を担保するため高性能モデルが選択される |
DeepEval は「評価される側」と「評価する側(judge)」で別のモデルを使うのが標準です。LiteLLMで被評価モデルを安価なGPT-5-miniに固定し、judgeはDeepEvalデフォルトの高性能モデル(今回は gpt-5.4)に任せることで、コスト最適化したエージェントの品質を高品質モデルでチェックするという構造が自然に組めます。
被評価モデルを anthropic/claude-sonnet-4-6 に切り替えて再実行すれば、同じjudgeでGPTとClaudeの品質スコアを横並びで比較できます。
まとめ
DeepEval で LangGraph エージェントの応答品質を評価する方法を、evaluate() 直叩きと pytest 統合の両方で紹介しました。
LLMTestCase に LangGraph の tool メッセージを retrieval_context として渡すことで、AnswerRelevancyMetric / FaithfulnessMetric / HallucinationMetric といった LLM-as-a-judge 型のメトリクスがそのまま使え、assert_test で書けば pytest ベースの CI にもそのまま組み込めます。LangSmith は実行履歴の可視化と運用監視、DeepEval は応答品質のメトリクス評価、と役割分担して併用するのが現実的で、LiteLLM で被評価モデルだけを差し替えれば同じ judge で複数モデルの品質スコアを横並び比較できる点も、本構成の大きな利点です。
最後まで読んでいただきありがとうございました。









