Zum Inhalt

Agentic Evaluations

Evaluieren Sie autonome Agenten end-to-end: Tasks, Trajectories, Trace-basierte Criteria und aggregierte Agent-Metriken.

Agentic Evaluations erweitern elluminate über einzelne LLM-Outputs hinaus auf Agenten, die planen, Tools aufrufen und über viele Schritte an einer Aufgabe arbeiten. Sie führen den Agenten extern aus (mit Harbor, einem eigenen Harness oder einem beliebigen Framework, dessen Output sich in das ATIF-Trajectory-Format übersetzen lässt) und laden die Trial-Ergebnisse inklusive der vollständigen Trajectories zu elluminate hoch. elluminate bewertet anschließend jedes Criterion gegen die Trajectory, und die UI zeigt Trace, Ratings pro Criterion und aggregierte Metriken.

Wann Agentic Evaluations verwenden

Verwenden Sie diesen Workflow, wenn:

  • Ihr System mehrere LLM-Aufrufe pro Task macht (Tool-Use, Plan/Act-Loops, Sub-Agents).
  • Die Evaluation anschauen muss, was der Agent gemacht hat, nicht nur seine finale Nachricht.
  • Sie bereits einen externen Runner einsetzen (oder einsetzen möchten), z.B. Harbor, LangChain, CrewAI, AutoGen oder eigenen Code.

Für einzelne Outputs oder Tool-Calling-Patterns, bei denen elluminate die Responses selbst generiert, siehe stattdessen das Guide Tool Calling.

elluminate führt Ihren Agenten nicht aus

Agentic Evaluations decken das Hochladen und Bewerten externer Agent-Runs ab. Sie führen den Agenten selbst aus (mit Harbor, einem eigenen Harness oder einem anderen Framework); elluminate speichert die Trial-Ergebnisse, rendert den Trajectory Viewer und bewertet optional jedes Criterion gegen die Trajectory.

Trajectory-Format

elluminate akzeptiert Trajectories ausschließlich im eigenen ATIF-Format (v1.*). Es gibt keinen nativen Importer für LangChain, CrewAI, AutoGen oder andere Frameworks — Sie übersetzen den Output Ihres Runners nach ATIF und laden ihn per SDK hoch. Das Beispiel im Abschnitt SDK Reference zeigt die Übersetzung für einen Harbor-förmigen Per-Task-Output; dasselbe Muster gilt für jedes andere Framework.

UI-Workflow

Agentic Experiment Übersicht

  1. AGENTIC Collection erstellen. Jede Zeile ist eine Task-Beschreibung. Der collection_type der Collection wird auf AGENTIC gesetzt, was den Trajectory-Viewer und die agentischen Metriken freischaltet. Da AGENTIC Experimente nie automatisch Responses generieren, nutzt die Collection eine einzelne RAW_INPUT-Column für den Task-Text; ein Prompt Template ist nicht nötig.
  2. Criterion Set erstellen. Jedes Criterion ist eine binäre YES/NO-Frage, die elluminate gegen die Trajectory beantwortet (z.B. "Hat der Agent die richtige Datei bearbeitet?").
  3. AGENTIC Experiment erstellen, das Template, Collection und Criterion Set verbindet. Der evaluation_mode des Experiments wird auf AGENTIC gesetzt, was die Auto-Generation deaktiviert; die Responses kommen vom externen Runner.
  4. Agent extern ausführen (Harbor, eigenes Script) und ein Ergebnis pro Task einsammeln, inklusive einer ATIF-Trajectory.
  5. Ergebnisse hochladen über das SDK. Wenn Trajectories vorhanden sind, bewertet elluminate automatisch jedes Criterion gegen die Trajectory.
  6. Ergebnisse inspizieren in der UI: Trajectory-Viewer, Ratings pro Criterion mit Reasoning und aggregierte Metriken (durchschnittliches Reward, durchschnittliche Execution Time, durchschnittliche Cost).

