Data as of Aug 25, 2026 · Based on 346 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
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.
Best when you need full observability and step-by-step tracing of every agent loop for debugging and reliability testing; complements orchestration platforms rather than replacing them.
Best for multi-agent conversational patterns—debate, planning, and collaborative refinement—where agents exchange roles and iterate on solutions; not primarily a graph-based state orchestrator, so pair with orchestration if you need durable state.
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](https://google.com/goto?url=CAESQwHrOzAVxDiL-gljL7_6lsjS4aLZvyvKLBTvS23Iku2iMJljpDvFNDtIiPYln1Tz0s-denyDurkw7AOAwEWA9XsHj3w) or [Phoenix by Arize](https://google.com/goto?url=CAESPQHrOzAV5lPJ_ePEle3zRo8-RNLzGb2ATa3jCXlOEMMDcWMwHwAQS3FOlIFHSl22jilRwiinkrFDNPvjmc0) 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 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 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**.
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.
Building and evaluating complex LLM agent workflows with loops and tool usage requires a shift from deterministic software architecture to a stateful, probabilistic control loop.
Here is a comprehensive framework broken down into Architecture/Building, State Management , and Evaluation.
Phase 1: Architectural Building Blocks
Phase 2: State Management & Memory
Phase 3: Evaluation Framework
Evaluating looping, tool-using agents requires moving beyond static input-output benchmarks to trajectory and behavior assessment.
If you'd like to narrow this down, tell me:
I can provide a more tailored architecture diagram or code pattern.
If you mean a framework for building complex agentic workflows—not just a chatbot agent loop—and evaluating the entire execution, my top picks are:
| Framework | Best for | Loops / branching | Tool use | Evaluation |
|---|---|---|---|---|
| LangGraph | Complex, stateful agent workflows | Excellent — graph cycles are a first-class pattern | Excellent | LangSmith provides tracing/evals |
| OpenAI Agents SDK | Lightweight agentic applications | Excellent agent/tool loops | Excellent | Built-in tracing + evaluation ecosystem |
| Microsoft Agent Framework | Production multi-agent/workflow systems, especially Python/.NET | Excellent — graph control flow, loops, conditional routing | Excellent | Built-in workflow evaluation |
| AutoGen / Microsoft Agent Framework successor | Existing AutoGen users | Strong | Strong | Stronger path now runs through Agent Framework |
For your specific wording—“complex LLM agent-based workflows with loops and tool usage”—I'd start with LangGraph.
It gives you a state-machine/graph abstraction where you can explicitly model:
┌───────────────┐
│ Planner │
└───────┬───────┘
↓
┌───────────────┐
┌────→│ Choose Tool │
│ └───────┬───────┘
│ ↓
│ ┌───────────────┐
│ │ Execute Tool │
│ └───────┬───────┘
│ ↓
│ ┌───────────────┐
│ │ Evaluate │
│ └───────┬───────┘
│ │
│ good? ├──no──→ loop
│ │
│ yes
│ ↓
│ ┌─────────┐
└────────│ Done │
└─────────┘
That's an important distinction from a simple agent framework: you can make the control flow explicit while still allowing the LLM to make decisions inside individual nodes. LangGraph specifically describes agents as operating in continuous feedback loops and supports persistence, streaming, debugging, and deployment.
For a serious system, I'd separate execution, observability, and evaluation:
Test cases / scenarios
│
▼
┌─────────────────────────────────────────────┐
│ Agent Workflow │
│ │
│ Planner → Tools → Critic → Loop → Output │
└──────────────────────┬──────────────────────┘
│
▼
Trace / trajectory
│
┌────────────┼────────────┐
▼ ▼ ▼
Outcome Tool use Efficiency
eval eval eval
│ │ │
└────────────┼────────────┘
▼
Aggregate score
I'd evaluate at three levels:
This matters because an agent can produce the correct final answer while taking a terrible trajectory—for example, making 17 unnecessary tool calls.
If you want the framework itself to provide more of the evaluation machinery, Microsoft Agent Framework is particularly interesting right now.
Its workflow system supports graph-based control flow, conditional routing, parallel execution, checkpointing, human-in-the-loop interactions, and multiple-agent orchestration.
More importantly for your question, its evaluation API explicitly supports evaluating workflows, including per-agent breakdowns, and has evaluators for things such as:
So if evaluation is as important as orchestration, I'd seriously consider MAF.
If you don't need a full graph/state-machine abstraction, the OpenAI Agents SDK is a cleaner option.
It has a deliberately small set of primitives—agents, tools/agent-as-tool or handoffs, and guardrails—and can express fairly sophisticated agent relationships without introducing a large workflow abstraction.
Its tracing is particularly useful for your use case: traces capture LLM generations, tool calls, handoffs, guardrails, and custom events, with spans representing the individual operations.
The JavaScript SDK similarly traces the hierarchy down to individual agent turns and function-tool calls.
Rule of thumb:
If you're building this as an evaluation/research harness rather than a production application, I'd lean toward LangGraph + a trajectory-level evaluator, because that gives you very explicit control over the execution graph and makes it straightforward to compare entire agent trajectories across models/prompts.
A useful way to think about complex LLM agent workflows is to evaluate them as closed-loop programs, not as prompt → response systems. The unit of evaluation is the trajectory: the sequence of model decisions, tool calls, observations, state changes, retries, and final outcomes. Recent agent-evaluation surveys emphasize that planning, tool use, memory, reliability, safety, and efficiency need to be evaluated separately rather than collapsed into a single answer score.
Here is a practical framework.
Represent every workflow as:
Goal
↓
Planner / Controller
↓
┌─────────────────────┐
│ Decide next action │
│ │
│ ┌───────────────┐ │
│ │ Tool call? │──┼──> Tool execution
│ └───────────────┘ │ ↓
│ │ Observation
│ Update state │<───────┘
│ │
│ Continue / stop │
└─────────────────────┘
↓
Final artifact / answer / side effect
Capture:
The trace is the primary artifact.
Question: Did the agent accomplish the user goal?
Metrics:
| Metric | Example |
|---|---|
| Goal completion | Did it create the requested report? |
| Correctness | Were facts/calculations correct? |
| Completeness | Were required subtasks done? |
| Artifact quality | Is the generated file usable? |
Use:
Do not only score the final answer.
Evaluate:
Example trajectory score:
Planning quality 0-5
Error recovery 0-5
State management 0-5
Efficiency 0-5
A correct answer reached through a fragile path should not receive the same score as a robust execution.
Evaluate every tool invocation.
Per tool call:
Tool selection:
Was this the right tool?
Arguments:
Were parameters valid?
Timing:
Was the tool called at the right step?
Interpretation:
Did the agent correctly use the result?
Useful metrics:
Tool success rate
= successful tool calls / total tool calls
Invalid call rate
= malformed or unavailable tool calls / total calls
Recovery rate
= recovered failures / total failures
Tool-use benchmarks commonly measure dimensions such as tool validity, schema adherence, runtime success, task completion, and planning efficiency.
Loops are where many agents fail.
Track:
same_action_repeat_count > threshold
same_tool_same_arguments repeatedly called
state unchanged after N iterations
token cost increasing without progress
Example guardrails:
if repeated_action_count > 3:
request_replan()
if no_state_change_for_steps > 5:
terminate_with_failure()
Metrics:
| Metric | Meaning |
|---|---|
| Average steps | Workflow efficiency |
| Maximum depth | Worst-case behavior |
| Recovery steps | Ability to escape errors |
| Dead-loop frequency | Stability |
An agent that succeeds but costs $5 per request may not be viable.
Track:
Latency
Token usage
Tool cost
External API calls
Failure rate
Human escalation rate
A useful composite score:
Agent Utility =
(success × quality)
-
(cost × latency × failure_penalty)
Create task families.
Example:
Example test object:
{
"task": "Prepare quarterly report",
"initial_state": {},
"tools": [
"database_query",
"spreadsheet_editor",
"email_sender"
],
"success_conditions": [
"report_created",
"numbers_verified",
"email_sent"
],
"failure_conditions": [
"unsupported_claims",
"wrong_recipient",
"duplicate_send"
]
}
Use multiple graders instead of one score.
Example:
Agent Trace
|
┌───────────┼───────────┐
↓ ↓ ↓
Outcome Tool use Safety
grader grader grader
↓ ↓ ↓
90 80 100
Overall: 87
Possible graders:
Best for:
Best for:
Best for:
A production trace should look like:
Run ID: 83921
Input:
"Analyze these customer complaints"
Step 1
LLM decision:
Use search_customer_database
Tool:
search_customer_database(
category="refund"
)
Result:
423 records
Step 2
LLM decision:
Cluster complaints
Step 3
Tool:
python_analysis()
Result:
3 major themes
Final:
Summary generated
Agent tracing tools and evaluation systems increasingly focus on run-level, trace-level, and thread-level evaluation because final answers alone hide inefficient or unsafe paths.
Every agent change should run:
Old agent
|
| compare
↓
New agent
Track:
| Metric | Before | After |
|---|---|---|
| Task success | 82% | 88% |
| Avg steps | 14 | 19 |
| Cost/task | $0.12 | $0.35 |
| Tool errors | 4% | 2% |
A change that improves accuracy but doubles cost may not be an improvement.
A mature setup usually has:
Task Generator
|
↓
Evaluation Runner
|
┌──────────────┼──────────────┐
↓ ↓ ↓
Agent Trace Validators Judges
↓ ↓ ↓
└──────────────┼──────────────┘
↓
Metrics Store
↓
Dashboard
Typical components:
Frameworks such as MLflow’s agent evaluation tooling, DeepEval, and other agent-evaluation systems are examples of approaches that evaluate multi-step behavior rather than only final text output.
A good rule of thumb:
Evaluate agents like distributed software systems, not like chatbots.
The answer matters, but the path taken, tools used, state transitions, failures, cost, and recoverability determine whether the system is actually reliable.
Building and evaluating complex LLM agent workflows with loops and tool usage requires a robust, production-grade framework. Because loops introduce unpredictable execution paths, state management, and infinite recursion risks, your architecture needs clear separation between control flow, state, and evaluation.
Phase 1: Architectural Framework for Execution
To handle loops and tool usage reliably, structure your workflow into these core components:
Phase 2: Evaluation Framework
Evaluating cyclic, tool-augmented agents goes beyond standard LLM-as-a-judge accuracy metrics. You need multi-layered evaluation:
Phase 3: Recommended Tech Stack & Tools
If you want to tailor this further, tell me:
I can provide a concrete architectural diagram or a code skeleton for your loop logic.
LangSmith
Yes. For complex LLM workflows with loops, branching, retries, and tool use, I’d treat the problem as software-system evaluation, not merely “LLM answer quality.”
A good framework has four layers:
This is increasingly the direction of agent evaluation tooling: trajectory evaluation is explicitly being treated as complementary to final-response evaluation.
Don't evaluate an agent as:
input → LLM → answer
Model it as:
┌──────────────┐
│ START │
└──────┬───────┘
↓
┌──────────────┐
│ PLAN/DECIDE │
└──────┬───────┘
↓
┌──────────────┐
│ TOOL CALL │
└──────┬───────┘
↓
┌──────────────┐
│ OBSERVE │
└──────┬───────┘
↓
┌────────┴────────┐
│ │
sufficient? insufficient?
│ │
↓ ↓
DONE replan/retry
│
└──────→ PLAN
Represent each execution as something like:
Run {
input
state
steps[]
final_output
status
cost
latency
}
and each step as:
Step {
id
parent_id
type # llm | tool | router | human | evaluator
input
output
tool_name
tool_args
tool_result
state_before
state_after
error
latency
tokens
}
The important part is that the trajectory becomes a first-class artifact.
Graph/state-machine approaches such as LangGraph are particularly well suited to this because workflows can explicitly represent conditional routing and agent loops, while maintaining state and checkpoints.
I'd use three nested evaluation layers.
Ask:
Was this individual action correct?
Examples:
| Step | Evaluation |
|---|---|
| Tool selection | Was the right tool selected? |
| Arguments | Were arguments valid/correct? |
| Retrieval | Was the query appropriate? |
| Tool result handling | Was the observation interpreted correctly? |
| Routing | Was the next state appropriate? |
| Retry | Was retry warranted? |
These are mostly deterministic assertions.
For example:
assert tool_name == "search_customer"
assert args["customer_id"] == expected_id
assert retry_count <= 2
Now evaluate the whole path:
Did the agent take a reasonable route to the solution?
Metrics I'd track:
trajectory_success
tool_selection_accuracy
tool_argument_accuracy
unnecessary_tool_calls
repeated_actions
loop_count
recovery_success
constraint_violations
steps_to_completion
This is crucial because an agent can get the correct answer through a terrible trajectory.
For example:
Run A:
search → database → answer
Run B:
search → search → search → database → search → answer
Both may produce the same answer, but B is clearly a worse agent.
Frameworks such as AgentEvals specifically focus on agent trajectories, including tool-call sequences, rather than only final outputs.
Finally:
Did the workflow actually accomplish the user's goal?
Measure:
task_success
answer_correctness
completeness
groundedness
format_compliance
safety
user_constraints_satisfied
Use deterministic validators wherever possible and LLM judges only where semantic judgment is genuinely necessary.
Loops are where conventional LLM evals become inadequate.
For every loop, define:
Why are we entering the loop?
while not sufficient_evidence(state):
...
Did this iteration actually make progress?
new_information > previous_information
What makes the loop stop?
if confidence >= threshold:
return DONE
What happens if the model never converges?
MAX_ITERATIONS = 8
MAX_TOOL_CALLS = 30
MAX_COST = 1.00
MAX_RUNTIME = 120
Then explicitly evaluate:
convergence_rate
iterations_to_success
iterations_to_failure
loop_repetition_rate
premature_termination_rate
runaway_loop_rate
I'd consider progress per iteration one of the most valuable metrics for sophisticated agents.
For example:
iteration evidence confidence
----------------------------------------
1 2 sources .42
2 4 sources .63
3 5 sources .81
4 5 sources .82 ← no meaningful progress
The fourth iteration should probably have terminated.
A useful test case should specify more than an input/output pair.
id: customer_refund_042
input:
customer: "Alice"
request: "Refund my most recent order"
environment:
customer_exists: true
order_exists: true
refund_eligible: true
expected:
goal: refund_order
constraints:
must_verify_identity: true
must_not_refund_ineligible_orders: true
trajectory:
required:
- lookup_customer
- lookup_order
- verify_eligibility
- issue_refund
forbidden:
- issue_refund_without_verification
limits:
max_steps: 12
max_tool_calls: 8
This gives you behavioral contracts rather than brittle expected transcripts.
That's important because you generally don't want to require:
exactly:
search → lookup → answer
when these might both be valid:
search → lookup → answer
and:
lookup → search → lookup → answer
Instead, specify constraints such as:
lookup_customer MUST occur before issue_refund
issue_refund MUST NOT occur if eligibility=false
answer MUST cite the resulting refund ID
Trajectory evaluators can then support concepts such as exact, ordered, or flexible trajectory matching.
I'd structure the evaluation suite like normal software testing:
┌───────────────┐
│ End-to-end │
│ scenarios │
└───────────────┘
┌─────────────────────┐
│ Trajectory / system │
│ tests │
└─────────────────────┘
┌─────────────────────────────┐
│ Tool / node evaluations │
└─────────────────────────────┘
┌─────────────────────────────────────┐
│ deterministic unit/property tests │
└─────────────────────────────────────┘
Cheap, deterministic:
tool schemas
routing logic
state transitions
retry policies
termination logic
permission checks
Test individual agent/tool combinations.
Run the agent and inspect its entire path.
Measure actual task completion.
Intentionally create:
tool failure
malformed tool result
empty search results
contradictory evidence
context overflow
ambiguous request
repeated observations
slow tool
partial database outage
malicious tool output
This catches much more than a standard “golden answer” dataset.
A strong evaluator might look like:
scores = {
# deterministic
"tool_validity": ...,
"policy_compliance": ...,
"required_steps": ...,
"forbidden_steps": ...,
"iteration_limit": ...,
# semantic
"answer_correctness": ...,
"reasoning_quality": ...,
"evidence_quality": ...,
# operational
"latency": ...,
"cost": ...,
}
I'd strongly prefer:
Code grader → LLM judge → human
rather than:
LLM judge → everything
For example, don't ask an LLM whether:
“Did the agent call
delete_user?”
Just inspect the trace.
Use an LLM judge for things like:
“Given the available evidence, was the agent's conclusion adequately supported?”
OpenTelemetry is a particularly useful abstraction here because it lets you separate your agent implementation from your evaluation infrastructure.
A trace might be:
TRACE
│
├── LLM: planner
│ └── tool_call: search
│
├── TOOL: search
│ └── result
│
├── LLM: planner
│ └── tool_call: search
│
├── TOOL: search
│
├── LLM: planner
│ └── tool_call: database
│
├── TOOL: database
│
└── LLM: final_answer
You can then run multiple evaluators against the same trace without rerunning the expensive agent. AgentEvals, for example, explicitly supports scoring agents from existing OpenTelemetry traces.
That separation is powerful:
AGENT RUN
│
↓
TRACE
│
┌──────────┼──────────┐
↓ ↓ ↓
quality safety cost
grader grader grader
│ │ │
└──────────┼──────────┘
↓
scorecard
Don't compare:
Agent A = 82%
Agent B = 84%
from 20 examples and conclude B is better.
Run the same scenarios across versions:
v1 v2 Δ
---------------------------------------
Task success 82% 87% +5
Tool accuracy 91% 94% +3
Loop rate 12% 7% -5
Cost $0.42 $0.51 +21%
Latency 8.2s 9.4s +15%
Then examine paired failures:
v1 succeeds / v2 fails
v1 fails / v2 succeeds
both fail
both succeed
This tells you what changed, rather than just producing a single aggregate score.
Recent work on agent evaluation is also emphasizing normalized execution protocols and trajectory logging precisely because otherwise comparisons between agent architectures can be misleading.
A workflow that gets 95% success using 40 tool calls isn't necessarily better than one achieving 93% using 8.
I'd use a scorecard roughly like:
Weight
Task success 40%
Trajectory quality 20%
Safety/correctness 15%
Efficiency 10%
Robustness 10%
User experience 5%
But keep the raw metrics too.
For example:
Success rate: 91%
Critical-error rate: 0.4%
Median steps: 7
P95 steps: 18
Median tool calls: 4
Loop failure rate: 1.2%
Median latency: 8.1s
P95 latency: 31.4s
Mean cost: $0.18
Don't collapse everything into one number. A single score is useful for CI gating, but dangerous as the primary diagnostic.
If you're building this yourself, I'd make the architecture:
┌──────────────────────┐
│ Eval Scenarios │
│ inputs + constraints │
└──────────┬───────────┘
↓
┌──────────────────────┐
│ Workflow Runner │
│ │
│ state machine / │
│ agent / tools / loop │
└──────────┬───────────┘
↓
┌──────────────────────┐
│ Trace │
│ │
│ LLM/tool/state/event │
│ /cost/error/timing │
└──────────┬───────────┘
↓
┌─────────────────┼──────────────────┐
↓ ↓ ↓
Step evaluators Trajectory evaluators Outcome evaluators
│ │ │
└─────────────────┼──────────────────┘
↓
┌──────────────────────┐
│ Score Report │
│ │
│ quality / safety / │
│ efficiency / cost │
└──────────┬───────────┘
↓
┌──────────────────────┐
│ Regression Store │
│ │
│ v1 vs v2 vs v3 ... │
└──────────────────────┘
For the orchestration layer, I'd consider:
For evaluation, I'd design around OpenTelemetry traces so that your evaluator isn't coupled to whichever orchestration framework you choose. Existing tooling such as AgentEvals demonstrates this pattern.
And for complex systems, I'd borrow heavily from traditional software testing: deterministic assertions, mocks, fault injection, regression suites, property tests, and CI gates. Recent research on structural agent testing is explicitly applying this testing-pyramid approach to agent trajectories and traces.
The biggest conceptual shift is:
Don't evaluate an agent only by whether it got the answer right. Evaluate whether it got the answer right through an acceptable trajectory, under acceptable resource and safety constraints.
For loop-heavy agents, I would make state + trajectory + termination first-class objects from day one. That gives you a framework that can evaluate everything from a simple ReAct loop to a multi-agent workflow with retries, branching, human approval, and long-running state.
A robust framework for building and evaluating complex LLM agent workflows with loops and tool usage relies on separating explicit orchestration logic (the graph/control flow) from implicit model reasoning, while tracking entire execution trajectories rather than just final outputs . Production systems use a modular pipeline combining structural design patterns, stateful execution loops, and multi-layered evaluation.
Building Complex Agent Workflows
When constructing agent loops and tool integration, favor transparent, inspectable orchestration over opaque "magic" agent abstractions.
Evaluating Agent Trajectories
Evaluating cyclic, multi-step agent behavior requires moving past single-turn input/output tests to analyze the entire chronological sequence of reasoning and tool interactions.
| Evaluation Layer | Mechanism | Primary Target |
|---|---|---|
| Trace Inspection | Manual or automated step-through of execution trees | Identifying hidden failure modes and excessive loop steps |
| Code-Based Checks | Deterministic assertions on tool arguments and schema matches | Syntax, correct tool selection, and formatting compliance |
| LLM-as-a-Judge | Secondary model instances scoring intermediate reasoning steps | Semantic coherence, helpfulness, and instruction following |
| Environment Verification | Checking side-effects or mock-environment mutations | Verifying if the tool output actually solved the step goal |
Production Iteration Lifecycle
If you'd like to narrow this down, please share: