33 minute read

Mastering LangChain Agent Evaluation: Tools, Tasks & Performance

Build Reliable AI Agents Now

Evaluating AI agents is not just a best practice; it’s a critical necessity for building reliable, production-ready systems. Without robust evaluation, your LangChain agents can silently fail, leading to poor user experiences, incorrect outputs, and increased operational costs. This comprehensive guide will equip you with the tools, methodologies, and best practices to master LangChain agent evaluation, ensuring your agents perform as expected every time.

You’ll learn how to set up evaluation environments, define relevant metrics, create effective test cases, and leverage powerful tools like LangSmith for automated assessment. By the end, you’ll have a clear framework for continuously improving your LLM agents. Let’s dive in and elevate your agent development process.

The Indispensable Need for LangChain Agent Evaluation

Developing intelligent agents capable of performing complex tasks is exciting, but their inherent non-determinism presents unique challenges. Unlike traditional software, AI agents, especially those powered by large language models (LLMs), don’t always produce the same output for identical inputs. This variability makes thorough evaluation paramount.

LangChain Agent Evaluation helps you systematically understand how your agent behaves across different scenarios. It allows you to identify regressions, measure improvements, and ensure your agent reliably achieves its goals. Ultimately, robust evaluation is key to building agents you can trust in production, as discussed in Building Production-Ready LangChain Agents.

Why Evaluate Your LLM Agents?

  • Reliability and Trust: Ensures your agent consistently delivers accurate and helpful responses, fostering user trust.
  • Performance Monitoring: Tracks key metrics like accuracy, latency, and cost over time, helping you optimize resources.
  • Debugging and Improvement: Pinpoints specific failures, incorrect tool usage, or reasoning flaws, guiding your development efforts.
  • Cost Efficiency: Reduces unnecessary API calls and token usage by identifying inefficient reasoning paths or redundant tool calls.
  • User Satisfaction: Leads to a better end-user experience by minimizing errors and maximizing task completion rates.

Understanding LangChain Agents and Their Evaluation Surface

Before diving into evaluation, let’s briefly recap what makes a LangChain agent. An agent combines an LLM with a set of tools, memory, and a prompt to perform multi-step reasoning. It observes the environment, decides which action to take, executes that action using a tool, and then repeats the process until a task is complete.

The agent’s “brain” is the LLM, which interprets user input, plans steps, and determines tool selection and arguments. Tools are external functions or APIs that the agent can call to interact with the real world or retrieve information. Evaluating an agent means assessing its overall task completion, but also its intermediate reasoning steps and critical tool usage.

Key Components to Evaluate

Component Description Evaluation Focus
LLM (Reasoning) The core intelligence for planning, interpreting, and generating responses. Logical coherence, absence of hallucinations, ability to follow instructions.
Tools External functions for data retrieval or actions. Correct tool selection, accurate argument parsing, effective tool execution.
Prompt Instructions, examples, and context guiding the LLM. Clarity, completeness, ability to elicit desired agent behavior.
Memory Stores conversational history or relevant context. Effective use of past interactions, avoidance of context window overflow.
Output Parser Formats the LLM’s raw output into structured actions or responses. Robustness to varied LLM outputs, correct action parsing.

The success of a LangChain agent heavily relies on its ability to correctly select and use the available tools. Evaluating this “tool usage evaluation” aspect is often the most challenging yet critical part of agent assessment. It involves not just checking the final answer, but analyzing the entire sequence of thoughts and actions.

Core Principles of LangChain Agent Evaluation

Effective LangChain agent evaluation follows several core principles. You need to define clear success criteria, create diverse evaluation tasks, and use a combination of automated and manual methods. The goal is to move beyond simple output checks to understand the agent’s internal reasoning process.

Defining Success Criteria

What does “success” mean for your agent? It’s not always a binary “yes” or “no.” Success criteria can include:

  • Task Completion: Did the agent successfully answer the question or complete the requested action?
  • Accuracy: Was the final answer factually correct and derived from reliable sources?
  • Tool Usage: Did the agent select the correct tools and provide valid arguments? Was tool usage efficient?
  • Efficiency: How quickly did the agent respond? How many tokens or API calls did it consume?
  • Safety: Did the agent avoid harmful, biased, or inappropriate responses?
  • Robustness: How well did the agent handle ambiguous inputs, edge cases, or unexpected scenarios?

Challenges in Agent Evaluation

Evaluating LLM agents comes with unique hurdles compared to traditional software testing. The non-deterministic nature of LLMs means you won’t always get the exact same output, even with the same input. Multi-step reasoning chains also introduce complexity, as a failure at any point can cascade into an incorrect final answer.

Ground truth generation, especially for complex, open-ended tasks, is often difficult and time-consuming. Subjectivity in task completion or quality of response further complicates automated scoring. This necessitates a thoughtful approach to setting up your evaluation framework.

Setting Up Your Evaluation Environment

To perform effective LangChain agent evaluation, you’ll need a robust environment. This typically involves Python, LangChain, and a dedicated platform like LangSmith for tracking and automated evaluation. You should also consider testing frameworks like pytest for unit-level checks.

Essential Dependencies

Ensure your environment has the necessary libraries installed. LangChain and its dependencies are fundamental, along with LangSmith for logging and evaluation.

1
2
3
pip install -U langchain langchain-community langsmith openai pydantic
# For local LLMs or specific integrations, install relevant packages
# e.g., pip install transformers accelerate torch

Reproducibility Configuration

For consistent evaluation, it’s crucial to configure your LLM to be as deterministic as possible. Set the temperature parameter to 0 and use a fixed seed if your model supports it. This helps reduce variability between runs, making it easier to compare agent versions.

Here’s an example of setting up an OpenAI model for reproducibility:

1
2
3
4
5
6
7
8
9
10
from langchain_openai import ChatOpenAI
import os

# Set your API key
os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "YOUR_LANGSMITH_API_KEY"
os.environ["LANGCHAIN_PROJECT"] = "Agent Evaluation Tutorial" # Optional

llm = ChatOpenAI(model="gpt-4o", temperature=0, seed=42)

LangSmith: Your Evaluation Command Center

LangSmith is an invaluable platform for LangChain Agent Evaluation. It provides tracing, debugging, and evaluation capabilities, allowing you to visualize agent execution paths, log inputs/outputs, and run automated evaluators. Activating LangSmith tracing is as simple as setting environment variables.

You can learn more about its utility in LangGraph RAG Evaluation Best Practices & LLM Testing Workflow.

Key Performance Metrics for LangChain Agent Evaluation

To properly evaluate a LangChain agent, you need a mix of quantitative and qualitative metrics. These metrics help you assess different aspects of an agent’s performance, from its final output to its internal reasoning.

Task Completion / Success Rate

This is often the most straightforward metric: Did the agent successfully accomplish the user’s goal? It can be a binary (pass/fail) score or a multi-level score (e.g., full success, partial success, failure).

  • How to measure: Define clear criteria for success for each task in your evaluation dataset. Manual review is common, but LLM-assisted evaluation can automate this (e.g., “Did the agent correctly answer the question: [question]?” -> “Yes/No”).

Correctness / Accuracy

Beyond just completing a task, was the information provided by the agent factually correct and accurate? This is especially critical for agents that retrieve or generate information.

  • How to measure: Compare the agent’s output against a ground truth answer or external knowledge sources. For subjective tasks, human judgment or LLM-as-a-judge evaluators are often used.

Tool Usage Evaluation

This is arguably the most important and complex aspect of LangChain Agent Evaluation. An agent’s intelligence is deeply tied to its ability to select and use tools effectively.

Correct Tool Selection

Did the agent choose the appropriate tool for the given sub-task? If it needed to search for current events, did it use a news search tool, or did it try to use a calculator?

  • How to measure:
    • Ground Truth Comparison: Define the expected tool calls for each evaluation task.
    • LLM-as-a-Judge: Ask an LLM evaluator if the tool selected was appropriate.

Correct Tool Arguments

Even if the agent selects the right tool, are the arguments it passes to that tool correct and complete? For example, if calling a get_stock_price tool, did it extract the correct stock ticker symbol from the query? This often involves Pydantic schemas for validation, as discussed in LangChain Custom Tools: OpenAI Function Calling & Pydantic.

  • How to measure:
    • Ground Truth Comparison: Check if the passed arguments match predefined correct arguments.
    • Schema Validation: Tools often have Pydantic schemas, which can automatically validate argument types and structure.
    • LLM-as-a-Judge: Ask an LLM evaluator to compare the extracted arguments with the original query.

Tool Efficiency / Redundancy

Did the agent use tools optimally, or did it make unnecessary or redundant calls? For instance, calling a search tool multiple times for the same information, or using a complex tool when a simpler one would suffice.

  • How to measure:
    • Step Count: Count the number of tool calls for a given task.
    • Redundancy Checks: Implement logic to detect repeated tool calls with identical arguments within a short span.
    • LLM-as-a-Judge: Ask an LLM to rate the efficiency of the tool usage sequence.

Efficiency Metrics

These metrics quantify the resource consumption and speed of your agent.

  • Latency: How long does it take for the agent to respond to a query? (e.g., time to first token, total response time).
  • Token Usage: How many input and output tokens did the agent consume? This directly impacts cost.
  • Cost: The actual monetary cost incurred per query or per task.

Robustness

How well does your agent handle variations in input, edge cases, or adversarial prompts? A robust agent maintains performance even under challenging conditions.

  • How to measure: Create a diverse set of test cases, including malformed inputs, ambiguous queries, and questions outside the agent’s capabilities. Observe failure modes.

Safety and Ethics

Does your agent avoid generating harmful, biased, or inappropriate content? This is crucial for public-facing applications.

  • How to measure: Use red-teaming techniques, adversarial prompts, and safety evaluators (e.g., content moderation APIs or specialized LLM evaluators) to detect undesirable outputs.

Designing Evaluation Tasks and Datasets

The quality of your evaluation heavily depends on the quality of your test cases and datasets. A well-designed dataset should cover the full range of scenarios your agent is expected to handle.

Synthetic vs. Real-world Data

  • Synthetic Data: Artificially generated prompts and expected outputs.
    • Pros: Easy to scale, can target specific edge cases, full control over diversity.
    • Cons: May not fully capture real-world user intent or linguistic nuances.
  • Real-world Data: Derived from actual user interactions or historical logs.
    • Pros: Represents actual user behavior, high ecological validity.
    • Cons: Harder to collect and anonymize, may lack coverage for rare scenarios, challenging to generate ground truth.

A balanced approach often involves starting with synthetic data for broad coverage and then enriching it with real-world data as your agent matures.

Categories of Evaluation Tasks

Your dataset should include a variety of tasks to thoroughly test your agent’s capabilities.

  • Simple Factual Recall: Agents retrieving information directly (e.g., from a RAG source).
  • Multi-step Reasoning: Tasks requiring several intermediate steps and tool calls.
  • Tool-Intensive Tasks: Queries specifically designed to test the agent’s ability to use multiple tools or a complex sequence of tool calls.
  • Problem-Solving Tasks: Open-ended questions requiring logical deduction and application of knowledge.
  • Edge Cases: Ambiguous queries, out-of-scope questions, malformed inputs, or questions requiring clarification.

Creating Test Cases (Ground Truth)

For each test case, you typically need:

  1. input: The user query.
  2. reference_output (Optional): The expected final answer.
  3. reference_steps (Optional): The expected sequence of tool calls and intermediate thoughts (ground truth for tool usage evaluation).

