Agent Runner Reference Implementations¶
Three reference implementations are provided in examples/. They show how to wrap common frameworks in the AgentRunner protocol. Copy and adapt whichever fits your stack.
Choosing a runner¶
| Runner | File | Use when |
|---|---|---|
ReActAgent |
react_bedrock.py |
You want full control over the agent loop; no framework dependency |
LangChainAgentRunner |
langchain.py |
Your agent is already built on LangChain |
BedrockAgentRunner |
bedrock_agents.py |
You have a deployed Bedrock Agent and want integration tests |
The first two run fully offline against mock tools — they are deterministic, free, and suitable for CI. The third calls real AWS infrastructure.
ReActAgent — custom ReAct loop on Bedrock¶
File: examples/react_bedrock.py
What it does:
Implements a Reason + Act loop from scratch. The model is prompted to output XML-tagged tokens (<thought>, <tool_call>, <answer>). The runner parses each response, executes tool calls through MockToolExecutor, and feeds observations back as <observation> messages. It loops until the model emits <answer> or hits max_iterations.
Usage:
Configuration:
# Override defaults at construction time
agent = ReActAgent(
model_id="anthropic.claude-3-5-sonnet-20241022-v2:0",
region="us-east-1",
max_iterations=10,
)
Pass a custom instance by creating a wrapper class:
# myapp/agents.py
from agent_eval.examples.react_bedrock import ReActAgent as _Base
class MyReActAgent(_Base):
def __init__(self):
super().__init__(
model_id="us.anthropic.claude-opus-4-6-20251101-v1:0",
max_iterations=15,
)
How trajectory steps map to the output:
| Model output | Step type |
|---|---|
<thought>...</thought> |
StepType.thought |
<tool_call>{"name": ..., "arguments": ...}</tool_call> |
StepType.tool_call |
| Tool response | StepType.observation |
<answer>...</answer> |
StepType.answer |
If max_iterations is reached without an <answer> tag, the trajectory returns with error set and no final_answer.
Tradeoffs: - Full control over parsing, prompting, and loop logic - No additional dependencies beyond boto3 - Prompt format is XML-specific — you'd need to adapt if your production agent uses a different format
LangChainAgentRunner — LangChain agent with mock tools¶
File: examples/langchain.py
What it does:
Wraps a LangChain AgentExecutor so it evaluates against MockToolExecutor. Each tool spec from the dataset is converted into a LangChain Tool that delegates to tool_executor.execute(). This means your agent calls its normal LangChain tool-use flow but gets back canned responses — runs are deterministic and don't touch real services.
Installation:
Usage:
Swapping the LLM:
The runner uses ChatBedrock by default. To use a different provider, copy the file and change the llm initialisation:
# Use Anthropic API directly
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(model="claude-opus-4-6")
# Use OpenAI
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o")
How trajectory steps are recorded:
Tool calls and observations are recorded as they happen inside the LangChain callback. The final_answer comes from executor.invoke()["output"]. Internal LangChain thought steps are not currently recorded as StepType.thought — only tool calls, observations, and the final answer appear in the trajectory.
Tradeoffs:
- Requires LangChain dependencies
- Agent logic matches your production code exactly (no reimplementation)
- Thought steps are not fully captured (LangChain abstracts them)
- Tool input parsing assumes JSON; falls back to {"input": raw_string} on parse failure
BedrockAgentRunner — AWS Bedrock Agents¶
File: examples/bedrock_agents.py
What it does:
Invokes a deployed Bedrock Agent via bedrock-agent-runtime:InvokeAgent and converts its trace events into a Trajectory. The agent runs against its real Lambda action groups — mock tools are bypassed entirely.
Usage:
export BEDROCK_AGENT_ID=XXXXXXXXXX
export BEDROCK_AGENT_ALIAS_ID=YYYYYYYYYY
supereval agent run my-dataset \
--runner examples.bedrock_agents:BedrockAgentRunner
Or pass IDs directly:
class MyBedrockRunner(BedrockAgentRunner):
def __init__(self):
super().__init__(
agent_id="XXXXXXXXXX",
agent_alias_id="YYYYYYYYYY",
region="us-west-2",
)
Important: Because this runner calls real AWS infrastructure:
- The tools block in each AgentTestCase is ignored — the agent uses its real Lambda functions
- Cost and latency figures are real AWS charges
- Runs are not deterministic — the agent may behave differently on each invocation
- Do not use this runner in CI unless you have a dedicated staging environment
Trace events mapped to steps:
| Bedrock trace event | Step type |
|---|---|
orchestrationTrace.rationale.text |
StepType.thought |
orchestrationTrace.invocationInput.actionGroupInvocationInput |
StepType.tool_call |
orchestrationTrace.observation.actionGroupInvocationOutput |
StepType.observation |
| Final completion chunk | StepType.answer |
When to use this vs. ReActAgent:
Use BedrockAgentRunner when you want to verify that your deployed Bedrock Agent behaves correctly end-to-end in a staging environment. Use ReActAgent (or LangChainAgentRunner) for fast, reproducible, offline evals in CI.
IAM requirements:
{
"Effect": "Allow",
"Action": ["bedrock:InvokeAgent"],
"Resource": "arn:aws:bedrock:*:*:agent-alias/*/*"
}
Implementing your own runner¶
If none of the reference implementations match your stack, implement the AgentRunner protocol directly:
from supereval.agent.models import Step, StepType, ToolCall, Trajectory
class MyAgent:
def run(self, task: str, tool_executor) -> Trajectory:
steps = []
# Call your agent logic here
# Use tool_executor.execute(tool_name, arguments) to call tools
# Use tool_executor.available_tools() to get ToolSpec list
result = tool_executor.execute("search", {"query": task})
steps.append(Step(
type=StepType.tool_call,
content="searched",
tool_call=ToolCall(name="search", arguments={"query": task}, result=result),
))
steps.append(Step(type=StepType.observation, content=str(result)))
answer = str(result[0]) if result else "not found"
steps.append(Step(type=StepType.answer, content=answer))
return Trajectory(steps=steps, final_answer=answer)
The only requirement is that run() returns a Trajectory. If your agent crashes, catch the exception and return Trajectory(error=str(exc)) — the scorer will mark the case as failed with the error message as the failure reason.