Trajectory-Viewer

Konzept-Mapping: Harbor zu elluminate

Wenn Sie von Harbor kommen, lassen sich die Konzepte ungefähr wie folgt abbilden:

Harbor elluminate
Dataset Collection (collection_type: AGENTIC)
Task Zeile in der Collection (ein TemplateVariables mit einer RAW_INPUT-Task-Column)
instruction.md Task-Text in der RAW_INPUT-Task-Column der Zeile
task.toml Env-Config Collection environment_config (optional)
Reward AgentTrialResult.reward
Trajectory AgentTrialResult.trajectory (ATIF)
Test- / Judge-Skripte Criterion Set (gegen die Trajectory ausgewertet)
Agent Experiment evaluation_mode: AGENTIC

AgentTrialResult-Payload

Jedes Trial, das Ihr Runner produziert, wird auf ein AgentTrialResult abgebildet. Pflicht- und optionale Felder:

Field Required Beschreibung
task_name yes Muss exakt einem Wert in der Collection-Spalte entsprechen, die als task_name_column angegeben ist.
messages no Finale Messages im OpenAI-Format (wird auf der Response-Seite angezeigt).
reward no Primärer Reward-Score (0.0–1.0).
steps no Anzahl der Agent-Steps / LLM-Aufrufe.
cost_usd no Gesamtkosten in USD für das Trial.
duration_seconds no Wall-Clock-Dauer.
input_tokens no Aggregierte Input-Tokens.
output_tokens no Aggregierte Output-Tokens.
cached_tokens no Aggregierte Cached-Input-Tokens.
error no Fehlermeldung, falls das Trial fehlgeschlagen ist.
metadata no Freier Dict, wird auf der Response-Seite angezeigt.
trajectory no Rohe ATIF-Trajectory (wird vom Backend validiert; siehe ATIF-Format).
criterion_ratings no Vorberechnete Ratings. Überspringt die Auswertung durch elluminate (siehe Evaluation-Modi).

ATIF-Trajectory-Format

Trajectories verwenden das Agent Trajectory Interchange Format (ATIF). Das Backend validiert den Payload beim Upload, speichert ihn aber roh, sodass zusätzliche Keys für den Trajectory-Viewer unverändert erhalten bleiben.

Eine minimale ATIF-v1-Trajectory:

{
  "schema_version": "ATIF-v1.0",
  "session_id": "harbor-run-001/write-hello-world",
  "agent": {
    "name": "harbor-demo-agent",
    "version": "0.1.0",
    "model_name": "claude-sonnet-4-6"
  },
  "steps": [
    {
      "step_id": 1,
      "source": "user",
      "message": "Write a Python hello world script to hello.py"
    },
    {
      "step_id": 2,
      "source": "agent",
      "message": "Writing hello.py.",
      "tool_calls": [
        {
          "tool_call_id": "tc_1",
          "function_name": "write_file",
          "arguments": {"path": "hello.py", "content": "print('Hello, World!')"}
        }
      ],
      "observation": {
        "results": [{"source_call_id": "tc_1", "content": "wrote 22 bytes"}]
      },
      "metrics": {"prompt_tokens": 420, "completion_tokens": 61, "cost_usd": 0.0042}
    }
  ],
  "final_metrics": {
    "total_steps": 2,
    "total_cost_usd": 0.0042,
    "total_prompt_tokens": 420,
    "total_completion_tokens": 61
  }
}

Evaluation-Modi

Agentic Evaluations unterstützen zwei komplementäre Pfade, um Ratings zu erzeugen.

Automatische Auswertung (Standard)

Laden Sie Trajectories mit evaluate=True hoch (Default). Für jedes Criterion im Criterion Set liest elluminate die Trajectory und erzeugt ein YES/NO-Rating mit Reasoning. Nutzen Sie diesen Pfad, wenn elluminate die Bewertung übernehmen soll.