Example of a test case structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[
  {
    "input": "What's the current stock price of Google?",
    "reference_output": "The stock price of GOOGL is $175.50.",
    "reference_steps": [
      {"tool_name": "get_stock_price", "tool_args": {"ticker": "GOOGL"}}
    ]
  },
  {
    "input": "Tell me about the recent news for Apple.",
    "reference_output": "Recent news for AAPL includes ...",
    "reference_steps": [
      {"tool_name": "get_company_news", "tool_args": {"ticker": "AAPL"}}
    ]
  },
  {
    "input": "Compare the stock prices of Microsoft and Amazon today.",
    "reference_output": "MSFT is $X and AMZN is $Y.",
    "reference_steps": [
      {"tool_name": "get_stock_price", "tool_args": {"ticker": "MSFT"}},
      {"tool_name": "get_stock_price", "tool_args": {"ticker": "AMZN"}}
    ]
  }
]

Evaluation Methodologies and Tools

You can evaluate LangChain agents using a combination of manual review, automated testing frameworks, and specialized platforms like LangSmith. Each method offers distinct advantages.

Manual Evaluation

Human review is invaluable, especially in the early stages of agent development or for highly subjective tasks. It provides qualitative insights that automated metrics often miss.

  • Pros: High accuracy for subjective judgments, identifies novel failure modes, offers deep qualitative feedback.
  • Cons: Time-consuming, expensive, not scalable for large datasets, prone to human bias.
  • Best for: Initial sanity checks, complex reasoning path analysis, and understanding nuanced failures.

Automated Evaluation with LangSmith

LangSmith is designed to streamline the evaluation of LLM applications, including LangChain agents. It allows you to create datasets, run your agent against them, and apply various evaluators.

LangSmith Datasets and Runs

You start by creating a dataset in LangSmith, which is a collection of input and optionally reference_output pairs. You then “run” your agent against this dataset, and LangSmith logs every trace of the agent’s execution.

Custom Evaluators

LangSmith supports various evaluators, including custom LLM-assisted ones. You can define an LLM evaluator to judge specific aspects of your agent’s performance, such as:

  • Correctness: “Is the final answer factually correct?”
  • Tool Usage Appropriateness: “Was the selected tool appropriate for the given sub-task?”
  • Argument Accuracy: “Were the arguments passed to the tool accurate based on the input?”
  • Step Efficiency: “Was the agent’s sequence of actions efficient and logical?”

Here’s an example of creating a custom LLM evaluator for tool usage and answer correctness:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
from langsmith import Client
from langsmith.evaluation import run_evaluator, EvaluationResult
from langsmith import traceable
import functools

# Initialize LangSmith client
client = Client()

# Define a dummy agent for demonstration
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langchain import hub
from langchain.agents import create_react_agent, AgentExecutor

class GetStockPriceInput(BaseModel):
    ticker: str = Field(description="Ticker symbol of the company")

@tool("get_stock_price", args_schema=GetStockPriceInput, description="Get the current stock price for a given ticker symbol.")
def get_stock_price(ticker: str) -> str:
    """Gets the current stock price for a given ticker symbol."""
    print(f"Calling get_stock_price for {ticker}")
    if ticker.upper() == "GOOGL":
        return "$175.50"
    elif ticker.upper() == "AAPL":
        return "$190.25"
    elif ticker.upper() == "MSFT":
        return "$420.00"
    else:
        return "Stock price not found."

class GetCompanyNewsInput(BaseModel):
    ticker: str = Field(description="Ticker symbol of the company")

@tool("get_company_news", args_schema=GetCompanyNewsInput, description="Get recent news articles for a company by its ticker symbol.")
def get_company_news(ticker: str) -> str:
    """Gets recent news for a company."""
    print(f"Calling get_company_news for {ticker}")
    if ticker.upper() == "AAPL":
        return "Apple announces new Vision Pro features and record quarterly earnings."
    else:
        return "No recent news found."

tools = [get_stock_price, get_company_news]

# Get the prompt to use - you can modify this!
prompt = hub.pull("hwchase17/react")

# Set up LLM with temperature=0 for reproducibility
llm = ChatOpenAI(model="gpt-4o", temperature=0, seed=42)

# Construct the agent
agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)

# Define a custom LLM evaluator function
@run_evaluator
def agent_tool_and_answer_evaluator(run, example) -> EvaluationResult:
    predicted_output = run.outputs["output"]
    input_question = example.inputs["input"]
    reference_answer = example.outputs["reference_output"]
    reference_steps = example.outputs.get("reference_steps", []) # List of expected tool_name, tool_args

    # Check for specific tool calls in the run's steps (if reference_steps are provided)
    tool_usage_score = 0
    tool_usage_comment = ""
    if reference_steps:
        # Flatten tool calls from run
        agent_tool_calls = []
        # LangSmith run object provides access to child runs, which are tool calls or internal LLM calls
        for child_run in run.child_runs:
            if child_run.name in [tool.name for tool in tools] and child_run.run_type == "tool":
                agent_tool_calls.append({
                    "tool_name": child_run.name,
                    "tool_args": child_run.inputs
                })

        # Simple check: did it call the exact expected tools in order with the exact arguments?
        # This can be made more sophisticated
        if len(agent_tool_calls) == len(reference_steps):
            match = True
            for i in range(len(reference_steps)):
                ref_step = reference_steps[i]
                agent_step = agent_tool_calls[i]

                # Check tool name
                if ref_step["tool_name"] != agent_step["tool_name"]:
                    match = False
                    tool_usage_comment += f"Mismatch: Expected tool '{ref_step['tool_name']}', Got '{agent_step['tool_name']}'. "
                    break


