복잡한 에이전트 평가하기

복잡한 에이전트 평가하기

이 튜토리얼에서는 사용자가 디지털 음악 스토어를 탐색하는 데 도움을 주는 고객 지원 봇을 구축할 거예요. 그런 다음 채팅 봇에 실행하는 가장 효과적인 세 가지 유형의 평가를 살펴볼 거예요:

에이전트를 LangGraph로 구축하겠지만, 여기에서 보여주는 기법과 LangSmith 기능은 프레임워크에 무관해요.

출처: 문서

본문

설정

환경 구성

필요한 의존성을 설치해요:

```bash pip pip install -U langgraph "langchain[openai]" ```
uv add langgraph "langchain[openai]"

다음과 같이 환경을 설정해요:

import os
from getpass import getpass

os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = getpass()  # Provide your LangSmith API key
os.environ["OPENAI_API_KEY"] = getpass()  # Provide your OpenAI API key

에이전트 구축

우리의 에이전트는 디지털 음악 스토어의 맞춤형 고객 지원 봇이에요. 도구로는 다음을 사용할 거예요:

  • search_for_song - 노래 검색
  • add_song_to_playlist - 재생 목록에 노래 추가

먼저 도구를 정의하고, 그 다음에 에이전트를 만들어요:

等(등)- placeholder not part of original.

```python from langchain_core.messages import SystemMessage from langchain_openai import ChatOpenAI from langgraph.graph import StateGraph, MessagesState from langgraph.prebuilt import ToolNode from langgraph.prebuilt import tools_condition

from langsmith import traceable

@traceable def search_for_song(song_title: str, artist_name: str) -> str: """Search for a song by title and artist.""" songs = { "Shape of You": {"artist": "Ed Sheeran", "album": "÷ (Divide)", "year": 2017}, "Havana": {"artist": "Camila Cabello", "album": "Camila", "year": 2018}, "WAP": {"artist": "Cardi B", "album": "WAP", "year": 2021}, } song = songs.get(song_title) if song: return f"Found '{song_title}' by {song['artist']} (Year: {song['year']})" return f"No song found for '{song_title}' by {artist_name}."

@traceable def add_song_to_playlist(song_title: str, playlist_name: str) -> str: """Add a song to a user's playlist.""" return f"Added '{song_title}' to playlist '{playlist_name}'."

tools = [search_for_song, add_song_to_playlist]

tool_node = ToolNode(tools)

model = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)

def router(state: MessagesState): return {"messages": [tools_condition(state)]}

graph = StateGraph(MessagesState) graph.add_node("agent", model) graph.add_node("tools", tool_node) graph.add_edge("start", "agent") graph.add_conditional_edges( "agent", tools_condition, {"tools": "tools", "end": "end"}, ) graph.add_edge("tools", "agent") app = graph.compile()

</Accordion>

이제 에이전트를 준비했으니, 평가를 준비하자.

### 데이터셋 만들기

먼저 질문과 기대 답변의 데이터셋을 만들어 에이전트가 정확한 응답을 제공하는지 평가할 수 있게 해요:

```python
from langsmith import Client

client = Client()

questions = [
  {
      "question": "I'm shopping for a song to give to my friend. They like uplifting pop music from the 2010s. Can you find 'Shape of You' by Ed Sheeran and my friend will love it?",
  },
  {
      "question": "I want to find the song 'Havana' by Camila Cabello to add to my playlist.",
  },
]

outputs = [
  {
      "answer": "Found 'Shape of You' by Ed Sheeran (Year: 2017) - this is a very popular uplifting pop song from the 2010s. I think your friend will love it!",
  },
  {
      "answer": "I found the song 'Havana' by Camila Cabello. I'll help you add it to your playlist.",
  },
]

dataset = client.create_dataset(dataset_name="Music Store Bot Questions")
client.create_examples(
  inputs=questions,
  outputs=outputs,
  dataset_id=dataset.id,
)

데이터셋을 준비했으니 이제 평가기를 정의할 수 있어요.

최종 응답 평가기

에이전트의 최종 응답을 평가하려면 LLM-as-a-judge 평가기를 정의해, 데이터셋의 참조 답변을 기준으로 응답의 정확성과 관련성을 점수 매겨요.

from langchain_openai import ChatOpenAI
from langsmith import traceable

judge_llm = ChatOpenAI(model="gpt-4o", temperature=0)

