에이전트 평가(Evaluating Agents)
이메일 어시스턴트는 라우터를 사용하여 이메일을 분류한 후 응답 생성을 위해 에이전트에게 전달합니다. 실제 운영 환경에서 제대로 작동할지 어떻게 확신할 수 있을까요? 그래서 테스트가 중요한 이유입니다: 응답 품질, 토큰 사용량, 지연 시간, 분류 정확도 같은 정량적 지표를 통해 에이전트 아키텍처에 대한 결정을 안내합니다.
LangSmith는 에이전트 테스트를 위한 두 가지 주요 방법을 제공합니다.

Resources
- Notebook Reference: evaluation.ipynb
- For running evaluations: test_tools.py, /tests/
- For LangSmith Studio: src/email_assistant
- Slides: Building Ambient Agents with LangGraph - Building Agents & Evaluations.pdf
환경 변수
from dotenv import load_dotenv
load_dotenv("../../.env", override=True)True
평가 실행 방법
Pytest / Vitest
Pytest와 Vitest는 Python 및 JavaScript 에서 테스트를 작성하기 위한 강력한 도구입니다. LangSmith는 이러한 프레임워크와 통합되어, 결과를 LangSmith에 기록하는 테스트를 작성하고 실행할 수 있도록 지원합니다. 이 노트북에서는 Pytest를 사용합니다.
- Pytest는 해당 프레임워크에 이미 익숙한 개발자가 시작하기에 좋은 방법입니다.
- Pytest는 각 에이전트 테스트 케이스에 일반화하기 어려운 특정 검증과 성공 기준이 필요한 더 복잡한 평가에 유용합니다.
LangSmith 데이터셋
LangSmith에서 데이터셋을 생성하고 LangSmith 평가 API를 사용하여 해당 데이터셋에 대해 어시스턴트를 실행할 수 있습니다.
- LangSmith 데이터셋은 테스트 스위트를 협력하여 구축하는 팀에게 유용합니다.
- 운영 트레이스, 어노테이션 큐, 합성 데이터 생성 등을 활용하여 계속해서 확장되는 골든 데이터셋에 예제를 추가할 수 있습니다.
- LangSmith 데이터셋은 데이터셋의 모든 테스트 케이스에 적용할 수 있는 평가자(예: 유사도, 정확히 일치 정확도 등)를 정의할 수 있을 때 특히 유용합니다.
테스트 케이스
테스트는 종종 테스트 케이스를 정의하는 것에서 시작하며, 이는 어려운 과정일 수 있습니다. 여기서는 처리하고자 하는 예시 이메일 모음과 테스트할 몇 가지 사항을 eval/email_dataset.py 파일에 다음과 같이 정의했습니다.
- Input Emails: 다양한 이메일 예시 모음
- Ground Truth Classifications:
Respond,Notify,Ignore - Expected Tool Calls: 응답이 필요한 각 이메일에 대해 호출되는 도구
- Response Criteria: 회신이 필요한 이메일에 대한 좋은 응답의 조건
참고로, 우리는 다음과 같은 두 종류의 테스트를 모두 가지고 있습니다.
- 엔드-투-엔드 “통합” 테스트 (예: 입력 이메일 → 에이전트 → 최종 결과물 vs 응답 기준)
- 워크플로우의 특정 단계를 위한 테스트 (예: 입력 이메일 → 에이전트 → 분류 vs 정답 분류)
from email_assistant.eval.email_dataset import (
email_inputs,
expected_tool_calls,
response_criteria_list,
triage_outputs_list,
)
test_case_ix = 0
print("Email Input:", email_inputs[test_case_ix])
print("Expected Triage Output:", triage_outputs_list[test_case_ix])
print("Expected Tool Calls:", expected_tool_calls[test_case_ix])
print("Response Criteria:", response_criteria_list[test_case_ix])Email Input: {'author': '앨리스 스미스 <[email protected]>', 'to': '랜스 마틴 <[email protected]>', 'subject': 'API 문서에 대한 간단한 질문', 'email_thread': '안녕하세요, 랜스님,\n\n새로운 인증 서비스의 API 문서를 검토하던 중, 몇몇 엔드포인트가 사양에서 누락된 것을 발견했습니다. 이것이 의도된 것인지, 아니면 문서를 업데이트해야 하는지 명확히 설명해주실 수 있나요?\n\n특히 다음 엔드포인트에 대해 문의드립니다:\n- /auth/refresh\n- /auth/validate\n\n감사합니다!\n앨리스 드림'}
Expected Triage Output: respond
Expected Tool Calls: ['write_email', 'done']
Response Criteria:
• write_email 도구 호출을 사용하여 이메일을 보내 질문을 인지했음을 알리고 조사가 진행될 것임을 확인합니다.
Pytest 예제
Pytest를 사용하여 워크플로우의 특정 부분에 대한 테스트를 작성하는 방법을 알아봅니다.
email_assistant가 이메일에 응답할 때 올바른 도구 호출을 하는지 테스트합니다.
from email_assistant.email_assistant import email_assistant
from email_assistant.eval.email_dataset import email_inputs, expected_tool_calls
from email_assistant.utils import extract_tool_calls, format_messages_string
from langsmith import testing as t
import pytest
@pytest.mark.langsmith
@pytest.mark.parametrize(
("email_input", "expected_calls"),
[
# 이메일 답장이 예상되는 몇 가지 예시를 선택하세요
(email_inputs[0], expected_tool_calls[0]),
(email_inputs[3], expected_tool_calls[3]),
],
)
def test_email_dataset_tool_calls(email_input, expected_calls):
"""
이메일 처리 과정에서 예상되는 도구 호출이 포함되는지 테스트합니다.
이 테스트는 이메일 처리 중 모든 예상 도구가 호출되는지 확인하지만,
도구 호출 순서나 도구별 호출 횟수는 검사하지 않습니다.
필요 시 이러한 측면에 대한 추가 검사를 포함시킬 수 있습니다.
"""
# 이메일 어시스턴트를 실행합니다.
messages = [("user", str(email_input))]
result = email_assistant.invoke({"messages": messages})
# 메시지 목록에서 도구 호출 추출합니다.
extracted_tool_calls = extract_tool_calls(result["messages"])
# 추출된 도구 호출에 예상된 모든 호출이 포함되어 있는지 확인합니다.
missing_calls = [
call for call in expected_calls if call.lower() not in extract_tool_calls
]
t.log_outputs(
{
"missing_calls": missing_calls,
"extracted_tool_calls": extracted_tool_calls,
"response": format_messages_string(result["messages"]),
}
)
# 예상된 호출이 누락되지 않으면 테스트가 통과됩니다.
assert len(missing_calls) == 0- Pytest로 실행하고 테스트 결과를 LangSmith에 로그로 남기기 위해서는, 함수에
@pytest.mark.langsmith데코레이터만 추가하면 됩니다. 이렇게 하면 테스트 결과가 LangSmith에 로그로 기록됩니다. @pytest.mark.parametrize를 통해 데이터셋 예제를 테스트 함수에 전달할 수 있습니다.
Pytest 실행하기
커맨드 라인에서 테스트를 실행할 수 있습니다. 위의 코드를 파이썬 파일로 정의했습니다. 프로젝트 루트에서 다음 명령어를 실행하세요:
LANGSMITH_TEST_SUITE='Email assistant: Test Tools For Interrupt' pytest test_tools.py실험 결과 보기
LangSmith UI에서 결과를 확인할 수 있습니다.
assert len(missing_calls) == 0은 LangSmith의 Pass 열에 기록됩니다.
log_outputs는 Outputs 열로 전달되며, 함수 인수는 Inputs 열로 전달됩니다. @pytest.mark.parametrize로 전달된 각 입력은 LangSmith의 LANGSMITH_TEST_SUITE 프로젝트 이름 아래 별도의 행으로 기록되며, 이는 Datasets & Experiments에서 확인할 수 있습니다.