<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-2784742237479601"
     crossorigin="anonymous"></script>
<ins class="adsbygoogle"
     style="display:block; text-align:center;"
     data-ad-layout="in-article"
     data-ad-format="fluid"
     data-ad-client="ca-pub-2784742237479601"
     data-ad-slot="7340313511"></ins>
<script>
     (adsbygoogle = window.adsbygoogle || []).push({});
</script>


                # Check tool arguments (case-insensitive for keys, flexible value matching)
                # This needs careful handling for args that might be dynamic or order-independent
                # For simplicity, we'll do a direct comparison here. In real-world, might need more robust parsing.
                if ref_step["tool_args"] != agent_step["tool_args"]:
                    match = False
                    tool_usage_comment += f"Mismatch: Expected args {ref_step['tool_args']}, Got args {agent_step['tool_args']} for tool '{ref_step['tool_name']}'. "
                    break

            if match:
                tool_usage_score = 1.0
                tool_usage_comment = "All expected tools used correctly."
            else:
                tool_usage_comment = tool_usage_comment.strip()
        else:
            tool_usage_comment = f"Number of tool calls mismatch. Expected {len(reference_steps)}, Got {len(agent_tool_calls)}."

    # Use LLM-as-a-judge for answer correctness
    llm_evaluator_prompt = """
    You are an AI assistant tasked with evaluating the correctness of an AI agent's response.
    Compare the AGENT'S RESPONSE to the REFERENCE ANSWER.
    The user's original INPUT was: {input_question}

    AGENT'S RESPONSE:
    {predicted_output}

    REFERENCE ANSWER:
    {reference_answer}

    Is the AGENT'S RESPONSE correct, partially correct, or incorrect relative to the REFERENCE ANSWER?
    Provide a score (1.0 for correct, 0.5 for partially correct, 0.0 for incorrect) and a brief justification.
    """

    eval_llm = ChatOpenAI(model="gpt-4o", temperature=0)
    eval_chain = llm_evaluator_prompt.format(
        input_question=input_question,
        predicted_output=predicted_output,
        reference_answer=reference_answer
    )

    # We'll just generate the LLM response and parse it simply.
    # In a real scenario, you'd use a Pydantic output parser for robustness.
    llm_eval_response = eval_llm.invoke(eval_chain).content

    # Simple parsing logic for the LLM's response
    answer_score = 0.0
    answer_comment = "Could not parse LLM evaluation."
    if "Score: 1.0" in llm_eval_response:
        answer_score = 1.0
        answer_comment = "Correct."
    elif "Score: 0.5" in llm_eval_response:
        answer_score = 0.5
        answer_comment = "Partially correct."
    elif "Score: 0.0" in llm_eval_response:
        answer_score = 0.0
        answer_comment = "Incorrect."

    return EvaluationResult(
        key="tool_usage_accuracy",
        score=tool_usage_score,
        comment=tool_usage_comment,

        # You can add multiple key-value pairs to the results
        # A bit hacky, but demonstrates adding multiple scores from one evaluator
        metrics={
            "answer_correctness": answer_score
        },
        feedback=answer_comment
    )

# Now, create a dataset in LangSmith and then run your agent with this evaluator.
# Example of creating a dataset (you'd do this once)
dataset_name = "Finance Assistant Evaluation"
if not client.list_examples(dataset_name=dataset_name):
    print(f"Creating dataset '{dataset_name}'...")
    dataset = client.create_dataset(
        dataset_name=dataset_name,
        description="Questions for a finance assistant agent, including stock prices and news."
    )

    # Add examples (you would load this from a JSON file or similar)
    examples_to_add = [
        {
            "input": "What's the current stock price of Google?",
            "reference_output": "The stock price of GOOGL is $175.50.",
            "reference_steps": [{"tool_name": "get_stock_price", "tool_args": {"ticker": "GOOGL"}}]
        },
        {
            "input": "Tell me about the recent news for Apple.",
            "reference_output": "Recent news for AAPL includes Apple announces new Vision Pro features and record quarterly earnings.",
            "reference_steps": [{"tool_name": "get_company_news", "tool_args": {"ticker": "AAPL"}}]
        },
        {
            "input": "What's the stock price for Microsoft?",
            "reference_output": "The stock price of MSFT is $420.00.",
            "reference_steps": [{"tool_name": "get_stock_price", "tool_args": {"ticker": "MSFT"}}]
        },
        {
            "input": "Is there any news on Tesla?",
            "reference_output": "No recent news found.",
            "reference_steps": [{"tool_name": "get_company_news", "tool_args": {"ticker": "TSLA"}}] # Assuming TSLA has no news
        },
        {
            "input": "Tell me about the current price of Amazon and its recent news.",
            "reference_output": "The stock price for AMZN is not found. No recent news found.",
            "reference_steps": [] # Complex query, agent might fail, or it's out of scope for simplicity
        }
    ]

    for ex in examples_to_add:
        client.create_example(
            dataset_id=dataset.id,
            inputs={"input": ex["input"]},
            outputs={"reference_output": ex["reference_output"], "reference_steps": ex.get("reference_steps", [])}
        )
    print("Dataset created and examples added.")
else:
    print(f"Dataset '{dataset_name}' already exists.")
    dataset = client.read_dataset(dataset_name=dataset_name)

# Run the agent against the dataset with the custom evaluator
print(f"Running evaluation for dataset '{dataset.name}'...")
# The traceable decorator can automatically log runs to LangSmith
# We need to explicitly call client.run_on_dataset for automated evaluation with specific evaluators
# Set experiment name to differentiate runs
experiment_name = f"Finance Agent Test - {llm.model_name}"

