---
title: "SUT"
---

<Note>SUT stands for *System Under Test*.</Note>
<Note>`@merit.sut` is optional. It exists to make your agent/workflow callable injectable and traceable.</Note>

`@merit.sut` registers a function (or class) as an injectable **resource** and wraps each invocation in an OpenTelemetry span. This gives you two things:
- A clean dependency injection (DI) boundary in your merits (you call the injected parameter, not a global).
- Trace spans you can query inside a test via `trace_context` (when tracing is enabled).

```python
import merit

@merit.sut
def chatbot(prompt: str) -> str:
    return f"Response to: {prompt}"

def merit_chatbot_works(chatbot):
    out = chatbot("hello")
    assert "hello" in out
```

## Basic Usage
The most common use case for `@merit.sut` is asserting on the SUT call span(s).

<Note>
`trace_context` is only available when tracing is enabled. From the CLI, run `merit test --trace`.
</Note>

<Steps>
<Step title="Decorate the system under test">

```python
# merits/merit_agent.py
import merit

from demo_app import agent

@merit.sut
def weather_agent(prompt: str):
    # Pass arguments to your production agent
    return agent(prompt, tools=["get_weather"])
```
</Step>
<Step title="Inject into a `merit` and assert on the trace">

```python
# merits/merit_agent.py

def merit_weather_agent_calls_tools(weather_agent, trace_context):
    out = weather_agent("What's the weather in SF?")

    # Retrieve spans for this SUT by name (defaults to the function name)
    spans = trace_context.get_sut_spans(name="weather_agent")

    # Assert on SUT span attributes emitted by Merit
    assert spans
    assert spans[0].attributes.get("merit.sut.name") == "weather_agent"
```
</Step>
</Steps>

## What `@merit.sut` actually does

### Injection semantics
`@merit.sut` registers a session-scoped resource factory so the Merit runner can inject it into tests by parameter name.

That’s why you should call the injected `chatbot` parameter inside `merit_chatbot_works(chatbot)`, not the decorated global name.

### Naming rules (important for DI and `get_sut_spans`)
- **Default name**: function name (`def weather_agent(...)` → `"weather_agent"`).
- **Name override**: `@merit.sut(name="...")`.
- **Classes**: default name is snake_case of the class (`MyAgent` → `"my_agent"`).

### Class-based SUTs
You can register a class as the SUT. By default Merit traces `__call__`, but you can specify another method:

```python
import merit

@merit.sut
class WeatherAgent:
    def __call__(self, prompt: str) -> str:
        return f"ok: {prompt}"

def merit_class_sut_is_injected(weather_agent):
    out = weather_agent("hi")
    assert out
```

```python
import merit

@merit.sut(method="run")
class Agent:
    def run(self, task: str) -> str:
        return f"done: {task}"

def merit_custom_method(agent):
    out = agent.run("task")
    assert out
```

## SUT span attributes
Each SUT call creates a span named `sut.<sut_name>` and sets:
- `merit.sut=true`
- `merit.sut.name=<sut_name>`

If `MERIT_TRACE_CONTENT=true` (default), Merit also records:
- `sut.input.args` / `sut.input.kwargs` (truncated repr)
- `sut.output` (truncated repr)

If `MERIT_TRACE_CONTENT=false`, Merit records only coarse metadata:
- `sut.input.count`
- `sut.output.type`

## Recommendations

### 1. Create isolated helpers; don't touch your production code

Many evaluation frameworks require developers to modify their production codebase to instrument traces. Merit avoids this pattern. The best way to introduce SUTs to your suite is to create isolated wrapper functions within your `merit_` files.

**Don't do this:**
```python
# src/app/agent.py
import merit
from typing import Literal

@merit.sut
def agent(prompt: str, domain: Literal["marketing", "coding"]) -> str:
    answer = call_llm(prompt=prompt, domain=domain)
    return f"Agent response: {prompt}"
```

**Do this:**
```python
# merits/merit_agent.py
import merit

from app import agent

@merit.sut
def marketing_agent(prompt: str) -> str:
    """Q&A system that answers questions about marketing concepts"""
    return agent(prompt, domain="marketing")

def merit_marketing_agent_invokes(marketing_agent):
    out = marketing_agent("What's CAC?")
    assert out
```

### 2. Pass using Dependency Injection; don't call directly

`@merit.sut` registers a resource factory for injection. Calling the decorated global directly is the wrong pattern (and may not work the way you expect). Always call the injected parameter.

**Don't do this:**
```python
import merit

@merit.sut
def chatbot(prompt: str) -> str:
    return f"Bot response: {prompt}"

def merit_chatbot_runs():
    # This is the resource factory, not the injected callable
    out = chatbot("Hello!")
    assert out
```

**Do this:**
```python
import merit

@merit.sut
def chatbot(prompt: str) -> str:
    return f"Bot response: {prompt}"

def merit_chatbot_runs(chatbot):
    out = chatbot("Hello!")
    assert out
```
