Skip to content

Endpoint records to traces

The endpoint’s records become a traces object, the input of Test set from traces. There are two routes:

Directly from the endpoint Download, convert, upload
How the platform reads the endpoint’s records itself you download the records, convert them to conversations, and upload the file
Delay a call becomes available about 24 hours after it was made none: a call can be downloaded right after it was made
Size no limit: every record the endpoint holds a few thousand traces at most
Filtering every record goes in; trace processing skips failed calls you choose: by status, by which model answered, by time window

Use the direct route for the full trace set. Use the download route when you need the traces now, when there are only a few thousand, or when the records must be filtered first, for example to keep only the fallback’s answers once a trained model serves the endpoint.

Both routes read a config.yaml and a job_description.json from a directory, so write them first (Trace inputs covers both files).

Create the traces object from the endpoint support-yeOdAS and the files in ./traces-input:

distil traces create-from-inference-endpoint --data ./traces-input support-yeOdAS

The command prints Prepared traces created. ID: <traces-id>. traces-input/ holds config.yaml, job_description.json and optionally a curated test.jsonl. It must not hold a traces.jsonl: the traces come from the endpoint, and the command refuses a directory that has one. Set the observation format of the endpoint’s records in the config:

trace_processing:
  observation_format: langfuse

--config, --job-description and --test replace --data with one path each.

With no flags, the command downloads the newest 1,000 records from the last day:

distil inference-endpoint download-traces support-yeOdAS

It writes one record per line to support-yeOdAS-traces.jsonl. --from-start-time and --to-start-time set the window, as an ISO 8601 date or date-time. Within the window, --count (-c) caps the records (default 1000) and --all takes every one. --file-name names the output file. With no records in the window, no file is written. To take every record since September 1:

distil inference-endpoint download-traces --from-start-time 2026-09-01 --all support-yeOdAS
Field What it holds
input The request body, as a JSON string: model, messages, and tools when the caller sent any
output The chat completions response, as a JSON string. The reply is choices[0].message
metadata.status The HTTP status the caller received
metadata.source Which model answered: fallback, or primary when a trained model serves the endpoint
start_time, latency When the call started, and how long it took in seconds

The rest is identifiers. Read one record before converting.

Per record: parse input and output, append the reply as the assistant turn to the request’s messages, carry tools across when present, and write one {"messages": [...]} object. Skip the records that should not become training data:

  • Failed calls. Keep metadata.status of 200 only.
  • Empty replies. Skip a response with neither content nor tool_calls, so no conversation ends on the user’s turn.
  • The wrong model’s answers, once a trained model serves the endpoint. Filter on metadata.source for the answers the next iteration should learn from.

This script applies the first two filters and writes the conversations from support-yeOdAS-traces.jsonl to traces-input/traces.jsonl:

import json


def convert(record):
    if record["metadata"].get("status") != 200:
        return None
    request = json.loads(record["input"])
    reply = json.loads(record["output"])["choices"][0]["message"]
    if not reply.get("content") and not reply.get("tool_calls"):
        return None
    assistant = {"role": "assistant", "content": reply.get("content") or ""}
    if reply.get("tool_calls"):
        assistant["tool_calls"] = reply["tool_calls"]
    converted = {"messages": [*request["messages"], assistant]}
    if request.get("tools"):
        converted["tools"] = request["tools"]
    return converted


with open("support-yeOdAS-traces.jsonl") as src, open("traces-input/traces.jsonl", "w") as dst:
    for line in src:
        converted = convert(json.loads(line))
        if converted is not None:
            dst.write(json.dumps(converted, ensure_ascii=False) + "\n")

The reply can carry a reasoning field next to content when the fallback is a reasoning model. The conversion keeps content, which is what your application used. Check the kept count against the download and read the first few lines.

The result is a traces.jsonl in the default openai_messages format. Put it next to config.yaml and job_description.json and upload the directory:

distil traces upload --data ./traces-input

The command prints Prepared traces created. ID: <traces-id>.

Build the test set from the traces.