_ = client.run_on_dataset(
    dataset_name=dataset_name,
    llm_or_chain_factory=lambda: agent_executor, # The agent to evaluate
    evaluator=agent_tool_and_answer_evaluator, # Your custom evaluator
    # You can pass a list of evaluators
    # evaluators=[agent_tool_and_answer_evaluator, "agent_critique", "criteria"], # LangSmith built-in evaluators
    project_name=experiment_name,
    verbose=True
)

print(f"Evaluation run completed. Check LangSmith for results under project '{experiment_name}'.")

This example shows how you would define an evaluator and link it to a dataset and an agent run. The agent_tool_and_answer_evaluator assesses both tool usage based on provided reference_steps and overall answer correctness using an LLM-as-a-judge approach. You can find the results, including traces and evaluation scores, in the LangSmith UI.

Comparison Evaluators

LangSmith also allows you to compare different versions of your agent (e.g., A/B testing different prompts or models). This is powerful for tracking iterative improvements. You can run multiple agents against the same dataset and compare their scores side-by-side.

Unit and Integration Tests for Agents (Pytest)

While LangSmith handles end-to-end evaluation, pytest is excellent for focused unit and integration testing. You can test individual tools, specific agent decision points, or smaller agent chains.

  • Tool Unit Tests: Ensure your custom tools work correctly in isolation.
  • Agent Decision Tests: Mock tool outputs and verify the agent makes the correct next decision or final answer for specific inputs.
  • Prompt Robustness Tests: Test how the agent responds to slight variations in prompts or unexpected inputs.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import pytest
from unittest.mock import MagicMock
from langchain_core.agents import AgentFinish
from langchain_core.messages import AIMessage
from langchain_core.exceptions import OutputParserException
from langchain.agents import AgentExecutor
# Assuming 'agent' and 'tools' are defined as in the LangSmith example above
from your_agent_module import get_stock_price, get_company_news, create_react_agent, ChatOpenAI, prompt

# Recreate the agent_executor for local testing if not importing directly
llm_test = ChatOpenAI(model="gpt-4o", temperature=0, seed=42)
tools_test = [get_stock_price, get_company_news]
agent_test = create_react_agent(llm_test, tools_test, prompt)
agent_executor_test = AgentExecutor(agent=agent_test, tools=tools_test, verbose=False, handle_parsing_errors=True)

def test_get_stock_price_tool():
    """Test the get_stock_price tool directly."""
    assert get_stock_price("GOOGL") == "$175.50"
    assert get_stock_price("UNKNOWN") == "Stock price not found."

def test_get_company_news_tool():
    """Test the get_company_news tool directly."""
    assert "Vision Pro features" in get_company_news("AAPL")
    assert get_company_news("TSLA") == "No recent news found."

def test_agent_answers_stock_price_question():
    """Test if the agent correctly answers a stock price question using the tool."""
    response = agent_executor_test.invoke({"input": "What's the price of GOOGL?"})
    assert "175.50" in response["output"]

def test_agent_answers_news_question():
    """Test if the agent correctly answers a news question using the tool."""
    response = agent_executor_test.invoke({"input": "Tell me about the news for Apple."})
    assert "Vision Pro features" in response["output"]

def test_agent_handles_unknown_stock_query():
    """Test if the agent gracefully handles a query for an unknown stock."""
    response = agent_executor_test.invoke({"input": "What's the price of XYZ?"})
    assert "not found" in response["output"] or "I cannot find" in response["output"] # Agent might try to rephrase

# You can also mock tools for more specific control
def test_agent_with_mocked_tool():
    """Test agent behavior with a mocked tool output."""
    mock_get_stock_price = MagicMock(return_value="Mocked Price: $999.99")
    mock_get_stock_price.name = "get_stock_price" # Important for agent to recognize
    mock_get_stock_price.description = "Get the current stock price for a given ticker symbol."
    mock_get_stock_price.args_schema = GetStockPriceInput # Also important for type checking

    # Recreate agent_executor with the mock tool
    mock_tools = [mock_get_stock_price, get_company_news]
    mock_agent = create_react_agent(llm_test, mock_tools, prompt)
    mock_agent_executor = AgentExecutor(agent=mock_agent, tools=mock_tools, verbose=False, handle_parsing_errors=True)

    response = mock_agent_executor.invoke({"input": "What's the price of GOOGL?"})
    assert "999.99" in response["output"]
    mock_get_stock_price.assert_called_once_with(ticker="GOOGL")

LangGraph Specific Evaluation

If you’re using LangGraph to build more complex, stateful agents, evaluation needs to consider the graph’s structure. You can evaluate:

  • Node Execution: Did the correct nodes fire in the expected order?
  • State Transitions: Did the graph transition between states as intended based on conditions?
  • Supervisor Decisions: If using a supervisor node (LangGraph Supervisor Node for Multi-Agent Orchestration), did it correctly route control?

LangSmith is excellent for visualizing LangGraph traces, showing the flow through nodes and state changes, making debugging and evaluation of complex agent orchestrations much easier. You can use similar custom evaluators to check for specific node visits or state values. This builds on concepts like conditional edges, explored in LangGraph StateGraph: Conditional Edges.

Step-by-Step Practical Example: Evaluating a Finance Assistant Agent

Let’s walk through a complete example to solidify your understanding of LangChain Agent Evaluation. We’ll evaluate a simple finance assistant that uses tools to fetch stock prices and company news.

1. Define the Agent and Tools

First, define your tools and assemble your agent. This is similar to the setup in the LangSmith evaluation example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import os
from langchain_openai import ChatOpenAI
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.tools import tool
from langchain import hub
from langchain.agents import create_react_agent, AgentExecutor
from dotenv import load_dotenv

load_dotenv() # Load environment variables from .env file

# Ensure API keys are set
# os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY")
# os.environ["LANGCHAIN_TRACING_V2"] = "true"
# os.environ["LANGCHAIN_API_KEY"] = os.getenv("LANGSMITH_API_KEY")
# os.environ["LANGCHAIN_PROJECT"] = "Finance Agent Evaluation Demo" # Optional