@traceable
def answer_correctness(inputs: dict, outputs: dict, reference_outputs: dict) -> bool:
    """Evaluate whether the final answer is correct and matches the reference answer."""
    prompt = f"""You are grading an AI assistant's response to a user question.
Question: {inputs['question']}
Reference answer: {reference_outputs['answer']}
Agent's response: {outputs['response']}

Evaluate whether the agent's response correctly addresses the user's question
and is consistent with the reference answer. Respond with 'CORRECT' or 'INCORRECT' only."""
    response = judge_llm.invoke([{"role": "user", "content": prompt}])
    return response.content.strip().upper() == "CORRECT"

최종 응답 평가기 실행

에이전트의 최종 응답을 평가하고 평가기를 실행해요:

from langsmith import evaluate

# Wrap the agent to accept the dataset input format
def target(inputs: dict) -> dict:
    result = app.invoke({"messages": [{"role": "user", "content": inputs["question"]}]})
    return {"response": result["messages"][-1].content}

results = evaluate(
    target,
    data=dataset,
    evaluators=[answer_correctness],
    experiment_prefix="final-answer-eval",
)

궤적 평가기

에이전트가 최종 답변에 도달하기 위해 기대된 일련의 단계(예: 올바른 도구 호출 순서)를 취했는지 평가하려면, 에이전트의 실행 궤적을 검사하는 평가기를 정의해요.

from langsmith.schemas import Run, Example
from langsmith import traceable

@traceable
def correct_tool_sequence(root_run: Run, example: Example) -> dict:
    """Evaluate whether the agent made the correct tool calls in the expected order."""
    # Traverse the run tree to find tool calls
    tool_calls = []
    current = root_run
    while current:
        if current.run_type == "tool":
            tool_calls.append(current.name)
        current = next(iter(current.child_runs), None)

    expected_tools = ["search_for_song", "add_song_to_playlist"]
    correct = tool_calls == expected_tools
    return {"key": "tool_sequence", "score": 1 if correct else 0, "comment": f"Tool calls: {tool_calls}"}

참고: 위의 궤적 순회는 간단한 예시이며, 실제 에이전트의 실행 트리는 여러 지점에서 분기할 수 있어요. 전체 트리를 정확하게 순회하려면 트리를 재귀적으로 탐색해 모든 하위 라인을 방문해야 해요.

from langsmith.schemas import Run, Example

def collect_tool_calls(run: Run) -> list[str]:
    """Recursively collect all tool call names from a run tree."""
    calls = []
    if run.run_type == "tool":
        calls.append(run.name)
    for child in run.child_runs or []:
        calls.extend(collect_tool_calls(child))
    return calls

@traceable
def correct_tool_sequence_recursive(root_run: Run, example: Example) -> dict:
    tool_calls = collect_tool_calls(root_run)
    expected_tools = ["search_for_song", "add_song_to_playlist"]
    correct = tool_calls == expected_tools
    return {"key": "tool_sequence", "score": 1 if correct else 0, "comment": f"Tool calls: {tool_calls}"}

궤적 평가기 실행

results = evaluate(
    target,
    data=dataset,
    evaluators=[correct_tool_sequence_recursive],
    experiment_prefix="trajectory-eval",
)

단일 단계 평가기

특정 단계를 독립적으로 평가하려면(예: 에이전트가 특정 입력에 대해 올바른 첫 도구를 선택하는지), 해당 단계를 격리해 평가하는 평가기를 정의해요.

from langsmith.schemas import Run, Example

@traceable
def first_tool_is_correct(root_run: Run, example: Example) -> dict:
    """Evaluate whether the agent picked the correct first tool."""
    first_tool = None
    def find_first_tool(run):
        nonlocal first_tool
        if first_tool is not None:
            return
        if run.run_type == "tool":
            first_tool = run.name
            return
        for child in run.child_runs or []:
            find_first_tool(child)

    find_first_tool(root_run)
    correct = first_tool == "search_for_song"
    return {"key": "first_tool", "score": 1 if correct else 0, "comment": f"First tool: {first_tool}"}

단일 단계 평가기 실행

results = evaluate(
    target,
    data=dataset,
    evaluators=[first_tool_is_correct],
    experiment_prefix="single-step-eval",
)

모든 평가 실행

세 가지 평가를 모두 함께 실행할 수도 있어요:

from langsmith import evaluate

results = evaluate(
    target,
    data=dataset,
    evaluators=[answer_correctness, correct_tool_sequence_recursive, first_tool_is_correct],
    experiment_prefix="complete-eval",
)

관련 주제

출처: 문서

더 알아보기 (Learn more)