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. elluminate ist runtime-agnostisch: Sie führen den Agenten dort aus, wo er ohnehin läuft — in eigenem Code, einem Framework wie LangChain, CrewAI oder AutoGen oder einem Harness wie Harbor — übersetzen seinen Output in das ATIF-Trajectory-Format 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.

Was Agentic Evaluation ist

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 — in eigenem Code, einem Framework oder einem Harness wie Harbor — und elluminate speichert die Trial-Ergebnisse, rendert den Agent Trace und bewertet optional jedes Criterion gegen die Trajectory.

Kernkonzepte

  • Task — eine Arbeitseinheit für Ihren Agenten, identifiziert durch einen eindeutigen Task-Namen.
  • Collection — Ihre Task-Menge: eine Zeile pro Task, mit einer task-Column (Name) und einer optionalen instruction-Column (Input).
  • Trajectory (Trace) — die vollständige Schritt-für-Schritt-Aufzeichnung eines Agent-Runs: jede Message, jeder Tool Call und jede Observation, im ATIF-Format.
  • Criterion — eine binäre YES/NO-Qualitätsfrage. elluminates LLM-Judge beantwortet jedes Criterion gegen die Trajectory — dieselbe Bewertung wie bei einem Standard-Experiment.
  • Experiment — ein Evaluation-Run: verbindet eine Collection und ein Criterion Set und hält die hochgeladenen Trajectories mit ihren Ratings und aggregierten Metriken.

Das Trajectory-Format

elluminate konsumiert genau einen Input von Ihrem Runner: eine Agent-Trajectory im ATIF-Format (Agent Trajectory Interchange Format), einer offenen Spezifikation, definiert von Harbor. Egal welchen Runner Sie nutzen, Sie übersetzen seinen Output nach ATIF und laden ihn hoch — entweder im Browser oder per SDK. Es gibt bislang keinen nativen Importer für LangChain, CrewAI, AutoGen oder andere Frameworks. Das vollständige Schema und ein minimales Beispiel finden Sie im Abschnitt ATIF-Trajectory-Format.

UI-Walkthrough

Agentic Collection erstellen
Collections → New collection, mit ausgewähltem Agentic-Collection-Typ.
  1. Agentic Collection erstellen. Unter Collections → New collection den Typ Agentic wählen. Sie ist mit zwei Columns vorkonfiguriert; diese konventionellen Namen sollten beibehalten werden: task — ein kurzer, eindeutiger Name pro Task (eine Zeile pro Task) — und instruction — der Input, den der Agent für diesen Task erhalten hat (bei einem Chat-Agenten die erste User-Nachricht). instruction ist optional: Wenn Sie eine außerhalb von elluminate erzeugte Trajectory hochladen, ist der Input bereits Teil der Trajectory, die Column kann also leer bleiben. Kein Prompt Template nötig, und Agentic Experimente generieren nie automatisch Responses.
  2. Criterion Set erstellen. Unter Criteria Library → New Criterion Set einen Namen vergeben, dann das Set öffnen und über Add New Criterion binäre YES/NO-Fragen hinzufügen, die elluminate gegen die Trajectory beantwortet (z.B. "Hat der Agent die richtige Datei bearbeitet?"). Um ein Criterion an die Ground-Truth-Daten einer Collection zu binden, wählen Sie im Set Use variables from \<Ihre Collection> und fügen dann über das Dropdown Add Variable einen {{column_name}}-Platzhalter für eine beliebige Column dieser Collection ein (z.B. {{expected_legal_area}}) — das Criterion wird dann gegen den Wert dieser Column geprüft.
  3. Agentic Experiment erstellen. Auf Experiments → New den Typ Agentic wählen, dann Collection und Criterion Set auswählen. Ein Prompt Template wird nicht benötigt — der Wizard überspringt diesen Schritt für Agentic Experimente. Der Experiment-Name wird aus der Collection vorausgefüllt — beliebig umbenennbar.
  4. Agent extern ausführen (eigener Code, ein Framework oder ein Harness wie Harbor) und ein Ergebnis pro Task einsammeln, inklusive einer ATIF-Trajectory.
  5. Ergebnisse hochladen — entweder direkt im Browser oder per SDK. Wenn Trajectories vorhanden sind, bewertet elluminate automatisch jedes Criterion gegen die Trajectory.
  6. Ergebnisse ansehen in der UI (siehe unten).
Agentic Experiment erstellen
Experiments → New: Erstellen eines Agentic Experiments.

Ergebnisse in der UI ansehen

Agentic Experiment Übersicht
Die Experiment-Seite, Tab Overview.