# --- Define Tools ---
class GetStockPriceInput(BaseModel):
    ticker: str = Field(description="Ticker symbol of the company")

@tool("get_stock_price", args_schema=GetStockPriceInput, description="Get the current stock price for a given ticker symbol.")
def get_stock_price(ticker: str) -> str:
    """Gets the current stock price for a given ticker symbol."""
    print(f"[TOOL CALL] get_stock_price for {ticker}")
    if ticker.upper() == "GOOGL":
        return "$175.50"
    elif ticker.upper() == "AAPL":
        return "$190.25"
    elif ticker.upper() == "MSFT":
        return "$420.00"
    elif ticker.upper() == "AMZN":
        return "$180.10"
    else:
        return "Stock price not found."

class GetCompanyNewsInput(BaseModel):
    ticker: str = Field(description="Ticker symbol of the company")

@tool("get_company_news", args_schema=GetCompanyNewsInput, description="Get recent news articles for a company by its ticker symbol.")
def get_company_news(ticker: str) -> str:
    """Gets recent news for a company."""
    print(f"[TOOL CALL] get_company_news for {ticker}")
    if ticker.upper() == "AAPL":
        return "Apple announces new Vision Pro features and record quarterly earnings."
    elif ticker.upper() == "GOOGL":
        return "Google shares rise on strong Q2 earnings."
    else:
        return "No recent news found for this company."

tools = [get_stock_price, get_company_news]

# --- Define LLM and Agent ---
llm = ChatOpenAI(model="gpt-4o", temperature=0, seed=42)
prompt = hub.pull("hwchase17/react") # Using the standard ReAct prompt

agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)

print("Agent and tools initialized.")

2. Prepare Evaluation Dataset

Create a diverse set of test cases, including inputs and their corresponding ground truth reference_output and reference_steps. Store this in a JSON file or directly in a Python list.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
[
  {
    "input": "What is the current stock price of Google?",
    "reference_output": "The stock price of GOOGL is $175.50.",
    "reference_steps": [{"tool_name": "get_stock_price", "tool_args": {"ticker": "GOOGL"}}]
  },
  {
    "input": "Tell me about the recent news for Apple.",
    "reference_output": "Recent news for AAPL includes Apple announces new Vision Pro features and record quarterly earnings.",
    "reference_steps": [{"tool_name": "get_company_news", "tool_args": {"ticker": "AAPL"}}]
  },
  {
    "input": "What's the price of Microsoft and Amazon?",
    "reference_output": "The stock price of MSFT is $420.00 and AMZN is $180.10.",
    "reference_steps": [
      {"tool_name": "get_stock_price", "tool_args": {"ticker": "MSFT"}},
      {"tool_name": "get_stock_price", "tool_args": {"ticker": "AMZN"}}
    ]
  },
  {
    "input": "Is there any news on Tesla?",
    "reference_output": "No recent news found for this company.",
    "reference_steps": [{"tool_name": "get_company_news", "tool_args": {"ticker": "TSLA"}}]
  },
  {
    "input": "What is the news for Google and what is its stock price?",
    "reference_output": "Google shares rise on strong Q2 earnings. The stock price of GOOGL is $175.50.",
    "reference_steps": [
      {"tool_name": "get_company_news", "tool_args": {"ticker": "GOOGL"}},
      {"tool_name": "get_stock_price", "tool_args": {"ticker": "GOOGL"}}
    ]
  },
  {
    "input": "What's the weather like?",
    "reference_output": "I cannot answer questions about the weather, as I am a finance assistant. My tools are for financial information.",
    "reference_steps": []
  },
  {
    "input": "Who is the CEO of Apple?",
    "reference_output": "I do not have access to information about company executives. My tools are for stock prices and company news.",
    "reference_steps": []
  }
]

3. Integrate with LangSmith and Run Evaluation

Use the client.run_on_dataset function with your agent and the custom evaluator from the previous section.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import json
from langsmith import Client
from langsmith.evaluation import run_evaluator, EvaluationResult
from langsmith import traceable
import functools

# Initialize LangSmith client (ensure LANGCHAIN_API_KEY and LANGCHAIN_TRACING_V2 are set)
client = Client()

# Your agent and tools definition would go here (same as step 1)
# For brevity, let's assume 'agent_executor', 'tools', 'get_stock_price', 'get_company_news'
# are available from the previous code block.

