Skip to content

Tenro

Simulation Harness for Testing AI Agents.

Simulate agent workflows and verify behavior without burning tokens.

  • Simulate scenarios — Control LLM responses, errors, and tool results
  • Verify workflows — Check tool usage, edge cases, and agent behavior
  • Run evaluations — Measure agent behavior across test cases
  • Agnostic by design — Works across multiple LLM providers and agent frameworks
  • Multi-agent support — Test multi-agent workflows

How it works

Tenro intercepts LLM calls at the HTTP level. This works with any framework that uses a supported provider SDK. No patches, no special configuration.

# agents.py
from tenro import Provider, link_agent, link_tool

@link_tool("get_weather")
def get_weather(city: str) -> dict:
    return weather_api.fetch(city)

@link_agent("WeatherAgent")
def weather_agent(query: str) -> str:
    result = get_weather(query)  # LangChain, CrewAI, OpenAI SDK, anything
    return openai.chat.completions.create(...).choices[0].message.content
# test_agents.py
from tenro import Provider, ToolCall
from tenro.simulate import llm, tool, agent
import tenro

@tenro.simulate
def test_weather_agent():
    # Simulate tool result and LLM agentic loop
    tool.simulate(get_weather, result={"temp": 72, "condition": "sunny"})
    llm.simulate(Provider.OPENAI, responses=[
        ToolCall(get_weather, city="Paris"),
        "It's 72°F and sunny in Paris.",
    ])

    weather_agent("Weather in Paris?")

    # Verify tool, LLM, and agent output
    tool.verify(get_weather, city="Paris")
    llm.verify_many(Provider.OPENAI, count=2)
    agent.verify(weather_agent, result="It's 72°F and sunny in Paris.")

Learn how it works


Choose your framework


Write tests that read like specs

No patch decorators. No response builders. Just simulate and verify.

from tenro import Provider, ToolCall
from tenro.simulate import llm, tool
from myapp.agent import get_weather, WeatherAgent

@tenro.simulate
def test_agent():
    tool.simulate(get_weather, result={"temp": 72, "condition": "sunny"})
    llm.simulate(
        Provider.OPENAI,
        responses=[
            ToolCall(get_weather, city="Paris"),
            "It's 72°F and sunny in Paris.",
        ],
    )

    result = WeatherAgent().run("Weather in Paris?")

    tool.verify(get_weather)
    llm.verify_many(Provider.OPENAI, count=2)
    assert result == "It's 72°F and sunny in Paris."

Manual mocks, helper functions, boilerplate:

# test_helpers.py - you write and maintain this for each LLM provider
def mock_llm_response(content=None, tool_call=None):
    if tool_call:
        message = ChatCompletionMessage(
            role="assistant", content=None,
            tool_calls=[ChatCompletionMessageToolCall(
                id="call_abc", type="function",
                function=Function(name=tool_call["name"], arguments=json.dumps(tool_call["args"]))
            )]
        )
    else:
        message = ChatCompletionMessage(role="assistant", content=content, tool_calls=None)
    return ChatCompletion(
        id="chatcmpl-123", created=0, model="gpt-5", object="chat.completion",
        choices=[Choice(index=0, finish_reason="stop", message=message)]
    )

# test_agent.py
@patch("myapp.tools.get_weather")
@patch("openai.chat.completions.create")
def test_agent(mock_llm, mock_weather):
    mock_weather.return_value = {"temp": 72, "condition": "sunny"}
    mock_llm.side_effect = [
        mock_llm_response(tool_call={"name": "get_weather", "args": {"city": "Paris"}}),
        mock_llm_response(content="It's 72°F and sunny in Paris."),
    ]
    result = my_agent.run("Weather in Paris?")
    assert result == "It's 72°F and sunny in Paris."
    mock_weather.assert_called_once_with(city="Paris")

Provider support

Provider Status
OpenAI Supported
Anthropic Supported
Google Gemini Supported

Get started

Installation Quick start