Skip to main content

Documentation Index

Fetch the complete documentation index at: https://laminar.sh/docs/llms.txt

Use this file to discover all available pages before exploring further.

Trace metadata applies to the entire trace. Use it for information that’s constant across spans:
  • Environment: production, staging, development
  • Region: us-west, eu-central
  • Deployment: version numbers, feature flags, A/B test variants
  • Request context: correlation IDs, upstream service info
  • Post-hoc signals: quality scores, reviewer notes, edit counts you compute after the trace ends

Adding metadata while the trace is running

Set metadata inside an active span context (for example, inside observe() or an @observed function). If you call it outside any span context, it won’t attach to anything.
import { Laminar, observe } from '@lmnr-ai/lmnr';

await observe({ name: 'processRequest' }, async () => {
  Laminar.setTraceMetadata({
    environment: 'production',
    featureFlag: 'new-algorithm-v2',
    region: 'us-west',
  });
});
See also: Laminar.setTraceMetadata and observe(..., { metadata })

Adding metadata after the trace ends

Some metadata only exists once the trace is over: the user accepted or edited the model’s suggestion, an offline scorer ran, a human reviewer triaged the run. Use LaminarClient.traces.pushMetadata to attach those signals to a finished trace by trace id. Capture the trace id while the trace is running, store it alongside whatever produces the signal (a database row, a queue message, a session id), then push the metadata once you have it. No need to copy a trace id out of the Laminar UI.
import { Laminar, LaminarClient, observe } from '@lmnr-ai/lmnr';

const client = new LaminarClient();

// 1. Capture the traceId while the trace is running.
const traceId = await observe({ name: 'chat_completion' }, async () => {
  // ... your work: LLM calls, tool calls, etc. ...
  return Laminar.getTraceId();
});

// Make sure the trace has finished before pushing metadata to it.
await Laminar.flush();

// 2. Push metadata. Possibly seconds, minutes, or days later.
if (traceId) {
  await client.traces.pushMetadata(traceId, {
    score: 0.85,
    reviewer: 'alice',
    editsMade: 2,
    needsReview: false,
  });
}
See also: client.traces.pushMetadata(traceId, metadata).
A typical flow: a user submits feedback through your app long after the original request ran, a backend job scores responses overnight, an evaluator pipeline grades a batch of runs and writes results back per trace. In each case you stored the trace id when the trace ran, and you push the score back when you have it. pushMetadata shallow-merges the patch into the trace’s existing metadata: top-level keys you pass overwrite existing ones, keys you don’t pass are left alone. Call it again later to add or replace more fields.
Call Laminar.getTraceId() / Laminar.get_trace_id() inside a span context. Outside, it returns null / None. Reading the trace id at the very top of an observe(...) block is the canonical place: the trace exists by then and you can return it from the block.
The trace must already exist in your project before the patch is applied. For short-lived scripts, call await Laminar.flush() (TypeScript) or Laminar.flush() (Python) before pushing metadata. If the trace can’t be found at the time the patch is processed (typically because it hasn’t been flushed yet), the call logs a warning and returns without raising.

Wire format

Without the SDK, set one OTel attribute per metadata key on the root span:
lmnr.association.properties.metadata.<key> = <primitive or JSON-stringified value>
Primitives (string, number, boolean, arrays of those) pass through; complex values must be JSON.stringifyd. See the full Span attribute reference for the rest of the keys Laminar reads. To attach metadata to a finished trace without the SDK, POST /v1/traces/metadata with { "traceId": "<uuid>", "metadata": { ... } } and your project API key as a Bearer token. The server shallow-merges the patch into the trace’s existing metadata.

Notes

  • Metadata set during the trace is merged as spans arrive. If multiple spans set the same key, the last span processed wins (order is not guaranteed), so values can change while a trace is still ingesting.
  • Set each metadata key in one place per trace (ideally the root span) and avoid duplicate keys in nested observe(...) calls or AI SDK telemetry.
  • pushMetadata only runs after the trace has ended and merges deterministically with whatever metadata the trace already has.
  • Keep metadata JSON-serializable and avoid sensitive data.

Filtering by metadata

In the Laminar UI, metadata filters currently match exact key-value pairs (for example, region=us-west).
If a trace seems to appear in results while it’s running and then disappears after it finishes, check whether the same metadata key is being set by multiple spans. Because metadata is merged on ingestion, the final value can change as more spans arrive, which can flip whether a trace matches a filter.

Metadata vs Tags

MetadataTags
ScopeEntire traceIndividual spans
FormatKey-value pairsString labels
Best forEnvironment, cohort, request context, post-hoc scoresCategorization, review labels
Add after the factclient.traces.pushMetadataclient.tags.tag

Best Practices

  • Use consistent keys (environment, region, feature_flag).
  • Use environment for prod/staging/dev; add a low-cardinality key like developer or source to separate dev traces in a shared project.
  • Store the trace id alongside the work that will produce post-hoc signals, so the data and the trace stay linked without manual lookups.
  • Keep it lightweight and JSON-serializable.
  • Avoid sensitive data (no PII).