Vorberechnete Ratings

Hängen Sie eigene Ratings pro Trial über criterion_ratings an und setzen Sie evaluate=False. elluminate speichert sie unverändert. Nutzen Sie diesen Pfad, wenn Sie bereits einen externen Judge betreiben oder historische Runs importieren wollen, ohne sie neu zu bewerten. Labels, die noch nicht im Criterion Set des Experiments existieren, werden beim Upload angelegt — so kann dieser Pfad auch neue Criteria seed-en (nützlich für Backfills historischer Runs, deren Criteria nicht vorab deklariert waren).

Modi mischen

Sie können auch evaluate=True lassen und gleichzeitig criterion_ratings liefern. Vorberechnete Ratings werden direkt gespeichert, und elluminate bewertet alle Criteria, die nicht vorab bewertet wurden. Praktisch für partielle Imports.

Einschränkungen

  • Keine Execution: elluminate führt Ihren Agenten nicht aus. Jeder Runner arbeitet außerhalb von elluminate; dieses Guide deckt das Hochladen der Ergebnisse ab.
  • Schema-Version: Trajectories müssen schema_version auf ATIF-v1.* setzen. Unbekannte Versionen werden abgelehnt.

SDK Reference

Das folgende Script deckt den kompletten End-to-End-Flow ab: AGENTIC Collection, AGENTIC Experiment, Konvertierung des Runner-Outputs und Upload mit eingereihter Auswertung durch elluminate. Das Script ist idempotent — Collections, Criterion Sets und Experimente werden über Läufe hinweg wiederverwendet, und Uploads werden übersprungen, wenn ein Experiment bereits Responses enthält.

Das Beispiel ausführen

Hinterlegen Sie Ihren API-Key (in der elluminate UI unter Project → Keys anlegen) entweder als Environment Variable oder in einer .env-Datei neben dem Script; das Beispiel ruft load_dotenv() auf:

# Variante 1: Shell
export ELLUMINATE_API_KEY=<your-key>
# optional, falls elluminate auf einem Non-Default-Host läuft:
# export ELLUMINATE_BASE_URL=https://your-instance.example.com

# Variante 2: .env in elluminate_sdk/examples/
echo "ELLUMINATE_API_KEY=<your-key>" > elluminate_sdk/examples/.env

# Ausführen
uv run --directory elluminate_sdk python examples/example_harbor_agentic_upload.py
"""Harbor-based Agentic Evaluation: end-to-end upload example.

This example shows the full workflow for evaluating an agent that was run
externally with Harbor (or any other agent framework), and uploading the
results, including ATIF trajectories, to elluminate for inspection and
automatic per-criterion evaluation.

Workflow:

1. Create an AGENTIC collection whose rows are the agent's tasks. The task
   description lives in a single RAW_INPUT column; no prompt template is
   needed because AGENTIC experiments do not auto-generate responses.
2. Create a criterion set describing what counts as success.
3. Create an AGENTIC experiment (no auto-generation; results are uploaded).
4. Run the agent externally (Harbor CLI, LangChain, CrewAI, custom code).
5. Read Harbor's per-task output and convert it into `AgentTrialResult` objects.
6. Upload via `experiment.upload_agent_results(...)`.
7. The backend stores the trajectories and, when `evaluate=True` and
   trajectories are present, elluminate automatically rates each criterion
   against the trajectory.

The script is idempotent: collections, criterion sets, and experiments are
reused across runs; uploads are skipped when an experiment already contains
responses.

For a self-contained demo this script uses a small in-memory stand-in for
Harbor's output. In a real run you would point `load_harbor_run()` at the
directory Harbor writes to (`~/.harbor/runs/<run_name>/tasks/<task>/...`).
"""

from typing import Any

from dotenv import load_dotenv
from elluminate import AgentTrialResult, Client, CriterionRatingIn
from elluminate.schemas import CollectionColumn, ColumnTypeEnum
from elluminate.schemas.criterion import CriterionIn
from elluminate.schemas.experiments import Experiment