# Custom LLM Evaluator (same as in section 9.2)
@run_evaluator
def agent_tool_and_answer_evaluator(run, example) -> EvaluationResult:
    predicted_output = run.outputs["output"]
    input_question = example.inputs["input"]
    reference_answer = example.outputs["reference_output"]
    reference_steps = example.outputs.get("reference_steps", []) # List of expected tool_name, tool_args

    tool_usage_score = 0
    tool_usage_comment = ""

    # Check for specific tool calls in the run's steps (if reference_steps are provided)
    if reference_steps:
        agent_tool_calls = []
        for child_run in run.child_runs:
            if child_run.name in [tool.name for tool in tools] and child_run.run_type == "tool":
                agent_tool_calls.append({
                    "tool_name": child_run.name,
                    "tool_args": child_run.inputs
                })

        match = True
        if len(agent_tool_calls) != len(reference_steps):
            match = False
            tool_usage_comment += f"Number of tool calls mismatch. Expected {len(reference_steps)}, Got {len(agent_tool_calls)}. "
        else:
            for i in range(len(reference_steps)):
                ref_step = reference_steps[i]
                agent_step = agent_tool_calls[i]

                if ref_step["tool_name"] != agent_step["tool_name"]:
                    match = False
                    tool_usage_comment += f"Mismatch: Expected tool '{ref_step['tool_name']}', Got '{agent_step['tool_name']}'. "
                    break

                # Compare arguments (simple dict equality for demonstration)
                # For real cases, ensure keys and values are consistent, might need normalization
                if ref_step["tool_args"] != agent_step["tool_args"]:
                    match = False
                    tool_usage_comment += f"Mismatch: Expected args {ref_step['tool_args']}, Got args {agent_step['tool_args']} for tool '{ref_step['tool_name']}'. "
                    break

        if match:
            tool_usage_score = 1.0
            tool_usage_comment = "All expected tools used correctly."
        else:
            tool_usage_comment = tool_usage_comment.strip()

    # LLM-as-a-judge for answer correctness
    llm_evaluator_prompt = """
    You are an AI assistant tasked with evaluating the correctness of an AI agent's response.
    Compare the AGENT'S RESPONSE to the REFERENCE ANSWER.
    The user's original INPUT was: {input_question}

    AGENT'S RESPONSE:
    {predicted_output}

    REFERENCE ANSWER:
    {reference_answer}

    Is the AGENT'S RESPONSE correct, partially correct, or incorrect relative to the REFERENCE ANSWER?
    Provide a score (1.0 for correct, 0.5 for partially correct, 0.0 for incorrect) and a brief justification.
    """

    eval_llm = ChatOpenAI(model="gpt-4o", temperature=0)
    llm_eval_query = llm_evaluator_prompt.format(
        input_question=input_question,
        predicted_output=predicted_output,
        reference_answer=reference_answer
    )

    llm_eval_response = eval_llm.invoke(llm_eval_query).content

    answer_score = 0.0
    answer_comment = "Could not parse LLM evaluation."
    if "Score: 1.0" in llm_eval_response:
        answer_score = 1.0
        answer_comment = "Correct."
    elif "Score: 0.5" in llm_eval_response:
        answer_score = 0.5
        answer_comment = "Partially correct."
    elif "Score: 0.0" in llm_eval_response:
        answer_score = 0.0
        answer_comment = "Incorrect."

    return EvaluationResult(
        key="overall_evaluation", # Main key
        score=(tool_usage_score + answer_score) / 2 if reference_steps else answer_score, # Combine scores if applicable
        comment=tool_usage_comment + " | " + answer_comment,
        metrics={
            "tool_usage_accuracy": tool_usage_score,
            "answer_correctness": answer_score
        },
        feedback=answer_comment
    )

# --- Load Dataset ---
dataset_file = "finance_assistant_evaluation_data.json"
with open(dataset_file, 'r') as f:
    eval_examples = json.load(f)

# Create/Update dataset in LangSmith
dataset_name = "Finance Assistant Evaluation Dataset v1"
if not client.list_examples(dataset_name=dataset_name):
    print(f"Creating dataset '{dataset_name}'...")
    dataset = client.create_dataset(
        dataset_name=dataset_name,
        description="Evaluation data for a finance assistant agent, covering stock prices, news, and out-of-scope queries."
    )
    for ex in eval_examples:
        client.create_example(
            dataset_id=dataset.id,
            inputs={"input": ex["input"]},
            outputs={"reference_output": ex["reference_output"], "reference_steps": ex.get("reference_steps", [])}
        )
    print("Dataset created and examples added.")
else:
    print(f"Dataset '{dataset_name}' already exists. Updating examples if necessary.")
    dataset = client.read_dataset(dataset_name=dataset_name)
    # You might want to update examples if the file changes, or clean and recreate.
    # For this demo, we'll assume it's up-to-date or handle adding new ones.
    existing_inputs = {ex.inputs["input"] for ex in client.list_examples(dataset_id=dataset.id)}
    for ex in eval_examples:
        if ex["input"] not in existing_inputs:
            client.create_example(
                dataset_id=dataset.id,
                inputs={"input": ex["input"]},
                outputs={"reference_output": ex["reference_output"], "reference_steps": ex.get("reference_steps", [])}
            )
            print(f"Added new example: {ex['input']}")

# --- Run Evaluation ---
experiment_name = f"Finance Agent Test - GPT4o ({llm.model_name})"
print(f"Starting evaluation run for experiment '{experiment_name}'...")

# The `project_name` here will be the name of your experiment in LangSmith
# You can also pass multiple evaluators as a list
run_results = client.run_on_dataset(
    dataset_name=dataset_name,
    llm_or_chain_factory=lambda: agent_executor, # Your agent instance
    evaluator=[agent_tool_and_answer_evaluator, "agent_critique", "criteria"], # Custom + built-in evaluators
    project_name=experiment_name,
    concurrency_level=5, # Number of concurrent runs
    verbose=True
)

print(f"Evaluation completed. Check LangSmith at: {run_results.url}")

After running this script, you’ll get a URL to your LangSmith project. Navigate there to see a detailed breakdown of each run, including:

  • Traces: Visualizations of the agent’s thought process, tool calls, and LLM interactions.
  • Evaluation Scores: The scores from your agent_tool_and_answer_evaluator (tool usage, answer correctness) and other built-in evaluators.
  • Feedback: Comments and justifications from the LLM-as-a-judge.

4. Analyze Results and Debug

In LangSmith, click on individual runs to see their full trace. If an agent fails, you can:

  • Examine the “Thought” steps: Did the LLM reason correctly?
  • Check tool calls: Was the correct tool selected? Were the arguments valid? Did the tool return an expected result?
  • Identify prompt issues: Is the agent misinterpreting instructions?

For instance, if the agent fails to get news for Tesla, checking the trace might reveal it tried get_company_news(ticker='TSLA'), which is correct tool usage, but the tool itself returned “No recent news found.” This indicates the tool’s data, not the agent’s reasoning, is the limiting factor. Conversely, if it tries get_stock_price('TESLA') instead of get_stock_price('TSLA'), that’s an agent reasoning error (incorrect argument parsing).

