Importing existing traffic
If your LLM calls are already recorded somewhere else, you do not have to wait
for new traffic to see anything in Metergraph. A separate command line tool,
metergraphrelay, reads each recorded call from the tool that holds it,
reshapes it into a Metergraph row, and uploads it through the same
ingest API the SDK uses. Your application is
not touched.
Run it once to load history, or run it hourly to keep Metergraph level with the other tool.
Import or instrument
Section titled “Import or instrument”Importing is the faster path to a populated dashboard. Instrumenting is the one that captures more.
| Import | Install the SDK | |
|---|---|---|
| Code change | None | One init() call |
| History | Everything the other tool kept | New calls only |
| Function attribution | No | Yes |
| Route names | Only if the source recorded one | Yes, inferred or declared |
| Streaming and time to first token | No | Yes |
| Recorded outcomes | No | Yes, via record_outcome() |
| Content controls | None on most paths | Per field, per call |
The two are not exclusive. A common shape is to import the last few months from your existing tool, install the SDK for everything from today, and let the dashboard span both.
What an import can and cannot recover
Section titled “What an import can and cannot recover”An imported row is only ever as good as what the source tool stored. The relay never invents a value it did not read.
What comes across on most paths. Timestamp, provider, model, input and output token counts, status, error type, the source’s own request id, trace and span ids, and the prompt and response text the source held. Cache and reasoning token buckets come across wherever the source recorded them.
What does not come across, on any path:
- Function attribution. The SDK walks the Python stack at capture time
and records the module and function that made the call
(
metergraph/_capture.py:621-652). No source tool stores that, so an imported row has nofuncand nomodule, and the parts of the dashboard that group spend by function will be empty for imported traffic. - Route names, unless the source recorded one. Every provider page below
says what the relay uses as the route and what it falls back to. When the
source carried nothing usable you get a bare SDK method name, a model class
name, or a literal
<provider>/backfill. Pass--routeto name the workflow yourself. See Name a route. - Streaming detail. No
streamflag and no time to first token. - Batch metadata, template hashes, and unit counts.
- Recorded outcomes. Outcomes are written by your application through the SDK, so there is nothing in a trace export to import. See Record real outcomes.
- Repository identity, so imported rows do not link back to a commit.
Latency is per source: Braintrust, LangSmith, Phoenix and Portkey all carry it, and Langfuse does not, because the relay reads only the observation’s start time.
Cost is also per source. Where the source computed a cost, that figure is carried across as reported. Where it did not, the row arrives unpriced and Metergraph prices it from its own model catalog at read time.
Supported sources
Section titled “Supported sources”| Where your calls are today | Page | Backfill | Hourly sync |
|---|---|---|---|
| Langfuse Cloud, or self-hosted v4 and newer | Langfuse | yes | yes |
| LangSmith | LangSmith | yes | yes |
| Braintrust | Braintrust | yes | yes |
| Arize Phoenix 13.15 and newer | Phoenix | yes | yes |
| Portkey | Portkey | yes | yes, API mode only |
| OpenAI stored completions | OpenAI | yes | no |
Every path imports model calls only. Chains, tools, retrievers, agents, evals, scores, annotations and feedback are never read.
If you send OpenTelemetry spans today, you may not need an import at all. The
SDK’s span exporter reads the OpenInference and gen_ai.* dialects directly,
so those spans can reach Metergraph live. See OTLP spans.
Shared setup
Section titled “Shared setup”Do this once, then follow the page for your tool.
-
Create an ingest key
Section titled “Create an ingest key”Sign in to Metergraph and open Keys. Copy an ingest key. Read keys and agent keys cannot upload traces and will be refused with a
403. Keep the key in a secret manager. See Manage your workspace. -
Install the relay
Section titled “Install the relay”Any machine that can reach both your source tool and Metergraph will do. It needs Python 3.10 or newer.
pip install metergraphrelay -
Write a
Section titled “Write a .env file”.envfileEvery relay command loads a
.envfrom the directory you run it in. Start with the Metergraph key. Each provider page adds its own lines..env METERGRAPH_APP_TOKEN=<your ingest key># Only for a Metergraph you operate yourself. Leave unset for hosted.# METERGRAPH_INGEST_URL=https://metergraph.internal.example.comCommit a
.env.examplewith the names and no values. Never commit.env.
Two ways to load
Section titled “Two ways to load”Backfill: pull, then push
Section titled “Backfill: pull, then push”pull writes a local JSONL file of Metergraph-shaped rows for the range you
ask for. Read the file, then push uploads it. Use this for a one time load
of history, or any time you want to see the rows before they leave the
machine.
metergraphrelay pull <provider> [selectors] --since 2026-08-01T00:00:00Z --output traces.jsonlmetergraphrelay push traces.jsonl--output defaults to ./traces.jsonl. -n caps the number of rows, not
the number of traces, and defaults to 100 on every provider except OpenAI,
where it is 10.
Scheduled: sync
Section titled “Scheduled: sync”One command pulls the next window and uploads it. Metergraph holds the checkpoint, so the relay keeps no local state and the same cron line is safe to run from more than one machine.
metergraphrelay sync <provider> --source-scope <stable name> --initial-since 2026-08-01T00:00:00Z [selectors]How ongoing sync works
Section titled “How ongoing sync works”Sync is a shipped feature, not a plan. The coordination routes are
POST /v1/import-sync/acquire, POST /v1/import-sync/leases/{id}/renew,
POST /v1/import-sync/leases/{id}/complete,
DELETE /v1/import-sync/leases/{id} and GET /v1/import-sync/state
(app/src/metergraph_app/api/import_sync.py), and the server accepts
portkey, langfuse, braintrust, phoenix and langsmith as sources
(import_identity.py:30).
The contract is the same for every provider:
- The server picks the window. Each run imports one window of at most one
hour, measured from the committed checkpoint
(
import_sync.py:27,MAX_WINDOW_SECONDS = 3600). - Windows overlap by five minutes. Every window after the first reaches
five minutes back behind the checkpoint (
import_sync.py:28), so a row written slightly late at the source is not missed. - Re-read rows are deduplicated. Each synced row carries
import_source,import_source_scopeandimport_event_id. The worker claims those identities before it does any pricing work, and a row whose identity is already claimed by another batch is dropped (import_identity.claim_rows). Overlap never double counts. - The lease lasts fifteen minutes and the relay renews it every five
minutes while a run is in flight (
import_sync.py:29,sync_core.py:17). - The checkpoint advances only on a clean window. A failed upload, a row the relay could not read, or a lost lease all leave the checkpoint where it was, and the same window is retried on the next run.
--source-scope is required, and must never change
Section titled “--source-scope is required, and must never change”It names this stream’s checkpoint on the Metergraph side, and deduplication is keyed on it. Use a plain, permanent label such as the project name. Never derive it from a credential or from the flag list: rotating a key or reordering flags would start a fresh checkpoint and re-import your history. Give each selector set its own scope. Two cron lines sharing one scope share a checkpoint, and each one drops the other’s rows.
--initial-since seeds only the first run
Section titled “--initial-since seeds only the first run”Once a checkpoint exists the server ignores it, so cron can pass it on every
run. Omitting it on a stream’s very first run is a 422.
One cron line per stream
Section titled “One cron line per stream”0 * * * * metergraphrelay sync langfuse --source-scope support-bot-prod --initial-since 2026-08-01T00:00:00+00:00 --trace-name support-bot --tag prodTo work through a backlog, run the same command back to back until it reports that it is caught up.
Exit codes
Section titled “Exit codes”0 means the window completed, the server was already caught up, or another
run holds the lease. Anything else prints an Error: line to stderr naming
the phase that failed, releases the lease, and leaves the window to be
retried. Two cases are worth recognising:
- “could not be normalized” means the source returned a row the relay
could not read. The window stays pending so nothing is lost quietly. Fix
the row at the source, or pass
--allow-skippedto advance past it. Do that deliberately: once the checkpoint moves, a skipped row is gone. - A
422on acquire means either the Metergraph server predates support for that source, or this is the stream’s first run and--initial-sincewas omitted.
Verify the first import
Section titled “Verify the first import”Open Traces in Metergraph. Imported calls show the provider, model, tokens, status and route, carrying the source tool’s original timestamp. Three things to check on the first rows:
- The route is what you expect. A value ending in
/backfill, or a bare SDK method or model class name, means the source carried no workflow name.--routeis the fix. input_tokensis the whole prompt. On a cached call it should be larger than the cache read count, not smaller.- Failed calls are present, with their token counts. A failed call still cost its input tokens, and the import keeps that.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause | What to do |
|---|---|---|
401 or 403 from Metergraph | The token is not an ingest key | Mint an ingest key on Keys |
402 from Metergraph | The monthly call allowance is spent | See Limits and allowances |
| A pull returns 0 rows just after traffic was recorded | The source tool has not made the rows readable yet | Wait, then rerun |
| Every sync run reports busy | Another machine’s run holds the lease, or a crash left one behind | Wait up to fifteen minutes for the lease to expire |
| Rows counted twice | pull and sync ran over one range | One method per range |
| Every route is a model or method name | The source carried no workflow name | Pass --route |
Problems specific to one source are on that source’s page.
Requirements
Section titled “Requirements”These paths need a metergraphrelay release with the sync command for your
provider, and a Metergraph server that has applied migration
0073_import_sync_sources. Hosted Metergraph has both. A Metergraph you
operate yourself needs that migration before any sync command will get past
acquire.