load_dotenv(override=True)

client = Client()  # (1)!
llm_config = client.get_llm_config(name="Claude Sonnet 4.6")

# Mock "Harbor output"; in a real integration this is read from disk.  # (2)!
# Each entry is what a Harbor run produces per task: a short task identifier,
# the instruction text, final messages, aggregate metrics, and an ATIF
# trajectory describing every step the agent took.
HARBOR_RUN: list[dict[str, Any]] = [
    {
        "task_name": "write-hello-world",
        "instruction": "Write a Python hello world script to hello.py",
        "reward": 1.0,
        "steps": 2,
        "cost_usd": 0.0042,
        "input_tokens": 420,
        "output_tokens": 61,
        "duration_seconds": 3.2,
        "messages": [
            {"role": "user", "content": "Write a Python hello world script to hello.py"},
            {"role": "assistant", "content": "Wrote hello.py: print('Hello, World!')"},
        ],
        "trajectory": {
            "schema_version": "ATIF-v1.0",
            "session_id": "harbor-run-001/write-hello-world",
            "agent": {
                "name": "harbor-demo-agent",
                "version": "0.1.0",
                "model_name": "claude-sonnet-4-6",
            },
            "steps": [
                {
                    "step_id": 1,
                    "source": "user",
                    "message": "Write a Python hello world script to hello.py",
                },
                {
                    "step_id": 2,
                    "source": "agent",
                    "message": "Writing hello.py.",
                    "tool_calls": [
                        {
                            "tool_call_id": "tc_1",
                            "function_name": "write_file",
                            "arguments": {"path": "hello.py", "content": "print('Hello, World!')"},
                        }
                    ],
                    "observation": {
                        "results": [{"source_call_id": "tc_1", "content": "wrote 22 bytes"}],
                    },
                    "metrics": {"prompt_tokens": 420, "completion_tokens": 61, "cost_usd": 0.0042},
                },
            ],
            "final_metrics": {
                "total_steps": 2,
                "total_cost_usd": 0.0042,
                "total_prompt_tokens": 420,
                "total_completion_tokens": 61,
            },
        },
    },
    {
        "task_name": "reverse-string-function",
        "instruction": "Create a Python function that reverses a string in reverse.py",
        "reward": 0.5,
        "steps": 2,
        "cost_usd": 0.0031,
        "input_tokens": 310,
        "output_tokens": 42,
        "duration_seconds": 2.1,
        "messages": [
            {"role": "user", "content": "Create a Python function that reverses a string in reverse.py"},
            {"role": "assistant", "content": "Wrote reverse.py with a one-line slice-based reverse."},
        ],
        "trajectory": {
            "schema_version": "ATIF-v1.0",
            "session_id": "harbor-run-001/reverse-string-function",
            "agent": {
                "name": "harbor-demo-agent",
                "version": "0.1.0",
                "model_name": "claude-sonnet-4-6",
            },
            "steps": [
                {
                    "step_id": 1,
                    "source": "user",
                    "message": "Create a Python function that reverses a string in reverse.py",
                },
                {
                    "step_id": 2,
                    "source": "agent",
                    "message": "Writing reverse.py.",
                    "tool_calls": [
                        {
                            "tool_call_id": "tc_1",
                            "function_name": "write_file",
                            "arguments": {
                                "path": "reverse.py",
                                "content": "def reverse(s: str) -> str:\n    return s[::-1]\n",
                            },
                        }
                    ],
                    "observation": {
                        "results": [{"source_call_id": "tc_1", "content": "wrote 42 bytes"}],
                    },
                    "metrics": {"prompt_tokens": 310, "completion_tokens": 42, "cost_usd": 0.0031},
                },
            ],
            "final_metrics": {
                "total_steps": 2,
                "total_cost_usd": 0.0031,
                "total_prompt_tokens": 310,
                "total_completion_tokens": 42,
            },
        },
    },
]


