trustworthiness_score between 0 and 1, plus an optional explanation, so you can flag hallucinated or unreliable answers. This guide shows both ways to wire TLM into Arize AX: Flow 1 grades existing Arize AX traces with TLM and writes the scores back via client.spans.update_evaluations(...); Flow 2 uploads a small dataset, runs an Arize AX experiment with a TLM-backed evaluator function, and surfaces the scores in Datasets+Experiments.
Both flows share the same setup. Run the code blocks below in order inside a single Python session — each block builds on imports and variables from earlier ones.
Cleanlab TLM Evaluation Tutorial (Google Colab)
Prerequisites
- Python 3.11+
- An
ARIZE_SPACE_IDandARIZE_API_KEYfrom your Arize AX space settings - An
OPENAI_API_KEYfrom OpenAI Platform (used for the model under trace) - A
CLEANLAB_TLM_API_KEYfrom your Cleanlab account (used for the TLM trustworthiness scores)
Launch Arize AX
If you don’t already have an Arize AX account, sign up at arize.com and grab yourARIZE_SPACE_ID and ARIZE_API_KEY from Settings → Space Settings.
Install
Configure credentials
Define evaluators
The shared setup: a CleanlabTLM client that also returns an explanation for each score, the canonical 2-row hallucination dataset that both flows score, and an Arize SDK client. TLM() reads CLEANLAB_TLM_API_KEY from the environment.
Flow 1 — Evaluate existing traces
Source the spans
Instrument OpenAI with OpenInference, make two calls (each forced to echo a known answer so the trace contains predictable text), then pull the resulting spans back from Arize AX.Run the evaluators
get_trustworthiness_score(...) accepts lists of prompts and responses and scores them in one batched call. Pair each span’s response text (the output column) with the original question rather than the echo instruction the trace sent, so TLM judges the answer on its own merits.
Log evaluations to Arize AX
update_evaluations(...) requires a context.span_id column (which export_to_df already provides) plus the reserved eval.<name>.{score,label,explanation} columns. Each TLM score becomes one row in this DataFrame.
Expected output
Verify in Arize AX
Open the project namedcleanlab-tracing-example-<timestamp> (the value printed above) in your Arize AX space. Each ChatCompletion span now carries a trustworthiness annotation column showing the score, the trustworthy / untrustworthy label, and TLM’s explanation.
Flow 2 — Run an experiment
Create a dataset
The dataset is the same two rows. Thespace= / examples= kwarg names match the v8 SDK exactly (note: not space_id= and not dataframe=).
Define the task
The task function receives the dataset row and returns whatever the experiment should grade. A real workflow would call an LLM here; this passthrough keeps the example deterministic.Wrap the evaluators
Experiment evaluators run inside anasyncio loop, so use async def and TLM’s get_trustworthiness_score_async(...). The sync get_trustworthiness_score(...) drives its own event loop and fails when one is already running. Return an EvaluationResult with score, label, and explanation populated — leaving any of those as None triggers unsupported cast from null to <type>: reserved column cannot be coerced to canonical type at upload time.
Run the experiment
Expected output
Verify in Arize AX
Open the Datasets + Experiments tab in Arize AX. The datasetcleanlab-experiment-example-ds-<timestamp> and the experiment cleanlab-experiment-example-<timestamp> (names printed above) appear with one run per dataset row, each carrying the trustworthiness score and label columns.
Troubleshooting
MissingApiKeyError: No API key provided.TLM()raisedMissingApiKeyErrorat construction. ExportCLEANLAB_TLM_API_KEYin the same shell that runs the script.This event loop is already runningorCannot run the event loop while another loop is runningfrom Flow 2. Your experiment evaluator calls the syncget_trustworthiness_score(...), which drives TLM’s own event loop withrun_until_complete. Experiment evaluators run insideasyncio;await tlm.get_trustworthiness_score_async(...)instead. Flow 1 calls the sync method because it runs outside any loop.KeyError: 'log'when reading the explanation. TheTLMclient was created withoutoptions={"log": ["explanation"]}, so results carry onlytrustworthiness_score. Pass the option when constructingTLM.- Scores near the threshold flip between runs. TLM’s score is continuous. The doc binarizes at
0.5so the printedscorecolumn stays stable; for borderline responses, inspect the rawtrustworthiness_scoreand pick a threshold that fits your use case. column "eval.<name>.label": unsupported cast from null to string: reserved column cannot be coerced to canonical type. Your evaluator returned a bare number or string instead of a fully populatedEvaluationResult(score=..., label=..., explanation=...). Populate all three fields.- Spans never appear after 60s. Span flush + ingest typically takes 5–15s. If the loop times out, check that
ARIZE_SPACE_ID+ARIZE_API_KEYare right and that you’re connecting to the correct region’s OTLP endpoint (otlp.arize.comfor US,otlp.eu.arize.comfor EU). - Experiment re-runs collide. Both names embed
TIMESTAMP = int(time.time())so a single re-run produces unique names. If you re-execute the samecombined.pyquickly, regenerateTIMESTAMPfirst or callarize.experiments.delete(...)/arize.datasets.delete(...)on the prior run’s names.