LangSmith 데이터셋 예제

LangSmith 데이터셋으로 평가를 실행하는 방법을 살펴봅니다. 이전 Pytest 예제에서는 이메일 어시스턴트의 도구 호출 정확도를 평가했습니다. 이번에 평가할 데이터셋은 이메일 어시스턴트의 분류(triage) 단계에 특화되어 있으며, 이메일에 응답이 필요한지 여부를 분류하는 것입니다.
데이터셋 정의
LangSmith SDK를 사용하여 LangSmith에서 데이터셋을 생성할 수 있습니다. 아래 코드는 eval/email_dataset.py 파일에 있는 테스트 케이스들로 데이터셋을 생성합니다.
from email_assistant.eval.email_dataset import examples_triage
from langsmith import Client
# LangSmith 클라이언트 초기화
client = Client()
# 데이터셋 이름
dataset_name = "E-mail Triage Evaluation"
# 데이터셋이 존재하지 않으면 신규로 생성합니다.
if not client.has_dataset(dataset_name=dataset_name):
dataset = client.create_dataset(
dataset_name=dataset_name,
description="이메일과 분류 결정에 대한 데이터셋입니다.",
)
# 데이터셋에 예제 추가
client.create_examples(
dataset_id=dataset.id,
examples=examples_triage,
)대상 함수
데이터셋은 다음과 같은 구조를 가지고 있으며, 이메일 입력과 이메일에 대한 정답(ground truth) 분류 결과를 출력으로 포함합니다:
examples_triage = [
{
"inputs": {"email_input": email_input_1},
"outputs": {"classification": triage_output_1}, # 참고: 이것은 생성된 데이터셋에서 reference_output이 됩니다
}, ...
]from pprint import pprint
print("데이터셋 예제 입력 (inputs):")
pprint(examples_triage[0]["inputs"])
print()
print("데이터셋 예제 참조 출력 (reference_outputs):")
pprint(examples_triage[0]["outputs"])데이터셋 예제 입력 (inputs):
{'email_input': {'author': '앨리스 스미스 <[email protected]>',
'email_thread': '안녕하세요, 랜스님,\n'
'\n'
'새로운 인증 서비스의 API 문서를 검토하던 중, 몇몇 엔드포인트가 사양에서 '
'누락된 것을 발견했습니다. 이것이 의도된 것인지, 아니면 문서를 업데이트해야 '
'하는지 명확히 설명해주실 수 있나요?\n'
'\n'
'특히 다음 엔드포인트에 대해 문의드립니다:\n'
'- /auth/refresh\n'
'- /auth/validate\n'
'\n'
'감사합니다!\n'
'앨리스 드림',
'subject': 'API 문서에 대한 간단한 질문',
'to': '랜스 마틴 <[email protected]>'}}
데이터셋 예제 참조 출력 (reference_outputs):
{'classification': 'respond'}
- 데이터셋 입력을 받아 이메일 어시스턴트에 전달하는 함수를 정의합니다.
- LangSmith evaluate API는
inputs딕셔너리를 이 함수에 전달합니다. - 그러면 이 함수는 에이전트의 출력이 담긴 딕셔너리를 반환합니다.
- 분류(triage) 단계를 평가하고 있기 때문에, 분류 결정만 반환하면 됩니다.
email_assistant.nodes["triage_router"].invoke(
{
"email_input": examples_triage[0]["inputs"]["email_input"],
}
)📧 Classification: RESPOND - This email requires a response
Command(update={'classification_decision': 'respond', 'messages': [{'role': 'user', 'content': 'Respond to the email: \n\n**Subject**: API 문서에 대한 간단한 질문\n**From**: 앨리스 스미스 <[email protected]>\n**To**: 랜스 마틴 <[email protected]>\n\n안녕하세요, 랜스님,\n\n새로운 인증 서비스의 API 문서를 검토하던 중, 몇몇 엔드포인트가 사양에서 누락된 것을 발견했습니다. 이것이 의도된 것인지, 아니면 문서를 업데이트해야 하는지 명확히 설명해주실 수 있나요?\n\n특히 다음 엔드포인트에 대해 문의드립니다:\n- /auth/refresh\n- /auth/validate\n\n감사합니다!\n앨리스 드림\n\n---\n'}]}, goto='response_agent')
def target_email_assistant(inputs: dict) -> dict:
"""워크플로우 기반 이메일 어시스턴트를 통해 이메일을 처리합니다."""
# 이메일 어시스턴트에서 triage_router 노드를 가져옴
triage_router = email_assistant.nodes["triage_router"]
# 입력된 이메일을 triage_router에 전달하여 분류 작업 수행
response = triage_router.invoke(
{
"email_input": inputs["email_input"],
}
)
# 분류 결정 결과를 반환
# 분류 결정 결과는 Command 객체의 update에 dict 형태로 담겨있음
return {
"classification_decision": response.update["classification_decision"],
}평가자 함수
이제 평가자 함수를 생성합니다. 무엇을 평가하고 싶을까요? 데이터셋에는 참조 출력이 있고, 위의 함수에서 정의된 에이전트 출력이 있습니다.
- 참조 출력:
"reference_outputs": {"classification": triage_output_1} ... - 에이전트 출력:
"outputs": {"classification_decision": agent_output_1} ...
에이전트의 출력이 참조 출력과 일치하는지 평가하고 싶습니다. 따라서 두 값을 비교하는 평가자 함수만 있으면 됩니다. 여기서 outputs는 에이전트의 출력이고 reference_outputs는 데이터셋의 참조 출력입니다.
def classification_evaluator(
outputs: dict,
reference_outputs: dict,
) -> bool:
"""답변이 예상 답변과 정확히 일치하는지 확인합니다."""
# 에이전트의 분류 결정과 참조 분류 결과를 비교
result: bool = (
outputs["classification_decision"].lower()
== reference_outputs["classification"].lower()
)
return result평가 실행하기
evaluate API는 데이터셋의 inputs 딕셔너리를 대상 함수에 전달합니다. 데이터셋의 reference_outputs 딕셔너리를 평가자 함수에 전달합니다. 그리고 에이전트의 outputs를 평가자 함수에 전달합니다.
이는 Pytest에서 했던 것과 유사합니다: Pytest에서는 @pytest.mark.parametrize를 사용하여 데이터셋 예제의 입력값과 참조 출력값을 테스트 함수에 전달했습니다.
experiment_results_workflow = client.evaluate(
# 에이전트 실행
target_email_assistant,
# 데이터셋 이름
data=dataset_name,
# 평가자
evaluators=[classification_evaluator],
# 실험 이름
experiment_prefix="E-mail assistant workflow",
# 동시 평가 실행 수
max_concurrency=2,
)View the evaluation results for experiment: 'E-mail assistant workflow-c5757d14' at:
https://smith.langchain.com/o/92657a09-ac43-48a7-9363-ed8025ba42d7/datasets/59ad2887-e26b-490c-bbcf-f45d1ebcf674/compare?selectedSessions=b6e23193-0117-424f-81e0-b76a5fb395a3
0it [00:00, ?it/s]
🚫 Classification: IGNORE - This email can be safely ignored
🚫 Classification: IGNORE - This email can be safely ignored
🔔 Classification: NOTIFY - This email contains important information
📧 Classification: RESPOND - This email requires a response
📧 Classification: RESPOND - This email requires a response
📧 Classification: RESPOND - This email requires a response
🔔 Classification: NOTIFY - This email contains important information
🔔 Classification: NOTIFY - This email contains important information
🔔 Classification: NOTIFY - This email contains important information
🔔 Classification: NOTIFY - This email contains important information
📧 Classification: RESPOND - This email requires a response
📧 Classification: RESPOND - This email requires a response
🚫 Classification: IGNORE - This email can be safely ignored
📧 Classification: RESPOND - This email requires a response
📧 Classification: RESPOND - This email requires a response
📧 Classification: RESPOND - This email requires a response
테스트 결과는 LangSmith UI에서 확인할 수 있습니다.