Die Experiment-Seite hat drei Tabs:

  • Overview — aggregierte Metriken (Overall Score; sowie Cost pro Task, durchschnittliche Dauer und Tokens, sofern Ihr hochgeladener Run sie enthält), ein Criteria Performance-Chart (Pass-Rate pro Criterion) und eine KI-generierte Experiment Summary (Executive Summary, plus Abschnitte wie Strengths, Weaknesses, Recommendations und Notable Samples — jeweils nur dann, wenn die Summary dazu Inhalt hat). Die Summary wird erst erzeugt, nachdem jede Trace annotiert wurde — sie kann also einige Minuten brauchen.
  • Sample Navigator — Detail pro Sample: die Phasen-Timeline (Gantt-artige Ansicht der Agent-Schritte) und der Agent Trace (jede Message, jeder Tool Call, jede Observation), zusammen mit den YES/NO-Ratings pro Criterion samt Reasoning. Delegiert ein Run an Sub-Agents, wird jede Sub-Agent-Trajectory verschachtelt und ein-/ausklappbar unter dem auslösenden Schritt gerendert, sodass Multi-Agent-Runs vollständig inspizierbar bleiben.
  • Responses Overview — alle Responses auf einen Blick.
Agent Trace und Phasen-Timeline
Die Experiment-Seite, Sample Navigator — Phasen-Timeline und Agent Trace für ein Sample.

Ergebnisse in der UI hochladen

Sobald Ihre Agentic Collection (die Task-Zeilen) und Ihr Agentic Experiment existieren, können Sie die Ergebnisse direkt im Browser hochladen — ganz ohne SDK. Öffnen Sie das Experiment und klicken Sie auf Upload results (wird auch im Empty State angeboten, solange noch keine Ergebnisse vorliegen).

Im Dialog:

  1. Ergebnisdatei ablegen — eine .json-Datei mit einem Array von Trial-Ergebnis-Objekten oder eine .jsonl-Datei mit einem Objekt pro Zeile.
  2. Task-Name-Column wählen — die Collection-Column, gegen deren Werte der task_name jedes Trials abgeglichen wird (üblicherweise task).
  3. "Evaluate after upload" umschalten (standardmäßig an) — wenn aktiv, bewertet elluminate nach dem Upload jedes Criterion gegen die hochgeladene Trajectory.
  4. Epoch setzen (Standard 1) — erhöhen, um einen weiteren Lauf derselben Tasks in dasselbe Experiment zu laden.

Format der Upload-Datei

Jedes Objekt in der Datei spiegelt das SDK-AgentTrialResult:

  • task_name (Pflicht) — muss exakt einem Wert in der Task-Name-Column der Collection entsprechen.
  • trajectory — der ATIF-Trajectory-Dict (schema_version muss ATIF-v1.x entsprechen, plus agent, steps und final_metrics).
  • optional: reward, cost_usd, steps, messages, criterion_ratings.

Eine minimale .json-Datei mit einem einzelnen Trial (ein Array mit einem Objekt):

[
  {
    "task_name": "write-hello-world",
    "reward": 1.0,
    "cost_usd": 0.0042,
    "trajectory": {
      "schema_version": "ATIF-v1.0",
      "session_id": "run-001/write-hello-world",
      "agent": {
        "name": "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
      }
    }
  }
]

Für eine .jsonl-Datei schreiben Sie dieselben Objekte eines pro Zeile (kein umschließendes Array, keine Kommas).

Partieller Erfolg und asynchrone Auswertung

Trials werden unabhängig verarbeitet: Eine fehlerhafte Trajectory wird nur für das betroffene Trial verworfen, und der Upload meldet Fehler pro Trial, sodass die übrigen trotzdem durchlaufen. Ist Evaluate after upload aktiv, läuft die Bewertung asynchron — die Ergebnisse erscheinen auf der Experiment-Seite, sobald sie fertig sind.

SDK-Walkthrough

Das folgende Script deckt den kompletten End-to-End-Flow ab: eine Agentic Collection, ein 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
(`HARBOR_RUN`) for the runner's output. In a real run you would replace it
with your own loader that reads your runner's per-task output from disk — for
Harbor, the run directory at `~/.harbor/runs/<run_name>/tasks/<task>/...`.
"""

from typing import Any

from dotenv import load_dotenv
from elluminate import AgentTrialResult, Client
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 are explicit identifiers for each criterion.
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: 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 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: Verify the trajectories are queryable from the SDK.  # (10)!
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 explizite Identifier für jedes Criterion.
  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. 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. Das Experiment erneut laden und prüfen, dass die Trajectories über das SDK abrufbar sind.

AgentTrialResult-Felder

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).

ATIF-Trajectory-Format

Trajectories verwenden das Agent Trajectory Interchange Format (ATIF), eine offene Trajectory-Spezifikation, definiert von Harbor.

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
  }
}