Data as of Sep 9, 2026 · Based on 361 AI responses from ChatGPT Search and Google AI Mode · See how Parse measures this
For building and evaluating complex agent workflows, use LangGraph (LangChain) as the primary choice for stateful, graph-modeled orchestration. Add
Langfuse or
LangSmith for detailed step-by-step tracing and debugging. Choose AutoGen when you need multi-agent conversational patterns, and StackAI (Gumloop) if you prefer a visual no-code/low-code builder. Use Pydantic validation and human-in-the-loop controls where reliability and structured tool calls matter.
Brands AI recommends here
Named in 57% of answers
Best when you need graph-modeled, stateful orchestration for complex loops and tool calls; widely recommended as the industry choice for building and evaluating agent workflows, but pair it with observability tools for tracing.
Named in 48% of answers
Named in 39% of answers
Named in 35% of answers
A useful way to think about complex LLM agent workflows is to treat them less like “prompts” and more like **distributed programs with probabilistic components**. You need a framework that handles: 1. **Workflow orchestration** (loops, branching, retries, state) 2. **Tool execution** (APIs, databases, code, search,…
A useful way to think about complex LLM agent workflows is to treat them less like “prompts” and more like distributed programs with probabilistic components. You need a framework that handles:
Frameworks such as LangGraph explicitly model agents as stateful graphs with nodes, edges, tool execution, and iterative loops, which is a good fit for complex workflows.
A production-grade agent system usually looks like this:
┌───────────────┐
│ User / Trigger │
└───────┬───────┘
│
▼
┌────────────────┐
│ Orchestrator │
│ (State Machine)│
└───────┬────────┘
│
┌───────────────┼────────────────┐
▼ ▼ ▼
┌──────────┐ ┌───────────┐ ┌──────────┐
│ Planner │ │ Executor │ │ Critic │
│ Agent │ │ Agent │ │ Agent │
└────┬─────┘ └─────┬─────┘ └────┬─────┘
│ │ │
▼ ▼ ▼
┌────────────────────────────────────────┐
│ Tool Layer │
│ APIs | DB | Search | Code | Files │
└────────────────────────────────────────┘
│
▼
┌─────────────────┐
│ Evaluation Layer│
│ Quality + Cost │
│ + Safety + Logs │
└─────────────────┘
Avoid hidden loops inside prompts.
Define:
State = {
"goal": "...",
"messages": [],
"plan": [],
"tool_results": [],
"artifacts": [],
"errors": [],
"iteration": 0
}
Examples:
plannerresearchertool_executorvalidatorsummarizerhuman_reviewExamples:
planner
|
v
needs_tools?
|
yes --------> tool_executor
| |
| v
└---------- validator
|
pass/fail
|
┌─────────┴─────────┐
v v
finish retry
This makes loops measurable:
A common failure mode is letting the LLM control everything.
Use:
Example:
User request
|
v
LLM planner
|
v
Python validator <--- deterministic
|
v
Tool calls
|
v
LLM reviewer
A robust loop:
while not done:
observe(state)
decide next action
if action == tool:
execute_tool()
update_state()
elif action == answer:
generate_output()
elif action == retry:
modify_strategy()
enforce_limits()
Always add:
Example:
MAX_STEPS = 20
for step in range(MAX_STEPS):
result = agent.run(state)
if result.complete:
break
state.update(result)
Treat tools as APIs, not magic functions.
Good tool:
{
"name": "search_customer",
"description": "Find customer records",
"input_schema": {
"customer_id": "string"
},
"returns": {
"customer": "object"
}
}
Bad tool:
do_everything(input)
Measure:
Evaluate at multiple layers.
Test individual pieces.
| Component | Metric |
|---|---|
| Planner | plan correctness |
| Tool selector | correct tool chosen |
| Retriever | relevant documents |
| Critic | catches errors |
| Generator | final answer quality |
Store every execution:
{
"run_id": "123",
"steps": [
{
"agent": "planner",
"input": "...",
"output": "...",
"tools": []
},
{
"agent": "researcher",
"tool": "search",
"latency": 800
}
]
}
Evaluate:
Score the final result:
Example rubric:
| Criterion | Score |
|---|---|
| Correctness | 0-5 |
| Completeness | 0-5 |
| Tool usage | 0-5 |
| Efficiency | 0-5 |
| Safety | 0-5 |
Create an agent test suite:
tests/
├── customer_support/
│ ├── refund.json
│ ├── escalation.json
│
├── research/
│ ├── multi_source.json
│
└── coding/
├── bug_fix.json
Each test contains:
{
"input": "...",
"expected_tools": [
"database_lookup"
],
"success_condition": {
"contains": "refund approved"
}
}
Run it on every:
A practical mapping:
| Need | Good fit |
|---|---|
| Simple tool-using assistant | OpenAI Agents SDK |
| Complex loops + state graphs | LangGraph |
| Role-based multi-agent teams | CrewAI |
| Document-heavy workflows | LlamaIndex Workflows |
| Enterprise durable workflows | Temporal + agent layer |
Current agent frameworks differ mainly in how much control they give over orchestration. Graph-based systems such as LangGraph emphasize explicit state and transitions, while lighter SDKs emphasize simpler agent loops and delegation.
Add:
Add:
A strong default stack for complex agent workflows is:
The key design principle is: make the agent flexible, but make the system around it deterministic and observable.
{
"run_id": "123",
"steps": [
{
"agent": "planner",
"input": "...",
"output": "...",
"tools": []
},
{
"agent": "researcher",
"tool": "search",
"latency": 800
}
]
}
Evaluate:
Score the final result:
Example rubric:
| Criterion | Score |
|---|---|
| Correctness | 0-5 |
| Completeness | 0-5 |
| Tool usage | 0-5 |
| Efficiency | 0-5 |
| Safety | 0-5 |
Create an agent test suite:
tests/
├── customer_support/
│ ├── refund.json
│ ├── escalation.json
│
├── research/
│ ├── multi_source.json
│
└── coding/
├── bug_fix.json
Each test contains:
{
"input": "...",
"expected_tools": [
"database_lookup"
],
"success_condition": {
"contains": "refund approved"
}
}
Run it on every:
A practical mapping:
| Need | Good fit |
|---|---|
| Simple tool-using assistant | OpenAI Agents SDK |
| Complex loops + state graphs | LangGraph |
| Role-based multi-agent teams | CrewAI |
| Document-heavy workflows | LlamaIndex Workflows |
| Enterprise durable workflows | Temporal + agent layer |
Current agent frameworks differ mainly in how much control they give over orchestration. Graph-based systems such as LangGraph emphasize explicit state and transitions, while lighter SDKs emphasize simpler agent loops and delegation.
Add:
**Building and evaluating complex LLM agent workflows with loops and tool usage requires a modular architecture centered on explicit state management, deterministic control flow for cyclic graphs, and trace-based multi-turn evaluation** **.** [](https://www.langchain.com/resources/ai-agent-frameworks)…
Building and evaluating complex LLM agent workflows with loops and tool usage requires a modular architecture centered on explicit state management, deterministic control flow for cyclic graphs, and trace-based multi-turn evaluation .
To handle loops and tool execution safely, you should separate state management, reasoning, and tool execution into distinct, observable layers.
max_iterations = 10 ) to prevent infinite loops and runaway API costs.Evaluating cyclic agent workflows requires moving beyond single-turn input/output testing to trace-level, multi-turn diagnostics.
| Evaluation Dimension | Core Metric / Focus | Recommended Approach |
|---|---|---|
| Tool Selection Accuracy | Did the agent call the correct tool with valid parameters? | Use programmatic assertions or an LLM-as-a-judge to score expected vs. actual tool arguments. |
| Path / Trajectory Efficiency | Did the agent take an optimal loop path or wander aimlessly? | Count total steps, redundant tool calls, and evaluate graph transition traces. |
| Task Completion & Faithfulness | Did the final output correctly answer the initial prompt without hallucinating? | Use frameworks like DeepEval or LangSmith to run unit tests on final states. |
| Loop Termination Integrity | Did the agent recognize when it was "done" instead of looping indefinitely? | Test edge cases where tasks are impossible to ensure the agent exits cleanly rather than timing out. |
If you'd like to narrow this down, let me know:
I can provide a concrete code template or test harness for your specific stack.
For complex LLM agents, I’d avoid treating evaluation as “did the final answer look good?” The right abstraction is **trajectory-level evaluation**: evaluate the outcome, the sequence of decisions, tool interactions, state transitions, and resource usage. Current agent-evaluation practice increasingly follows this…
For complex LLM agents, I’d avoid treating evaluation as “did the final answer look good?” The right abstraction is trajectory-level evaluation: evaluate the outcome, the sequence of decisions, tool interactions, state transitions, and resource usage.
Current agent-evaluation practice increasingly follows this approach: traces capture the full trajectory, while evaluators score both intermediate behavior and final outcomes.
Think of an agent run as:
Input
↓
┌──────────────────────────────────────────┐
│ Agent loop │
│ │
│ Observe state │
│ ↓ │
│ Decide / plan │
│ ↓ │
│ Select tool ──→ Execute tool │
│ ↑ │ │
│ └────── observe ──┘ │
│ │
│ repeat until termination │
└──────────────────────────────────────────┘
↓
Final output
Represent every run as a trace:
Trace = {
"input": ...,
"steps": [
{
"state_before": ...,
"model_output": ...,
"decision": ...,
"tool": ...,
"arguments": ...,
"tool_result": ...,
"state_after": ...,
"latency_ms": ...,
"tokens": ...,
},
...
],
"final_output": ...,
"success": ...,
}
The important design choice is: make the trace a first-class artifact. This lets you replay, compare, score, and debug individual steps rather than only looking at the final response.
For every tool invocation:
Example:
Tool selection 1.0
Argument validity 1.0
Argument grounding 0.8
Necessity 0.0 ← unnecessary call
This catches problems that final-answer evaluation misses.
Evaluate whether each state transition made sense:
state_t
↓
decision_t
↓
action_t
↓
observation_t
↓
state_t+1
Questions:
This is especially important for workflows with branching and loops.
Score the whole path, not individual calls.
Useful metrics:
| Metric | What it catches |
|---|---|
| Task success | Did the agent accomplish the objective? |
| Step count | Excessive reasoning/action |
| Tool-call count | Wasteful tool use |
| Repeated-state rate | Loops |
| Repeated-tool rate | Tool thrashing |
| Invalid-call rate | Poor tool selection |
| Recovery rate | Ability to handle failures |
| Path efficiency | Correct result with minimal unnecessary work |
| Constraint violations | Unsafe/forbidden behavior |
A trajectory can therefore be:
Correct + efficient → excellent
Correct + inefficient → acceptable but needs optimization
Incorrect + efficient → reasoning/tool problem
Incorrect + looping → major reliability problem
This distinction is critical: a correct final answer can conceal a broken trajectory.
Finally evaluate:
Use deterministic assertions whenever possible and LLM-as-judge only for qualities that genuinely require semantic judgment.
I recommend this hierarchy:
Agent Evaluation
│
┌────────────┴────────────┐
↓ ↓
Deterministic Semantic
evals evals
│ │
┌─────┼─────┐ LLM judge
↓ ↓ ↓ Human review
tools loops schema
Prefer code for things such as:
assert result.status == "success"
assert "customer_id" in tool_args
assert trace.tool_calls <= 8
assert trace.repeated_states < 3
assert forbidden_tool not in trace.tools
assert output_schema_is_valid(result)
These are cheap, reproducible, and difficult for an evaluator model to hallucinate.
Use an evaluator model for questions such as:
Did the agent choose a reasonable strategy given the information available at each step? or:
Did the final answer adequately address the user's request? Give the judge the relevant trace, not just the final answer.
Loops deserve their own evaluation subsystem.
For each run track:
loop_metrics = {
"iterations": n,
"unique_states": ...,
"repeated_states": ...,
"repeated_tools": ...,
"progress_events": ...,
"stagnant_iterations": ...,
}
Then define termination invariants:
MAX_ITERATIONS
MAX_COST
MAX_TOOL_CALLS
MAX_SAME_TOOL_REPETITIONS
MAX_SAME_STATE_REPETITIONS
MAX_WALL_CLOCK_TIME
But don't only test whether the loop eventually terminated.
Test why it terminated:
SUCCESS
GOAL_REACHED
NO_VALID_ACTION
MAX_ITERATIONS
MAX_COST
TOOL_FAILURE
GUARDRAIL
TIMEOUT
A system that frequently hits MAX_ITERATIONS is not “robust because it didn't hang.”
One particularly useful extension is to ask:
Given the state at step N, what should the agent have done? This produces a step-level expected action without requiring a single canonical trajectory.
For example:
Observed state:
- Search returned 12 results
- 3 match the required criteria
- User requested one recommendation
Agent action:
→ searches again
Evaluator:
→ unnecessary action
→ should have selected from existing results
This matters because agents are nondeterministic. There may be many valid trajectories to the same result. Evaluation should therefore usually judge properties of the path, rather than requiring exact trajectory matching.
I'd maintain separate quality and efficiency scores:
Efficient
↑
│
Dangerous │ Ideal
success │
│
──────────────────┼────────────────→ Correct
│
Wasteful │ Ineffective
failure │
│
For example:
score = {
"task_success": 0.92,
"trajectory_quality": 0.84,
"tool_correctness": 0.97,
"loop_health": 0.71,
"grounding": 0.90,
"efficiency": 0.63,
"safety": 1.00,
}
Don't collapse these immediately into one number.
A single aggregate score makes it difficult to answer why version B is better than version A.
Instead of creating 1,000 random examples, create a deliberately adversarial dataset:
normal tasks
├── straightforward
├── multi-step
├── long-horizon
│
tool failures
├── timeout
├── malformed response
├── empty response
├── contradictory response
│
reasoning traps
├── misleading tool result
├── irrelevant information
├── ambiguous request
│
loop traps
├── repeated observation
├── oscillating states
├── ineffective tool
├── partial progress
│
constraint traps
├── missing permission
├── unavailable resource
├── conflicting requirements
│
distribution shifts
├── unusual inputs
├── long context
├── unfamiliar tool combination
Every production failure should ideally become a regression case. This “trace → failure → dataset → regression test” loop is now a common pattern in agent evaluation systems.
Your evaluation harness should make experiments look like:
Dataset
│
├── Agent v1 ──→ traces ──→ evaluators ──→ scores
│
├── Agent v2 ──→ traces ──→ evaluators ──→ scores
│
└── Agent v3 ──→ traces ──→ evaluators ──→ scores
│
↓
regression analysis
Track:
v1 v2 v3
Task success 82% 87% 91%
Tool accuracy 91% 94% 95%
Loop failures 8% 5% 2%
Avg tool calls 7.2 5.9 4.8
P95 latency 14s 11s 9s
Cost/run $.18 $.14 $.11
Then put release gates around important metrics:
assert task_success >= 0.90
assert safety >= 0.99
assert loop_failure_rate <= 0.02
assert cost_regression <= 0.10
A robust system can look like:
┌───────────────┐
│ Test Dataset │
└───────┬───────┘
↓
┌───────────────┐
│ Agent Runner │
└───────┬───────┘
↓
┌───────────────┐
│ Trace Store │
└───────┬───────┘
↓
┌─────────────────┼─────────────────┐
↓ ↓ ↓
Step Evals Trace Evals Outcome Evals
│ │ │
└─────────────────┼─────────────────┘
↓
┌──────────────┐
│ Scorecard │
└──────┬───────┘
↓
Regression Gate
↓
Production
│
↓
New Traces
│
└──────→ Dataset
For implementation, LangChain's LangSmith is one existing option that explicitly supports trajectory-level agent evaluation, offline/online evaluation, datasets, and tracing.
Arize AI Phoenix is another strong fit if you want an open-source, OpenTelemetry/OpenInference-oriented observability and evaluation layer; it emphasizes tracing every agent step and turning traces into datasets and experiments.
You can also build the framework yourself—the important part is the evaluation model, not the vendor.
If you're designing this from scratch, make these five objects first-class:
Run
Trace
Step
Evaluator
Experiment
And define the relationship as:
Experiment
└── Run[]
└── Trace
└── Step[]
├── model decision
├── tool call
├── observation
└── state transition
Evaluator
└── scores Run / Trace / Step
Experiment
└── aggregates evaluator results
That gives you a framework that works equally well for a simple ReAct agent, a planner/executor architecture, a multi-agent system, or a long-running workflow with arbitrary loops.
The key principle: evaluate what the agent did, not merely what it said. For complex agents, the trajectory is part of the product.
Building and evaluating complex LLM agent workflows with loops and tool usage requires a shift from traditional software testing to a hybrid of deterministic logic control and probabilistic performance measurement. Here is a robust end-to-end framework designed to build, manage, and evaluate cyclic, tool-augmented…
Building and evaluating complex LLM agent workflows with loops and tool usage requires a shift from traditional software testing to a hybrid of deterministic logic control and probabilistic performance measurement.
Here is a robust end-to-end framework designed to build, manage, and evaluate cyclic, tool-augmented multi-agent systems.
Phase 1: Architecture & Design Pattern (Building)
Complex workflows with loops require explicit state management and guardrails to prevent infinite recursion or cascading errors.
Phase 2: Observability & Tracing
You cannot fix what you cannot see. Cyclic execution makes debugging impossible without deep tracing.
Phase 3: Evaluation Framework
Evaluating cyclic, tool-using agents requires shifting from single-turn evaluations to trajectory-based testing.
Evaluate the entire path the agent took, not just the final output, using LLM-as-a-judge patterns via libraries like DeepEval or Ragas:
If you'd like to dive deeper, let me know:
I can provide a concrete code architecture example or tailor the evaluation metrics to your use case.
No sources cited
Building and evaluating complex LLM agent workflows with loops and tool usage requires a robust, production-grade framework. Below is a comprehensive architectural framework divided into the **Build Phase** (state management, execution, and control flow) and the **Evaluation Phase** (observability, testing, and…
Building and evaluating complex LLM agent workflows with loops and tool usage requires a robust, production-grade framework. Below is a comprehensive architectural framework divided into the Build Phase (state management, execution, and control flow) and the Evaluation Phase (observability, testing, and guardrails).
Phase 1: The Build Framework (Execution & Control Flow)
Shared State Graph : Represent the agent's memory as a centralized, typed state object (e.g., using TypedDict or Pydantic) that updates immutably or via reducers across execution steps.
Checkpointing : Implement persistent state saving at every node transition to allow human-in-the-loop interventions, time-travel debugging, and fault tolerance during long-running loops.
Router Nodes : Use dedicated LLM or deterministic conditional router nodes to inspect current state and decide whether to terminate, loop back to a tool, or transition to a synthesis step.
Iteration Limits : Enforce hard caps on loop iterations (e.g., maximum 5 or 10 tool-call loops) to prevent infinite reasoning loops and runaway API costs.
Fallbacks and Exception Handling : Wrap tool execution nodes in try-catch blocks that feed error strings back into the agent's state rather than crashing the workflow, allowing the agent to self-correct.
Strict Schema Definition : Define tools using clear Pydantic schemas with detailed docstrings to minimize tool-selection errors by the LLM.
Sandbox Execution : Isolate high-risk tool executions (e.g., code interpreters, database queries) in secure, sandboxed environments with restricted permissions.
Phase 2: The Evaluation Framework (Testing & Monitoring)
Step-Level Tracing : Capture inputs, outputs, latency, and token consumption for every single node and tool invocation (popular tools include LangSmith or Phoenix).
Graph Visualization : Export execution traces as visual graphs to inspect where loops diverged or failed.
Unit Testing Tools : Test individual tool outputs and deterministic helper functions independently from the LLM reasoning loop.
Simulated Trajectory Evaluation : Evaluate multi-step agent paths against a golden dataset using LLM-as-a-judge metrics to score goal completion, efficiency (number of steps taken), and tool-selection accuracy.
Regression Test Suites : Run continuous integration tests on historical failed traces whenever prompt templates or base models are updated.
Input/Output Filters : Implement deterministic or model-based guardrails before tool execution (to check for prompt injection or malicious parameters) and after final output generation.
Would you like me to:
Yes. For complex agent workflows, I’d treat the **workflow execution itself as the primary evaluation object**, not merely the final LLM response. Recent agent-eval practice increasingly separates run-, trace/trajectory-, and thread-level evaluation, because a correct answer can hide bad tool choices, unnecessary…
Yes. For complex agent workflows, I’d treat the workflow execution itself as the primary evaluation object, not merely the final LLM response. Recent agent-eval practice increasingly separates run-, trace/trajectory-, and thread-level evaluation, because a correct answer can hide bad tool choices, unnecessary loops, or fragile execution paths.
Use five layers:
Represent each test case as:
task:
input: "Find the cheapest refundable flight to Boston next Friday."
goal:
type: structured_outcome
expected: ...
constraints:
max_steps: 12
max_cost_usd: 0.50
allowed_tools:
- search_flights
- get_flight_details
forbidden:
- purchase_flight
Your dataset should contain more than happy paths:
This is particularly important because tool selection and tool argument correctness are distinct failure modes.
Capture every observable state transition:
User
↓
LLM decision
↓
Tool A(args)
↓
Tool result
↓
LLM decision
↓
Tool B(args)
↓
Tool result
↓
LLM decision
↓
Final answer
For each step, record:
{
"step": 7,
"type": "tool_call",
"tool": "search_flights",
"arguments": {...},
"result": {...},
"latency_ms": 843,
"tokens": 1200,
"error": null
}
Also record:
Don't require hidden chain-of-thought. Evaluate observable actions, state transitions, tool calls, outputs, and outcomes instead.
Don't have one giant "agent quality" score.
Use a hierarchy:
| Level | Question | Example metric |
|---|---|---|
| Step | Was this action correct? | Tool-selection accuracy |
| Tool | Were arguments valid? | Argument correctness |
| Trajectory | Was the overall path reasonable? | Path/trajectory score |
| Task | Did it accomplish the objective? | Task success |
| Session | Did it behave correctly across turns? | Session success |
| System | Was it efficient/safe? | Cost, latency, policy violations |
For example:
Step correctness 0.94
Tool selection 0.97
Argument correctness 0.91
Trajectory quality 0.82
Task success 0.89
Safety 0.99
Cost efficiency 0.76
This makes debugging dramatically easier than:
Agent score = 0.87 Current evaluation tooling similarly emphasizes span-, trace-, trajectory-, session-, and dataset-level measurements.
Loops deserve their own evaluation layer.
Define:
progress(step) =
whether the state after this step is measurably closer
to satisfying the task
Then detect patterns such as:
A → B → A → B
or:
search → search → search → search
or:
tool_call → same_error → tool_call → same_error
Useful metrics:
loop_rate =
runs containing an unproductive cycle
--------------------------------------
total runs
redundant_calls / total_tool_calls
successful recoveries after tool failure
----------------------------------------
runs experiencing tool failure
useful state transitions / total state transitions
I'd also impose hard execution limits:
MAX_STEPS = 30
MAX_SAME_TOOL_CALLS = 3
MAX_COST = 1.00
MAX_RUNTIME_SECONDS = 120
These are not just infrastructure safeguards; they're evaluation metrics.
Your evaluation system should run like software CI:
┌─────────────┐
│ Test Dataset│
└──────┬──────┘
↓
┌───────────────┐
│ Agent Version │
└───────┬───────┘
↓
Execute traces
↓
┌──────────┴──────────┐
↓ ↓
Deterministic evals LLM judges
↓ ↓
└──────────┬──────────┘
↓
Aggregate scores
↓
Regression gates
↓
Ship / Reject
For example:
release_gates:
task_success: ">= 0.90"
tool_selection: ">= 0.95"
safety_violations: "== 0"
infinite_loops: "== 0"
p95_latency_seconds: "<= 20"
cost_per_successful_task: "<= 0.20"
And don't only maintain a static benchmark. Feed production failures back into the evaluation dataset. That creates the useful cycle:
production trace → failure → labeled example → regression test → new agent version → comparison.
A strong system combines three kinds of evaluators:
Use code wherever the answer is objectively knowable.
assert result.status == "completed"
assert "purchase_flight" not in trajectory.tools
assert trajectory.step_count <= 20
assert final.price <= budget
These should be your highest-confidence tests.
Compare an execution against acceptable behavior:
Expected:
search → filter → compare → answer
Acceptable:
search → search → filter → compare → answer
Bad:
search → search → search → search → ...
Don't require an exact trajectory when multiple strategies are valid. Evaluate invariants and constraints instead.
Use judges for things that are difficult to formalize:
Calibrate judges against human labels; don't treat an LLM judge as ground truth. Tool-specific judges are already being used for selection and invocation quality in current agent-eval systems.
I'd make every execution produce an Agent Trace:
AgentTrace(
task_id,
agent_version,
input,
final_output,
steps=[
Step(
type="llm",
input=...,
output=...,
),
Step(
type="tool",
name=...,
arguments=...,
result=...,
),
],
outcome={
"success": True,
"artifacts": ...,
"side_effects": ...,
},
resources={
"tokens": ...,
"cost": ...,
"latency": ...,
},
safety={
"violations": ...,
}
)
Then evaluators become functions over AgentTrace:
evaluate_task_success(trace)
evaluate_tool_selection(trace)
evaluate_tool_arguments(trace)
evaluate_trajectory(trace)
evaluate_loop_behavior(trace)
evaluate_safety(trace)
evaluate_cost(trace)
evaluate_final_response(trace)
That architecture is much more scalable than baking evaluation logic into the agent itself.
If you're looking for an existing implementation rather than building this infrastructure yourself, I'd shortlist:
The important distinction is that the agent framework and the evaluation framework don't have to be the same thing. You can build the workflow with LangGraph, a custom state machine, or another orchestration system, while emitting standardized traces into your evaluation layer.
┌─────────────────────────┐
│ Test Dataset │
│ tasks + expectations │
└────────────┬────────────┘
│
▼
┌───────────┐ ┌─────────────────────┐
│ Agent │──────▶│ Execution Harness │
│ Workflow │ │ limits + sandbox │
└───────────┘ └──────────┬──────────┘
│
▼
┌──────────────┐
│ Agent Trace │
└──────┬───────┘
│
┌───────────────┼────────────────┐
▼ ▼ ▼
Deterministic Trajectory LLM Judges
Evals Evals
│ │ │
└───────────────┼────────────────┘
▼
┌──────────────┐
│ Scorecard │
└──────┬───────┘
▼
┌─────────────────┐
│ CI/CD Gate │
│ + Production │
│ Monitoring │
└────────┬────────┘
│
▼
New regression
examples
The key design principle: evaluate outcome + trajectory + resource use + safety, while treating loops and tool calls as first-class events. A final-answer-only evaluator will systematically miss some of the most important agent failures.
If you're building this from scratch, I can also give you a concrete Python architecture for the execution harness + trace schema + evaluator interfaces + pytest/CI integration, which is probably the next useful level of detail.
Current evaluation tooling similarly emphasizes span-, trace-, trajectory-, session-, and dataset-level measurements.
Loops deserve their own evaluation layer.
Define:
progress(step) =
whether the state after this step is measurably closer
to satisfying the task
Then detect patterns such as:
A → B → A → B
or:
search → search → search → search
or:
tool_call → same_error → tool_call → same_error
Useful metrics:
loop_rate =
runs containing an unproductive cycle
--------------------------------------
total runs
redundant_calls / total_tool_calls
successful recoveries after tool failure
----------------------------------------
runs experiencing tool failure
useful state transitions / total state transitions
I'd also impose hard execution limits:
MAX_STEPS = 30
MAX_SAME_TOOL_CALLS = 3
MAX_COST = 1.00
MAX_RUNTIME_SECONDS = 120
These are not just infrastructure safeguards; they're evaluation metrics.
Your evaluation system should run like software CI:
┌─────────────┐
│ Test Dataset│
└──────┬──────┘
↓
┌───────────────┐
│ Agent Version │
└───────┬───────┘
↓
Execute traces
↓
┌──────────┴──────────┐
↓ ↓
Deterministic evals LLM judges
↓ ↓
└──────────┬──────────┘
↓
Aggregate scores
↓
Regression gates
↓
Ship / Reject
For example:
release_gates:
task_success: ">= 0.90"
tool_selection: ">= 0.95"
safety_violations: "== 0"
infinite_loops: "== 0"
p95_latency_seconds: "<= 20"
cost_per_successful_task: "<= 0.20"
And don't only maintain a static benchmark. Feed production failures back into the evaluation dataset. That creates the useful cycle:
production trace → failure → labeled example → regression test → new agent version → comparison.
A strong system combines three kinds of evaluators:
Use code wherever the answer is objectively knowable.
assert result.status == "completed"
assert "purchase_flight" not in trajectory.tools
assert trajectory.step_count <= 20
assert final.price <= budget
These should be your highest-confidence tests.
Compare an execution against acceptable behavior:
Expected:
search → filter → compare → answer
Acceptable:
search → search → filter → compare → answer
Bad:
search → search → search → search → ...
Don't require an exact trajectory when multiple strategies are valid. Evaluate invariants and constraints instead.
Use judges for things that are difficult to formalize:
Calibrate judges against human labels; don't treat an LLM judge as ground truth. Tool-specific judges are already being used for selection and invocation quality in current agent-eval systems.
Building and evaluating complex LLM agent workflows with loops and tool usage requires a shift from traditional linear pipelines (like basic RAG) to stateful, robust architectural patterns. Because loops introduce risks like infinite recursion, cascading errors, and state drift, your framework needs clear boundaries…
Building and evaluating complex LLM agent workflows with loops and tool usage requires a shift from traditional linear pipelines (like basic RAG) to stateful, robust architectural patterns. Because loops introduce risks like infinite recursion, cascading errors, and state drift, your framework needs clear boundaries for control flow, state management, and continuous evaluation.
Here is a comprehensive framework for building and evaluating these systems.
Phase 1: Architectural Design & Control Flow
For complex agent workflows featuring loops and tool integration, avoid unstructured "fully autonomous" loops. Instead, use structured orchestration patterns:
Phase 2: State Management & Tool Execution
Managing memory and side effects safely inside a loop is critical to preventing corruption of the agent's context window.
Phase 3: Evaluation Framework
Evaluating looping, tool-using agents is notoriously difficult because a single task can have multiple valid execution paths. Use a multi-layered evaluation strategy:
- Test individual tools in isolation for mock inputs and error handling.
- Test individual agent prompts/nodes for correct tool-selection accuracy (did the router pick the right tool for a given state?).
- Evaluate whether the *sequence* of tool calls and reasoning steps made logical sense, even if the final output had minor flaws.
- Use frameworks like LangSmith or Phoenix by Arize to visualize the execution graph and trace hidden latency or token costs per loop iteration.
- **LLM-as-a-Judge:** Use a separate, highly capable model to assess the final output against ground truth, grading for correctness, safety, and adherence to constraints.
- **Assertion-Based / Deterministic Checks:** For code-generation or data-retrieval agents, run automated tests (unit tests, schema validation) against the agent’s final artifact.
Recommended Tools & Libraries
To implement this framework, leverage production-grade orchestration and evaluation stacks:
If you'd like to dive deeper, let me know:
I can help you sketch out a concrete state schema and transition graph for your specific scenario.
No sources cited
Yes. For complex LLM agents, I’d treat the **entire execution trajectory—not just the final answer—as the unit of evaluation**. This is especially important once you have loops, branching, retries, memory, and tools: an agent can reach a correct answer through an unstable or dangerously inefficient path. Recent…
Yes. For complex LLM agents, I’d treat the entire execution trajectory—not just the final answer—as the unit of evaluation. This is especially important once you have loops, branching, retries, memory, and tools: an agent can reach a correct answer through an unstable or dangerously inefficient path. Recent agent-evaluation work converges on evaluating final outcomes, individual tool decisions, and full trajectories separately.
I’d structure the system around five layers:
Define every evaluation case as a structured task:
task_id: refund_042
input:
user: "Refund my most recent order."
environment:
customer_id: "cust_123"
orders:
- id: "ord_789"
status: "delivered"
refundable: true
available_tools:
- get_orders
- get_order
- issue_refund
success:
state:
refund_issued: true
order_id: ord_789
constraints:
max_steps: 12
max_cost_usd: 0.20
prohibited_actions:
- refund_without_confirmation
The key distinction is between:
For tool-using agents, state-based success criteria are particularly valuable: the final response can sound convincing even when the underlying database/API state is wrong.
Don't store only:
input → final answer
Store:
input
↓
LLM decision
↓
tool call + arguments
↓
tool result
↓
LLM decision
↓
tool call + arguments
↓
...
↓
final answer
↓
final environment state
A useful canonical representation is:
Trajectory = {
"task_id": str,
"run_id": str,
"steps": [
{
"step": 0,
"type": "llm",
"input_state": ...,
"output": ...,
"tool_calls": [...]
},
{
"step": 1,
"type": "tool",
"tool": "get_orders",
"arguments": {...},
"result": ...,
"error": None
},
],
"final_answer": str,
"final_state": ...,
"metrics": {
"latency_ms": ...,
"input_tokens": ...,
"output_tokens": ...,
"tool_calls": ...,
}
}
This makes the trajectory replayable and gives you the raw material for virtually every evaluator.
Tools such as LangSmith's AgentEvals explicitly treat the sequence of messages and tool calls as an evaluation object, while MLflow similarly emphasizes complete agent trajectories rather than isolated responses.
Don't create one giant agent_score.
Use a hierarchy.
For every tool call:
| Dimension | Question |
|---|---|
| Selection | Was this the correct tool? |
| Arguments | Were the arguments correct? |
| Preconditions | Was the tool allowed at this point? |
| Result handling | Did the agent interpret the result correctly? |
| Necessity | Did it actually need to call the tool? |
Example:
evaluate_tool_call(
expected_tool="get_order",
actual_tool="search_orders",
expected_args={"order_id": "ord_789"},
actual_args={"query": "latest order"}
)
This level catches errors that a final-answer evaluator misses.
Trajectory benchmarks increasingly use exactly these kinds of diagnostics—tool selection, argument correctness, and dependency/order satisfaction.
Evaluate each decision:
Given:
current state
available tools
previous observations
Did the agent choose a reasonable next action?
This can often be deterministic:
assert tool_name in allowed_tools
assert arguments_match_schema(args)
assert not violates_policy(state, action)
Or use an LLM judge for cases where several actions are reasonable.
Now evaluate the whole path:
Did the agent:
- gather the necessary information?
- avoid irrelevant actions?
- recover from failures?
- terminate appropriately?
- avoid loops?
- respect dependencies?
Importantly, don't always demand an exact reference trajectory.
There may be many valid paths:
A → B → C → DONE
A → C → DONE
A → B → D → C → DONE
A useful evaluator therefore supports:
These are also the trajectory-matching modes provided by AgentEvals.
Finally:
success = verify_environment(final_state, expected_state)
For example:
expected = {
"refund_issued": True,
"refunded_order": "ord_789"
}
actual = environment.snapshot()
assert actual["refund_issued"] == expected["refund_issued"]
assert actual["refunded_order"] == expected["refunded_order"]
This should ideally be programmatic, not LLM-judged.
Only after the above should you judge:
This can be an LLM-as-judge, provided you've calibrated it against human judgments. LangSmith, for example, supports both automated judges and human review specifically because LLM judges themselves aren't perfectly reliable.
Loops are where ordinary LLM evaluation breaks down.
Represent the agent as a state machine:
┌─────────────┐
│ THINK │
└──────┬──────┘
│
tool required?
/ \
yes no
│ │
▼ ▼
┌──────────┐ ┌─────────┐
│ TOOL │ │ FINAL │
└────┬─────┘ └─────────┘
│
tool succeeded?
/ \
yes no
│ │
▼ ▼
OBSERVE RECOVER
│ │
└────┬────┘
▼
THINK
Then make loop behavior measurable.
max_steps
actual_steps
max_tool_calls
actual_tool_calls
retries
repeated_tool_calls
repeated_states
cycles_detected
unique_states
progress_per_step
time_to_first_success
time_to_termination
I'd explicitly detect:
if state_hash in previous_states:
flag("state_loop")
and:
if identical_tool_call_count(tool, args) > 2:
flag("repeated_action")
You can also define a progress function:
progress(state) = number_of_goal_conditions_satisfied
Then flag trajectories like:
step progress
0 1
1 2
2 2
3 2
4 2
5 2
as likely stuck, even if they haven't technically exceeded the step limit.
Correctness isn't enough.
Two agents might both succeed:
Agent A: 4 tool calls, 8 seconds, $0.04
Agent B: 37 tool calls, 94 seconds, $0.71
They're not equivalent.
I'd report:
Outcome
├── task_success
├── partial_success
└── failure
Trajectory
├── tool_selection_accuracy
├── argument_accuracy
├── unnecessary_actions
├── recovery_quality
├── loop_rate
└── trajectory_similarity
Efficiency
├── steps
├── tool_calls
├── tokens
├── latency
└── cost
Safety
├── policy_violations
├── unauthorized_actions
├── destructive_actions
└── unsafe_tool_arguments
Then create a composite score only at the reporting layer, not as your fundamental metric.
A scalable implementation might look like this:
┌─────────────────────┐
│ Test Dataset │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Agent Executor │
│ │
│ LLM → Tool → LLM │
│ ↖ ↓ │
│ loop │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Trace Recorder │
└──────────┬──────────┘
│
┌───────────────┼────────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Determin- │ │ LLM Judges │ │ Environment│
│ istic Evals │ │ │ │ Verifiers │
└──────┬─────┘ └──────┬─────┘ └──────┬─────┘
│ │ │
└───────────────┼────────────────┘
▼
┌─────────────────────┐
│ Evaluation Report │
│ │
│ outcome │
│ trajectory │
│ tool use │
│ safety │
│ efficiency │
└─────────────────────┘
I'd resist starting with sophisticated LLM judges.
Build these layers sequentially:
This gives you a much more robust system than simply asking an LLM, "Was the agent good?"
Your dataset should deliberately exercise different failure modes:
| Category | Example |
|---|---|
| Happy path | Straightforward successful task |
| Multi-hop | Requires several dependent tools |
| Branching | Multiple valid strategies |
| Tool ambiguity | Two tools look appropriate |
| Tool failure | API returns an error |
| Bad data | Tool returns incomplete information |
| Retry | Temporary failure requires retry |
| Loop trap | Tool results don't change |
| Contradiction | Tools return conflicting information |
| Missing tool | Required capability doesn't exist |
| Long horizon | 20+ meaningful steps |
| State mutation | Agent changes external state |
| Safety boundary | User asks for unauthorized action |
| Early termination | Agent should stop rather than continue |
| Adversarial | Tool output attempts to redirect the agent |
This is important because trajectory length itself changes the failure profile; recent trajectory-oriented benchmarking has found meaningful degradation as tool diversity and dependency depth increase.
I would make "correct final answer" only one axis:
Agent Quality
│
┌───────────────┬─────┴─────┬───────────────┐
▼ ▼ ▼ ▼
Outcome Trajectory Safety Efficiency
│ │ │ │
Did it work? Did it act Was it allowed? Was it
sensibly? economical?
An agent should fail evaluation if:
✓ final answer correct
✗ unauthorized tool call
✗ 20 unnecessary calls
✗ entered a loop
✗ used incorrect arguments
✗ got lucky after taking a dangerous path
Conversely, it shouldn't necessarily fail simply because it used a different trajectory from your "golden" trajectory if the alternative path was valid.
That's the central tension in agent evaluation: you need enough trajectory constraints to catch bad behavior without accidentally defining one particular implementation as the only correct solution. Current agent-evaluation frameworks explicitly distinguish deterministic trajectory matching from more flexible LLM-judged trajectory quality for this reason.
You don't need a particular framework, but a practical implementation could combine:
The resulting mental model is:
Evaluate the task, record the trajectory, verify the world state, score every important decision, and treat loops/budget/safety as first-class constraints. That framework scales from a simple tool-calling agent to fairly complicated multi-agent workflows without changing the underlying evaluation model.
I would make "correct final answer" only one axis:
Agent Quality
│
┌───────────────┬─────┴─────┬───────────────┐
▼ ▼ ▼ ▼
Outcome Trajectory Safety Efficiency
│ │ │ │
Did it work? Did it act Was it allowed? Was it
sensibly? economical?
An agent should fail evaluation if:
✓ final answer correct
✗ unauthorized tool call
✗ 20 unnecessary calls
✗ entered a loop
✗ used incorrect arguments
✗ got lucky after taking a dangerous path
Conversely, it shouldn't necessarily fail simply because it used a different trajectory from your "golden" trajectory if the alternative path was valid.
That's the central tension in agent evaluation: you need enough trajectory constraints to catch bad behavior without accidentally defining one particular implementation as the only correct solution. Current agent-evaluation frameworks explicitly distinguish deterministic trajectory matching from more flexible LLM-judged trajectory quality for this reason.
You don't need a particular framework, but a practical implementation could combine:
The resulting mental model is:
Evaluate the task, record the trajectory, verify the world state, score every important decision, and treat loops/budget/safety as first-class constraints. That framework scales from a simple tool-calling agent to fairly complicated multi-agent workflows without changing the underlying evaluation model.
For complex LLM agents, I’d treat the problem as **workflow engineering + trajectory evaluation**, rather than “prompt evaluation.” The key is to evaluate the **entire execution path**—loops, tool choices, arguments, retries, state changes, and final outcome—not just the final text. This is now a common pattern in…
For complex LLM agents, I’d treat the problem as workflow engineering + trajectory evaluation, rather than “prompt evaluation.” The key is to evaluate the entire execution path—loops, tool choices, arguments, retries, state changes, and final outcome—not just the final text. This is now a common pattern in agent-evaluation tooling.
I’d structure the system into five layers:
┌──────────────────────────────────────────────────────┐
│ Evaluation Set │
│ tasks × environments × expected outcomes × policies │
└───────────────────────┬──────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ Workflow Runner │
│ │
│ state → LLM → tool → observation → route → loop │
│ ↑ │ │
│ └────────── retry ────────┘ │
└───────────────────────┬──────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ Trace │
│ steps, tools, args, observations, state, timing, cost │
└───────────────────────┬──────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ Evaluators │
│ outcome | trajectory | tools | state | efficiency │
│ safety | recovery | loop behavior | final response │
└───────────────────────┬──────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ Regression / Analysis │
│ pass rates, failure clusters, trajectory diffs, CI │
└──────────────────────────────────────────────────────┘
Don't make the evaluation framework depend on parsing logs from an opaque agent.
Represent each execution as something like:
State = {
"goal": ...,
"messages": [...],
"artifacts": {...},
"tool_results": [...],
"iteration": 7,
"budget": {...},
"status": "running",
}
And each transition as:
LLM decision
↓
tool call ──→ tool result
↓ │
state update ←─────┘
↓
termination / retry / another iteration
This makes loops first-class rather than treating them as an accidental property of the prompt.
For sophisticated workflows, a graph + agent loops inside graph nodes is a particularly useful abstraction. LangGraph, for example, explicitly models agent/tool loops this way.
Every execution should produce a normalized trace such as:
{
"task_id": "research_042",
"status": "success",
"steps": [
{
"type": "llm",
"node": "planner",
"input_tokens": 3200,
"output_tokens": 180
},
{
"type": "tool",
"name": "search",
"arguments": {"query": "..."},
"result": "...",
"latency_ms": 840
},
{
"type": "llm",
"node": "planner"
}
],
"final_output": "...",
"state_delta": {...},
"cost": 0.14,
"latency_ms": 12340
}
The important part is that tool calls and their arguments are data, not buried inside textual logs.
Trajectory-aware systems specifically emphasize tool selection, argument correctness, ordering/dependencies, and the final result as separate signals.
I recommend four levels.
| Level | Question | Typical evaluator |
|---|---|---|
| Step | Did the agent make the right decision here? | deterministic assertion |
| Trajectory | Was the sequence of actions reasonable? | trajectory judge / rules |
| Outcome | Did the task actually succeed? | deterministic + LLM judge |
| Thread | Does behavior remain correct across turns? | stateful evaluator |
The first three are particularly important. Modern agent-eval guidance similarly separates final-response, single-step, and trajectory evaluation.
For every tool invocation:
These are often best as deterministic assertions.
For example:
assert tool.name == "lookup_customer"
assert tool.args["customer_id"] == expected_id
Don't spend an LLM call judging something that can be checked exactly.
This is where things get interesting.
Score:
Importantly, don't require one exact trajectory unless the workflow actually requires one.
For example:
Expected:
search → retrieve → calculate → answer
Acceptable:
search → retrieve → calculate → answer
search → calculate → retrieve → answer
might both be fine.
LangChain's trajectory evaluator explicitly supports strict, unordered, subset, and superset matching for this reason.
Measure whether the actual task was accomplished:
task_success
answer_correctness
artifact_correctness
constraint_satisfaction
For actions with external side effects, verify the environment, not merely the agent's claim.
For example:
Agent: "I updated the database."
Evaluation:
database_before
↓
agent trajectory
↓
database_after
↓
assert expected mutation
That's much stronger than evaluating the final sentence.
Loops deserve their own metrics.
I'd record:
iterations
unique_tools_called
repeated_tool_calls
state_changes
progress_per_iteration
failed_iterations
recovery_iterations
Then define things like:
useful_iterations / total_iterations
duplicate_calls / total_tool_calls
Define a task-specific progress function:
P(state_0) = 0.0
P(state_1) = 0.3
P(state_2) = 0.7
P(state_3) = 1.0
Then look for trajectories such as:
0.0 → 0.3 → 0.7 → 1.0 good
0.0 → 0.3 → 0.3 → 0.3 stuck
0.0 → 0.3 → 0.1 → 0.3 thrashing
0.0 → 0.5 → 0.9 → 0.9 → 0.9
↑
failed to terminate
This is substantially more informative than simply recording max_iterations_exceeded.
I would not build the framework around an LLM judge.
Use three evaluator types:
┌─ deterministic assertions
│
Trajectory ──────┼─ domain-specific evaluators
│
└─ LLM judge
Use for:
Use for:
Use for things that are inherently qualitative:
This hybrid approach is also reflected in current trajectory-evaluation tooling, which combines deterministic trajectory matching with LLM-as-judge evaluation.
A powerful test case should look more like:
EvalCase(
id="refund_042",
task="Refund the customer's most recent eligible purchase",
initial_state={
"customer": ...,
"orders": ...
},
tools=[
get_customer,
list_orders,
refund_order
],
invariants=[
"Never refund an ineligible order",
"Refund requires order lookup",
],
success_condition=
"Exactly one eligible order was refunded",
budgets={
"max_steps": 12,
"max_cost": 0.50
}
)
Now you're testing the agent in an environment, rather than testing a prompt.
This also enables simulated users and multi-turn scenarios. Google's EvalBench, for example, evaluates multi-turn agents by capturing each turn, tool call, parameter, latency, and token usage before applying scorers.
Don't just produce:
PASS: 83%
FAIL: 17%
Produce:
17% failures
├── wrong_tool 5%
├── bad_arguments 3%
├── premature_termination 2%
├── infinite/redundant_loop 3%
├── tool_error_recovery 2%
├── state_corruption 1%
└── final_answer_error 1%
This turns evaluation into an engineering feedback loop.
Even better, cluster failures by trajectory pattern:
Pattern A
search → search → search → answer
"retrieval loop"
Pattern B
tool_error → same_tool → same_tool
"error recovery failure"
Pattern C
correct_tool → wrong_argument → hallucinated_answer
"argument grounding failure"
A strong framework shouldn't only test happy paths.
For each task, generate variants:
normal request
ambiguous request
missing information
misleading information
tool failure
empty tool result
partial tool result
slow tool
malformed tool result
conflicting tool results
very long context
irrelevant context
user changes objective mid-loop
Then ask whether the agent recovers, rather than merely whether it succeeds under ideal conditions.
Because agents are stochastic, store:
model
model version
system prompt version
tool definitions/version
workflow version
temperature
random seed (if available)
eval-case version
environment snapshot
And support:
run(case, seed=1)
run(case, seed=2)
run(case, seed=3)
...
Then report:
Success rate: 91%
± across seeds: 3.2%
Trajectory validity: 87%
Tool correctness: 94%
Median iterations: 6
P95 iterations: 14
Median cost: $0.11
P95 cost: $0.48
A single successful run is weak evidence for a stochastic agent.
If I were implementing this from scratch:
┌───────────────┐
│ Eval Cases │
└───────┬───────┘
│
┌───────▼───────┐
│ Test Runner │
└───────┬───────┘
│
┌─────────────▼─────────────┐
│ Agent Runtime │
│ │
│ graph / state / loops │
│ LLM / tools / subagents │
└─────────────┬─────────────┘
│
┌───────▼───────┐
│ Trace Collector│
└───────┬───────┘
│
┌───────────────────┼──────────────────┐
▼ ▼ ▼
Step Evaluators Trajectory Evaluator Outcome Evaluator
│ │ │
└───────────────────┼──────────────────┘
▼
┌───────────────┐
│ Score + Events│
└───────┬───────┘
▼
┌───────────────┐
│ Regression DB │
└───────────────┘
For the runtime, LangGraph is a natural fit if explicit graph/state/loop control is important; the OpenAI Agents SDK is another option if you want a more agent-centric runtime and integrations for durable execution.
For evaluation, I'd keep the evaluator interface runtime-agnostic:
result = evaluate(
trajectory,
case,
evaluators=[
task_success,
tool_correctness,
trajectory_quality,
loop_efficiency,
safety,
],
)
That separation is important: you should be able to change the agent framework without rewriting your evaluation methodology.
The most important design decision is this:
Evaluate agents as stateful programs whose execution produces trajectories, not as LLMs that produce answers. The final answer is one observation. The real object you're evaluating is:
┌──── tool ────┐
│ ▼
goal → decision → action → observation
▲ │
│ ▼
└──── state update ←─┘
Once you model it that way, loops, tool usage, retries, multi-agent handoffs, state mutations, cost, and termination all become measurable.
There is also growing evidence that simply adding more agents is not automatically beneficial; controlled evaluations have found that multi-agent configurations can trade additional cost/complexity for little or no accuracy improvement.
If you're choosing an implementation stack today, I'd shortlist LangGraph + LangSmith/AgentEvals, OpenAI Agents SDK + your own eval harness, or DeepEval/MLflow as a framework-independent evaluation layer. The important thing is less the particular brand and more whether the stack gives you full trace capture + deterministic assertions + trajectory scoring + environment/state verification.
I recommend four levels.
| Level | Question | Typical evaluator |
|---|---|---|
| Step | Did the agent make the right decision here? | deterministic assertion |
| Trajectory | Was the sequence of actions reasonable? | trajectory judge / rules |
| Outcome | Did the task actually succeed? | deterministic + LLM judge |
| Thread | Does behavior remain correct across turns? | stateful evaluator |
The first three are particularly important. Modern agent-eval guidance similarly separates final-response, single-step, and trajectory evaluation.
To build and evaluate complex LLM agent workflows with loops and tool usage, use an **architectural pattern based on state-machine graphs** ** paired with step-level tracing and LLM-as-a-judge evaluation frameworks**.[](https://tyk.io/learning-center/ai-agent-frameworks-a-developers-guide-to-production/)…
To build and evaluate complex LLM agent workflows with loops and tool usage, use an architectural pattern based on state-machine graphs ** paired with step-level tracing and LLM-as-a-judge evaluation frameworks**.
Production-grade agent frameworks (such as LangGraph or LlamaIndex Workflows ) treat agent loops as cyclic graphs rather than rigid linear chains.
Phase 1: Building the Agent Workflow (State-Machine Graph)
call_model, execute_tools , or human_review —which take the current state and return state updates.Phase 2: Key Architectural Components
| Component | Core Concept | Implementation Tool / Strategy |
|---|---|---|
| Orchestration Engine | Cyclic graph processing state transitions | LangGraph or custom state machines |
| Tool Execution | Secure schema parsing and runtime safety | Native provider tool-calling APIs (OpenAI, Anthropic) |
| Memory Management | Persistent cross-turn history and checkpointing | Redis/PostgreSQL state savers |
| Guardrails & Limits | Preventing infinite loops and runaway costs | Max-step counters and fallback branches |
Phase 3: Evaluating Complex Agent Workflows
Evaluating loops and tool usage requires testing both the final output and the intermediate reasoning trajectory.
If you'd like, let me know:
I can provide a modular code template using a leading orchestration framework.