Iteratively refine your agent by:

  • Prompt Engineering: Adjust the agent’s system prompt or few-shot examples.
  • Tool Enhancements: Improve tool descriptions, arguments, or the tool’s underlying logic.
  • Model Selection: Experiment with different LLMs.
  • Error Handling: Add explicit error handling within your agent or tools.

This iterative process of evaluating, debugging, and improving is fundamental to mastering LangChain Agent Evaluation. It helps mitigate issues like hallucination, which can be further addressed with techniques like Self-RAG, as explored in Self-RAG LangGraph: Hallucination Detection & Grader.

Best Practices for LangChain Agent Evaluation

Adopting a structured approach to evaluation will save you significant time and effort in the long run.

  • Start Early and Iterate Often: Integrate evaluation into your development workflow from day one. Don’t wait until the agent is “finished.”
  • Define Clear Success Criteria: Before you start testing, know exactly what constitutes a successful outcome for each task.
  • Automate as Much as Possible: Leverage tools like LangSmith to automate the running of tests and scoring, freeing up human reviewers for qualitative analysis.
  • Diversify Your Evaluation Dataset: Include a wide range of inputs: common queries, edge cases, out-of-scope questions, adversarial prompts, and prompts that test specific tool interactions.
  • Evaluate More Than Just the Final Answer: Analyze the agent’s entire reasoning process, including intermediate thoughts and tool calls. LangSmith traces are invaluable here.
  • Version Control Your Agents and Datasets: Keep track of changes to your agent’s code, prompts, and evaluation datasets. This enables reproducible evaluations and comparison over time.
  • Combine Quantitative and Qualitative Metrics: Use automated metrics for scalability and efficiency, but always complement them with human review for nuanced understanding.
  • Establish Baselines: When making changes, always compare the new agent’s performance against a previous version or a simple baseline to ensure real improvement.
  • Consider Cost of Evaluation: Running LLM-as-a-judge evaluators can be costly. Be mindful of the model used for evaluation and the frequency of runs.

Conclusion

Mastering LangChain Agent Evaluation is not merely about identifying bugs; it’s about building robust, reliable, and trustworthy AI systems. By meticulously designing evaluation tasks, defining clear metrics, and leveraging powerful tools like LangSmith, you can systematically improve your agents’ performance. From assessing accurate tool usage to ensuring correct task completion, a thorough evaluation framework is your compass in the complex world of LLM agent development.

Embrace this iterative process of evaluation, debugging, and refinement, and you’ll be well-equipped to deploy agents that consistently deliver value and exceed user expectations. Your journey to building reliable AI agents now has a clear path forward.

Frequently Asked Questions (FAQs)

Q1: What is LangChain Agent Evaluation?

LangChain Agent Evaluation is the process of systematically assessing the performance, reliability, and correctness of AI agents built with the LangChain framework. It involves defining metrics, creating test cases, running agents against these tests, and analyzing their outputs and internal reasoning to identify areas for improvement.

Q2: Why is evaluating LLM agents more difficult than traditional software?

LLM agents are non-deterministic, meaning they don’t always produce the exact same output for the same input, making reproducibility challenging. Their multi-step reasoning processes and reliance on external tools also introduce complexity, requiring evaluation of not just the final answer but also intermediate steps and tool usage. Ground truth for open-ended tasks can also be difficult to define.

Q3: What are the most important metrics for LangChain Agent Evaluation?

Key metrics include:

  • Task Completion/Success Rate: Did the agent achieve its goal?
  • Correctness/Accuracy: Was the information provided accurate?
  • Tool Usage Evaluation: Did the agent select the right tools with correct arguments and use them efficiently?
  • Efficiency: Latency, token usage, and cost.
  • Robustness: How well it handles edge cases and varied inputs.
  • Safety: Avoidance of harmful or biased outputs.

Q4: How does LangSmith help with LangChain Agent Evaluation?

LangSmith is a powerful platform for tracing, debugging, and evaluating LLM applications, including LangChain agents. It allows you to:

  • Visualize agent execution paths (traces).
  • Create datasets of evaluation tasks.
  • Run your agent against these datasets.
  • Apply custom LLM-as-a-judge evaluators to score performance.
  • Track and compare different agent versions over time.

Q5: Can I evaluate the agent’s tool usage specifically?

Yes, evaluating tool usage is critical. You can assess:

  1. Correct tool selection: Did the agent choose the appropriate tool?
  2. Correct tool arguments: Were the arguments passed to the tool accurate based on the query?
  3. Tool efficiency: Did the agent use tools optimally without redundancy? LangSmith’s tracing capabilities allow you to see each tool call, and custom evaluators can be configured to specifically score these aspects against a defined ground truth.

Q6: What’s the difference between synthetic and real-world evaluation data?

Synthetic data is artificially generated and controlled, allowing you to easily scale and target specific scenarios or edge cases. However, it may not fully reflect real-world user behavior. Real-world data comes from actual user interactions, offering high ecological validity but can be harder to collect, anonymize, and establish ground truth for. A balanced approach often uses both.

Q7: How can I debug a failing LangChain agent?

Debugging starts with examining the agent’s trace in LangSmith. Look for:

  • Incorrect “Thought” steps: Is the LLM’s reasoning logical?
  • Wrong tool selection: Did it pick the appropriate tool for the sub-task?
  • Invalid tool arguments: Are the arguments passed to the tool correct according to its schema?
  • Tool execution errors: Did the tool itself return an error or unexpected result?
  • Prompt issues: Is the prompt clear enough, or does it lead to misinterpretations? Based on the analysis, you can refine your prompt, improve tool descriptions, or adjust your agent’s configuration.

Leave a comment