def harbor_to_agent_trial(task_output: dict[str, Any]) -> AgentTrialResult:  # (3)!
    """Map one Harbor per-task output dict to an `AgentTrialResult`.

    `task_name` on `AgentTrialResult` is what elluminate matches against the
    collection's `task_name_column` value, so here we set it to the full
    instruction text (which is also what the `task` column row holds).
    """
    return AgentTrialResult(
        task_name=task_output["instruction"],
        messages=task_output["messages"],
        reward=task_output["reward"],
        steps=task_output["steps"],
        cost_usd=task_output["cost_usd"],
        input_tokens=task_output["input_tokens"],
        output_tokens=task_output["output_tokens"],
        duration_seconds=task_output["duration_seconds"],
        trajectory=task_output["trajectory"],
        metadata={"run_name": "harbor-run-001", "task_id": task_output["task_name"]},
    )


# Step 1: AGENTIC collection with a single RAW_INPUT `task` column.  # (4)!
# No prompt template is required because AGENTIC experiments never
# auto-generate; responses are supplied by `upload_agent_results`.
collection, _ = client.get_or_create_collection(
    name="Harbor Demo Tasks",
    defaults={
        "collection_type": "AGENTIC",
        "columns": [CollectionColumn(name="task", column_type=ColumnTypeEnum.RAW_INPUT)],
        "variables": [{"task": h["instruction"]} for h in HARBOR_RUN],
    },
)

# Step 2: criterion set defining what success looks like for these tasks.  # (5)!
# Labels let pre-computed ratings (Option B) refer to criteria unambiguously.
criterion_set, _ = client.get_or_create_criterion_set(
    name="Harbor Demo Criteria",
    defaults={
        "criteria": [
            CriterionIn(
                criterion_str="Did the agent correctly complete the requested task?",
                label="task-complete",
            ),
            CriterionIn(
                criterion_str="Did the agent use tools appropriately?",
                label="uses-tools",
            ),
            CriterionIn(
                criterion_str="Is the agent's final output correct?",
                label="output-correct",
            ),
        ],
    },
)


def get_or_create_agentic_experiment(name: str, description: str) -> tuple[Experiment, bool]:  # (6)!
    """Return an AGENTIC experiment, creating it if missing.

    Also reports whether the experiment already has uploaded responses so the
    caller can skip a redundant upload on re-runs (avoids epoch conflicts).
    """
    try:
        experiment = client.get_experiment(name=name, fetch_responses=False)
        populated = experiment.results is not None and experiment.results.completed_epochs > 0
        return experiment, populated
    except ValueError:
        experiment = client.create_experiment(
            name=name,
            collection=collection,
            prompt_template=None,
            criterion_set=criterion_set,
            description=description,
            evaluation_mode="AGENTIC",
            llm_config=llm_config,
        )
        return experiment, False


# Step 3: AGENTIC experiment. No auto-generation; results come from Harbor.  # (7)!
experiment, experiment_populated = get_or_create_agentic_experiment(
    "Harbor Demo — Agent Run",
    "Harbor-run coding agent with ATIF trajectories.",
)
print(f"Experiment: {experiment.name} (id={experiment.id})")

# Step 4: Convert Harbor output to `AgentTrialResult` objects.  # (8)!
results = [harbor_to_agent_trial(task_output) for task_output in HARBOR_RUN]

# Step 5 (option A): upload with `evaluate=True`. elluminate rates every  # (9)!
# criterion against the trajectory and fills in per-criterion ratings.
if experiment_populated:
    print("Experiment already has responses; skipping Option A upload.")
