Evaluate an agent against a dataset with LangSmith's evaluate and aevaluate: tool selection, skill routing, delegation, and guardrails, either locally or in the LangSmith UI.

Everything on this page needs harnessx[langsmith]. Without it, harnessx.evals raises on import. A local run still calls the agent, so it costs model and tool spend; uv run python examples/07-quality/evaluating_with_datasets.py --offline needs no API key.

Enable tracing

Set LANGSMITH_API_KEY and add LangSmithExtension(project_name=...) to the agent. Without project_name it uploads to the LANGSMITH_PROJECT project, or to harnessx-agents when that is unset too. See Extensions & artifacts for what each span carries.

Tracing transmits prompts and tool payloads to LangSmith, so enable it only for data you intend to send. Trace delivery is best effort and is not an execution guard. Use the flight recorder when you need captured runtime boundaries for offline inspection.

Running an evaluation

import operator

from harnessx import Agent, AgentConfig, PermissionLevel
from harnessx.evals import build_example, contains_evaluator, evaluate_agent, tool_selection_evaluator

OPERATORS = {"+": operator.add, "-": operator.sub, "*": operator.mul, "/": operator.truediv}

def calculate(a: float, op: str, b: float) -> str:
    """Apply one arithmetic operator to two numbers.

    Args:
        a: The left number.
        op: One of + - * /.
        b: The right number.
    """
    return str(OPERATORS[op](a, b))

def make_agent(inputs):
    agent = Agent(config=AgentConfig(model="claude-sonnet-4-6"))
    agent.tools.register_tool(calculate, permission=PermissionLevel.ALLOW, replay_policy="safe")
    return agent

dataset = [
    build_example(
        inputs={"prompt": "What is 144 / 12?"},
        outputs={"expected_tools": ["calculate"], "contains_all": ["12"], "max_allowed_iterations": 3},
    )
]

summary = evaluate_agent(
    agent=make_agent,
    dataset=dataset,
    evaluators=[tool_selection_evaluator, contains_evaluator],
    experiment_prefix="math-agent-benchmark",
    offline=True,   # Disables LangSmith upload; the agent still calls its model.
)
print(f"Pass rate: {summary.pass_rate * 100:.1f}%")

Pass a built-in suite name ("tool_calling", "skills", "multi_agent", "guardrails", "memory", or "all") instead of a dataset to use the shipped examples. Experiments default to the prefix harnessx-eval; built-in datasets synced to LangSmith are prefixed harnessx-. default_evaluators() returns the standard evaluator set.

Evaluate from a running event loop

evaluate_agent_async(...) awaits the same evaluation from async code. It runs the synchronous runner in a worker thread, so pass a factory rather than an Agent bound to your loop. It cannot be cancelled once started and must not be nested inside another evaluation.

from harnessx.evals import evaluate_agent_async

async def benchmark():
    summary = await evaluate_agent_async(
        agent=make_agent,      # a factory, not an Agent bound to this loop
        dataset="tool_calling",
        offline=True,
    )
    return summary.pass_rate

Running evals from the CLI

# Scripted integration demonstration (langsmith extra, no API keys required)
uv run python examples/07-quality/evaluating_with_datasets.py --offline

# Run live model benchmarks without upload (model API key required)
python -m harnessx.evals.cli --suite tool_calling --offline

# Run against Anthropic and upload results + traces to LangSmith
export LANGSMITH_API_KEY="lsv2_pt_..."
python -m harnessx.evals.cli --suite skills --model claude-sonnet-4-6 --upload

# Run all benchmark suites with concurrency
python -m harnessx.evals.cli --suite all --concurrency 2

When uploaded, the CLI prints a clickable LangSmith URL to inspect row-level scores, side-by-side prompt diffs, and the complete nested execution tree for every turn.