LLM-as-Judge 평가
평가(triage) 단계에 대한 단위 테스트(evaluate() 사용)와 도구 호출에 대한 테스트(Pytest 사용)를 살펴보았습니다.
이제 LLM을 평가자로 활용하여 일련의 성공 기준에 따라 에이전트의 실행을 평가하는 방법을 소개하겠습니다.

먼저, 평가 등급과 평가 등급에 대한 근거를 포함하는 LLM 채점자용 구조화된 출력 스키마를 정의합니다.
from pydantic import BaseModel, Field
from langchain.chat_models import init_chat_model
class CriteriaGrade(BaseModel):
"""특정 기준에 따라 응답을 채점합니다."""
justification: str = Field(
description="응답의 구체적인 예시를 포함한 등급 및 점수에 대한 근거입니다."
)
grade: bool = Field(description="응답이 제공된 기준을 충족합니까?")
# 각 테스트마다 재생성하지 않도록 평가용 전역 LLM 생성
criteria_eval_llm = init_chat_model("openai:gpt-4.1")
criteria_eval_structured_llm = criteria_eval_llm.with_structured_output(CriteriaGrade)email_input = email_inputs[0]
print("Email Input:")
pprint(email_input)
print()
success_criteria = response_criteria_list[0]
print("Success Criteria:", success_criteria)Email Input:
{'author': '앨리스 스미스 <[email protected]>',
'email_thread': '안녕하세요, 랜스님,\n'
'\n'
'새로운 인증 서비스의 API 문서를 검토하던 중, 몇몇 엔드포인트가 사양에서 누락된 것을 발견했습니다. '
'이것이 의도된 것인지, 아니면 문서를 업데이트해야 하는지 명확히 설명해주실 수 있나요?\n'
'\n'
'특히 다음 엔드포인트에 대해 문의드립니다:\n'
'- /auth/refresh\n'
'- /auth/validate\n'
'\n'
'감사합니다!\n'
'앨리스 드림',
'subject': 'API 문서에 대한 간단한 질문',
'to': '랜스 마틴 <[email protected]>'}
Success Criteria:
• write_email 도구 호출을 사용하여 이메일을 보내 질문을 인지했음을 알리고 조사가 진행될 것임을 확인합니다.
이메일 어시스턴트가 이메일 입력을 받아 문자열로 응답을 생성하면, 이를 LLM 평가자가 평가하여 등급과 근거를 제공합니다.
response = email_assistant.invoke({"email_input": email_input})
response📧 Classification: RESPOND - This email requires a response
{'messages': [HumanMessage(content='Respond to the email: \n\n**Subject**: API 문서에 대한 간단한 질문\n**From**: 앨리스 스미스 <[email protected]>\n**To**: 랜스 마틴 <[email protected]>\n\n안녕하세요, 랜스님,\n\n새로운 인증 서비스의 API 문서를 검토하던 중, 몇몇 엔드포인트가 사양에서 누락된 것을 발견했습니다. 이것이 의도된 것인지, 아니면 문서를 업데이트해야 하는지 명확히 설명해주실 수 있나요?\n\n특히 다음 엔드포인트에 대해 문의드립니다:\n- /auth/refresh\n- /auth/validate\n\n감사합니다!\n앨리스 드림\n\n---\n', additional_kwargs={}, response_metadata={}, id='989e34f7-6fb8-469b-b808-db7bb8ecb573'),
AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_5JXSDY2xy9svZCz0gqVQbSBv', 'function': {'arguments': '{"to":"[email protected]","subject":"Re: API 문서에 대한 간단한 질문","content":"안녕하세요, 앨리스님.\\n\\n문의해주신 새로운 인증 서비스의 API 문서에서 /auth/refresh 및 /auth/validate 엔드포인트가 누락된 부분에 대해 확인하겠습니다. 관련 사양이 의도적으로 제외된 것인지, 아니면 문서 업데이트가 필요한지 개발팀과 논의 후 정확한 정보를 전달드리겠습니다.\\n\\n확인 후 1~2영업일 내로 다시 안내드리겠습니다. 혹시 추가로 궁금하신 점이 있으시면 언제든 말씀해 주세요.\\n\\n감사합니다.\\n랜스 드림"}', 'name': 'write_email'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 165, 'prompt_tokens': 1128, 'total_tokens': 1293, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4.1-2025-04-14', 'system_fingerprint': 'fp_144ea3b974', 'id': 'chatcmpl-CUjGEaerRNZuBboUiF9aabRA6CJwd', 'service_tier': 'default', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--36d6df54-1738-4234-b9f6-b20e1dd8c97e-0', tool_calls=[{'name': 'write_email', 'args': {'to': '[email protected]', 'subject': 'Re: API 문서에 대한 간단한 질문', 'content': '안녕하세요, 앨리스님.\n\n문의해주신 새로운 인증 서비스의 API 문서에서 /auth/refresh 및 /auth/validate 엔드포인트가 누락된 부분에 대해 확인하겠습니다. 관련 사양이 의도적으로 제외된 것인지, 아니면 문서 업데이트가 필요한지 개발팀과 논의 후 정확한 정보를 전달드리겠습니다.\n\n확인 후 1~2영업일 내로 다시 안내드리겠습니다. 혹시 추가로 궁금하신 점이 있으시면 언제든 말씀해 주세요.\n\n감사합니다.\n랜스 드림'}, 'id': 'call_5JXSDY2xy9svZCz0gqVQbSBv', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1128, 'output_tokens': 165, 'total_tokens': 1293, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}),
ToolMessage(content="Email sent to [email protected] with subject 'Re: API 문서에 대한 간단한 질문' and content: 안녕하세요, 앨리스님.\n\n문의해주신 새로운 인증 서비스의 API 문서에서 /auth/refresh 및 /auth/validate 엔드포인트가 누락된 부분에 대해 확인하겠습니다. 관련 사양이 의도적으로 제외된 것인지, 아니면 문서 업데이트가 필요한지 개발팀과 논의 후 정확한 정보를 전달드리겠습니다.\n\n확인 후 1~2영업일 내로 다시 안내드리겠습니다. 혹시 추가로 궁금하신 점이 있으시면 언제든 말씀해 주세요.\n\n감사합니다.\n랜스 드림", id='a81f5737-6a71-4e1e-9407-b6d2a2a8eecc', tool_call_id='call_5JXSDY2xy9svZCz0gqVQbSBv'),
AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_viuHjBp4veeOQqiFLMAEWMm5', 'function': {'arguments': '{"done":true}', 'name': 'Done'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 13, 'prompt_tokens': 1450, 'total_tokens': 1463, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4.1-2025-04-14', 'system_fingerprint': 'fp_564354cebb', 'id': 'chatcmpl-CUjGI59vCUntRs75KqN644oZBc99W', 'service_tier': 'default', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--4f9c8de8-1968-4ec9-b7b0-18f6532df494-0', tool_calls=[{'name': 'Done', 'args': {'done': True}, 'id': 'call_viuHjBp4veeOQqiFLMAEWMm5', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1450, 'output_tokens': 13, 'total_tokens': 1463, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})],
'email_input': {'author': '앨리스 스미스 <[email protected]>',
'to': '랜스 마틴 <[email protected]>',
'subject': 'API 문서에 대한 간단한 질문',
'email_thread': '안녕하세요, 랜스님,\n\n새로운 인증 서비스의 API 문서를 검토하던 중, 몇몇 엔드포인트가 사양에서 누락된 것을 발견했습니다. 이것이 의도된 것인지, 아니면 문서를 업데이트해야 하는지 명확히 설명해주실 수 있나요?\n\n특히 다음 엔드포인트에 대해 문의드립니다:\n- /auth/refresh\n- /auth/validate\n\n감사합니다!\n앨리스 드림'},
'classification_decision': 'respond'}
from email_assistant.eval.prompts import RESPONSE_CRITERIA_SYSTEM_PROMPT
RESPONSE_CRITERIA_SYSTEM_PROMPT"You are evaluating an email assistant that works on behalf of a user.\n\nYou will see a sequence of messages, starting with an email sent to the user. \n\nYou will then see the assistant's response to this email on behalf of the user, which includes any tool calls made (e.g., write_email, schedule_meeting, check_calendar_availability, done).\n\nYou will also see a list of criteria that the assistant's response must meet.\n\nYour job is to evaluate if the assistant's response meets ALL the criteria bullet points provided.\n\nIMPORTANT EVALUATION INSTRUCTIONS:\n1. The assistant's response is formatted as a list of messages.\n2. The response criteria are formatted as bullet points (•)\n3. You must evaluate the response against EACH bullet point individually\n4. ALL bullet points must be met for the response to receive a 'True' grade\n5. For each bullet point, cite specific text from the response that satisfies or fails to satisfy it\n6. Be objective and rigorous in your evaluation\n7. In your justification, clearly indicate which criteria were met and which were not\n7. If ANY criteria are not met, the overall grade must be 'False'\n\nYour output will be used for automated testing, so maintain a consistent evaluation approach."
from langchain_core.messages.utils import get_buffer_string
all_messages_str = get_buffer_string(response["messages"])
eval_result = criteria_eval_structured_llm.invoke(
[
{"role": "system", "content": RESPONSE_CRITERIA_SYSTEM_PROMPT},
{
"role": "user",
"content": f"""\n\n Response criteria: {success_criteria} \n\n Assistant's response: \n\n {all_messages_str} \n\n Evaluate whether the assistant's response meets the criteria and provide justification for your evaluation.""",
},
]
)
eval_resultCriteriaGrade(justification="The response uses the write_email tool to send an email to [email protected] with the requested content. The email acknowledges receipt of Alice's question and confirms that Lance will investigate the issue and follow up after discussing with the development team. This matches the response criteria that required acknowledging the inquiry and confirming that an investigation will take place.", grade=True)
더 큰 테스트 스위트 실행하기
Pytest와 evaluate()를 사용한 에이전트 평가 방법과 LLM 심사자 활용 예제를 살펴보았습니다.
이제 더 많은 테스트 케이스로 평가를 실행하여 다양한 상황에서 에이전트의 성능을 종합적으로 확인할 수 있습니다.
더 큰 테스트 스위트로 email_assistant를 실행해 보겠습니다.
LANGSMITH_TEST_SUITE="Email assistant: Test Full Response Interrupt" \
LANGSMITH_EXPERIMENT="email_assistant" \
pytest tests/test_response.py --agent-module email_assistanttest_response.py에서 몇 가지를 확인할 수 있습니다.
데이터셋 예제를 pytest를 실행하고 LANGSMITH_TEST_SUITE에 로깅할 함수로 전달합니다:
# 참조 출력 키
@pytest.mark.langsmith(output_keys=["criteria"])
# 변수명과 테스트 케이스가 담긴 튜플 리스트
# 각 테스트 케이스는 (email_input, email_name, criteria, expected_calls)
@pytest.mark.parametrize(("email_input", "email_name", "criteria,expected_calls"), create_response_test_cases())
def test_response_criteria_evaluation(email_input, email_name, criteria, expected_calls):채점 스키마와 함께 LLM 평가자를 사용합니다:
class CriteriaGrade(BaseModel):
"""특정 기준에 따라 응답을 채점합니다."""
grade: bool = Field(description="응답이 제공된 기준을 충족합니까?")
justification: str = Field(description="응답의 구체적인 예시를 포함한 등급 및 점수에 대한 근거입니다.")기준을 기반으로 에이전트 응답을 평가합니다:
# 기준에 따라 평가
eval_result = criteria_eval_structured_llm.invoke([
{"role": "system",
"content": RESPONSE_CRITERIA_SYSTEM_PROMPT},
{"role": "user",
"content": f"""\n\n 응답 기준: {criteria} \n\n 어시스턴트의 응답: \n\n {all_messages_str} \n\n 어시스턴트의 응답이 기준을 충족하는지 평가하고 평가에 대한 근거를 제시하세요."""}
])! LANGSMITH_TEST_SUITE="Email assistant: Test Full Response Interrupt" \
LANGSMITH_EXPERIMENT="email_assistant" \
pytest tests/test_response.py --agent-module email_assistant# TODO: 랭스미스에서 실험 이름을 확인하고 여기에 복사&붙여넣기 하세요
experiment_name = "email_assistant:c5757d14"
email_assistant_experiment_results = client.read_project(
project_name=experiment_name,
include_stats=True,
)
print("Latency p50:", email_assistant_experiment_results.latency_p50)
print("Latency p99:", email_assistant_experiment_results.latency_p99)
print("Token Usage:", email_assistant_experiment_results.total_tokens)
print("Feedback Stats:", email_assistant_experiment_results.feedback_stats)Latency p50: 0:00:02.340000
Latency p99: 0:00:15.989650
Token Usage: 12809
Feedback Stats: {'classification_evaluator': {'n': 16, 'avg': 0.9375, 'stdev': 0.24206145913796356, 'errors': 0, 'values': {}, 'type': 'primary', 'contains_thread_feedback': False}}