else:
    upload = experiment.upload_agent_results(
        results=results,
        task_name_column="task",
        evaluate=True,
    )
    print(
        f"Uploaded: {upload.created_responses} responses, "
        f"{upload.created_ratings} ratings, "
        f"{upload.pending_evaluations} pending trace evaluations"
    )
    if upload.errors:
        print(f"Errors: {upload.errors}")

# Step 6 (option B): skip elluminate's evaluation and upload pre-computed  # (10)!
# ratings you already have (e.g. from your own judge). `evaluate=False`
# prevents the backend from queuing its own evaluation. A fresh experiment
# keeps the two demo paths independent.
precomputed_experiment, precomputed_populated = get_or_create_agentic_experiment(
    "Harbor Demo — Pre-computed Ratings",
    "Harbor-run coding agent with externally computed ratings.",
)
precomputed_results = [
    AgentTrialResult(
        task_name=HARBOR_RUN[0]["instruction"],
        messages=HARBOR_RUN[0]["messages"],
        reward=1.0,
        trajectory=HARBOR_RUN[0]["trajectory"],
        criterion_ratings=[
            CriterionRatingIn(
                label="task-complete",
                rating="YES",
                reasoning="External judge verified the agent produced the requested file.",
            ),
        ],
    ),
]
if precomputed_populated:
    print("Pre-computed experiment already has responses; skipping Option B upload.")
else:
    precomputed_upload = precomputed_experiment.upload_agent_results(
        results=precomputed_results,
        task_name_column="task",
        evaluate=False,
    )
    print(
        f"Pre-computed: {precomputed_upload.created_responses} responses, "
        f"{precomputed_upload.created_ratings} ratings, "
        f"{precomputed_upload.pending_evaluations} pending trace evaluations"
    )

# Step 7: Verify the trajectories are queryable from the SDK.  # (11)!
experiment.fetch_responses()
for resp in experiment.responses():
    task = resp.prompt.template_variables.input_values.get("task", "?")
    steps = len(resp.trajectory["steps"]) if resp.trajectory else 0
    print(f"  [{task[:50]}] trajectory_steps={steps}")
  1. SDK-Client initialisieren (nutzt ELLUMINATE_API_KEY) und einen LLMConfig fürs Experiment auswählen (nur Metadaten; AGENTIC Experimente rufen ihn nie auf).
  2. Stand-in für den On-Disk-Output Ihres Runners. Ersetzen Sie das durch Code, der Harbors task_result.json + trajectory.json pro Task einliest.
  3. Einen Runner-Output in ein AgentTrialResult übersetzen. Das ist der einzige integrationsspezifische Code, den Sie brauchen; task_name muss exakt dem Wert in der Task-Column der Collection entsprechen.
  4. Eine AGENTIC Collection mit einer einzelnen task-Column (RAW_INPUT) anlegen, eine Zeile pro Task. Kein Prompt Template erforderlich.
  5. Das Criterion Set erstellen, das Erfolg definiert. Labels sind explizit, damit vorberechnete Ratings (Option B) sie eindeutig referenzieren können.
  6. Idempotenter Helper, der ein AGENTIC Experiment zurückgibt und meldet, ob es bereits hochgeladene Responses enthält.
  7. Das Hauptexperiment per Get-or-Create holen. evaluation_mode="AGENTIC" deaktiviert die Auto-Generation; Responses werden per Upload geliefert.
  8. Jeden Runner-Output in ein AgentTrialResult konvertieren.
  9. Option A, automatische Auswertung: Upload mit evaluate=True, damit elluminate jedes Criterion gegen die Trajectory bewertet. Wird übersprungen, wenn das Experiment bei einem erneuten Lauf bereits Responses hat.
  10. Option B, vorberechnete Ratings: criterion_ratings liefern und evaluate=False setzen, um die Ergebnisse Ihres eigenen Judges unverändert zu speichern. Läuft gegen ein separates Experiment, damit die beiden Pfade unabhängig bleiben.
  11. Das Experiment erneut laden und prüfen, dass die Trajectories über das SDK abrufbar sind.