name: 02b-typescript-tracing
description: >
Use when adding MLflow tracing to a Node/TypeScript GenAI agent. Covers the
official mlflow-tracing and mlflow-openai npm packages: mlflow.init(),
tracedOpenAI, manual mlflow.trace/@mlflow.trace/mlflow.withSpan,
nested spans, sessions/users, and when to choose a custom OTLP fallback.
Foundation Step 2b.
Sibling to the Python tracing skill (Foundation Step 2). Consumes the MLflow
environment from Step 1.
license: Apache-2.0
clients: [ide_cli, genie_code]
bundle_resource: none
deploy_verb: none
deploy_note: "Node/TypeScript tracing via the mlflow-tracing/mlflow-openai npm packages — no deployed resource; identical on both clients. See skills/genie-code-environment."
coverage: full
metadata:
last_verified: "2026-06-05"
volatility: high
upstream_sources: []
author: "prashanth-subrahmanyam"
version: "1.0.0"
domain: "genai-agents"
pipeline_position: "F2b"
consumes: "mlflow_environment"
produces: "ts_tracing_config"
grounded_in: "docs.databricks.com/aws/en/mlflow3/genai/tracing/app-instrumentation/typescript-sdk, docs.databricks.com/aws/en/mlflow3/genai/tracing/app-instrumentation, docs.databricks.com/aws/en/mlflow3/genai/tracing/prod-tracing"
TypeScript / Node tracing with mlflow-tracing
When to Use
Use this skill when:
- Your agent runs in Node.js / TypeScript (Databricks Apps with an AppKit/Express/Hono backend, AppKit-integrated agents, custom Node services).
- You want first-class MLflow trace shapes matching the Python SDK
(same span types, same
gen_ai.*attributes, same UI affordances). - You want automatic tracing for OpenAI-compatible clients (Foundation
Models, Azure OpenAI, etc.) the same way
mlflow.openai.autolog()works in Python.
Pick a custom OTLP path instead only when:
- You already operate an OpenTelemetry collector and want vendor-neutral spans.
- You instrument multiple services and prefer one OTel exporter pipeline.
- The
mlflow-tracingnpm package does not yet cover a hook you need.
In every other Node case, prefer this skill. It is the canonical Node tracing path and matches the Databricks TypeScript SDK guide.
Prerequisites:
- Foundation Step 1 (MLflow environment, tracking URI, auth) — applies
to Node too. See
01-mlflow-genai-foundation. - An MLflow experiment id (Python or REST). The Node SDK does not create experiments — it only writes traces into an existing one.
Install
npm install mlflow-tracing
# Add the OpenAI autolog wrapper if you call OpenAI / Foundation Models / Azure OpenAI directly
npm install mlflow-openai
The mlflow-tracing package is the core SDK (mlflow.init,
mlflow.trace, mlflow.withSpan, mlflow.startSpan,
mlflow.update_current_trace). The mlflow-openai package adds the
tracedOpenAI helper that wraps an OpenAI client so every
chat.completions.create(...) call becomes an auto-traced span.
Pin the version. Like the Python SDK, the npm packages evolve with each MLflow minor release. Pin
mlflow-tracingandmlflow-openaiinpackage.jsonand review release notes when bumping.
Initialize
Call mlflow.init once at process startup, before any LLM call:
import * as mlflow from "mlflow-tracing";
mlflow.init({
trackingUri: "databricks",
experimentId: process.env.MLFLOW_EXPERIMENT_ID!,
});
The SDK reads Databricks credentials from environment variables (same as the Python SDK):
| Variable | Purpose |
|---|---|
DATABRICKS_HOST |
Workspace URL (e.g. https://my-workspace.cloud.databricks.com) |
DATABRICKS_TOKEN |
PAT for the calling identity (user PAT in dev, SP PAT or OAuth in prod) |
MLFLOW_EXPERIMENT_ID |
Experiment id where traces should land |
For Databricks Apps, the host is auto-injected; pass the SP token
(or use the OBO x-forwarded-access-token for per-user attribution if
the SDK supports it in your version). Set MLFLOW_EXPERIMENT_ID in
app.yaml. See references/prod-tracing-deployment.md
for the full env-var matrix.
For AppKit projects, the standard pattern is to lift Databricks env vars from the AppKit runtime and pass them through to your Node server — the same way you already do for the model client.
Automatic tracing — tracedOpenAI
The Node equivalent of mlflow.openai.autolog():
import OpenAI from "openai";
import { tracedOpenAI } from "mlflow-openai";
const client = tracedOpenAI(
new OpenAI({
apiKey: process.env.DATABRICKS_TOKEN, // or per-user OBO token
baseURL: `${process.env.DATABRICKS_HOST}/serving-endpoints`,
}),
);
const resp = await client.chat.completions.create({
model: "databricks-claude-sonnet-4-6",
messages: [{ role: "user", content: "Hello" }],
});
Every chat.completions.create(...) (and responses.create(...),
embeddings, etc.) call now produces an LLM span with token counts,
latency, model id, and request/response messages — no manual
instrumentation.
Use the same client everywhere in your codebase. tracedOpenAI is a
transparent proxy; it has the same TypeScript types as the underlying
OpenAI instance.
Manual tracing
mlflow.trace(fn, options) — wrap a function
The function-wrapping form is the closest equivalent to Python's
@mlflow.trace:
import * as mlflow from "mlflow-tracing";
async function classifyIntent(query: string) {
const resp = await client.chat.completions.create({
model: "databricks-claude-sonnet-4-6",
messages: [{ role: "user", content: query }],
});
return resp.choices[0].message.content;
}
const tracedClassifyIntent = mlflow.trace(classifyIntent, {
name: "classify_intent",
spanType: "AGENT",
});
// use tracedClassifyIntent everywhere instead of classifyIntent
@mlflow.trace decorator (TS 5.0+)
If your project enables TypeScript 5.0 stage-3 decorators, you can decorate class methods directly:
import * as mlflow from "mlflow-tracing";
class SkyloyaltyAgent {
@mlflow.trace({ name: "answer", spanType: "AGENT" })
async answer(question: string) {
return this._planAndExecute(question);
}
@mlflow.trace({ name: "plan_and_execute", spanType: "CHAIN" })
private async _planAndExecute(q: string) {
/* ... */
}
}
Enable stage-3 decorators in tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"experimentalDecorators": false
}
}
mlflow.withSpan(...) — block scope
For inline blocks where wrapping or decorating doesn't fit:
import * as mlflow from "mlflow-tracing";
await mlflow.withSpan(
{ name: "retrieve_context", spanType: "RETRIEVER" },
async (span) => {
span.setInputs({ query });
const docs = await vector.search(query);
span.setOutputs({ docCount: docs.length });
return docs;
},
);
withSpan opens a child of the current trace, runs the callback, and
closes the span on return or throw. Inputs/outputs/attributes can be
set via the span argument.
Explicit startSpan / span.end
For long-running spans that don't fit a single async callback (e.g. a streaming SSE response that finishes incrementally):
const span = mlflow.startSpan({ name: "stream_response", spanType: "LLM" });
span.setInputs({ prompt });
try {
for await (const chunk of stream) {
/* push to client */
}
span.setOutputs({ status: "ok" });
} catch (err) {
span.setOutputs({ status: "error", error: String(err) });
throw err;
} finally {
span.end();
}
Match every startSpan with a span.end() (typically in finally) or
the trace will hang open in the UI.
Nested spans
Nested spans work the same as Python: any span opened inside another becomes a child:
const computeArea = mlflow.trace(
async (radius: number) => {
return mlflow.withSpan({ name: "square", spanType: "FUNCTION" }, () => {
return Math.PI * radius * radius;
});
},
{ name: "compute_area", spanType: "FUNCTION" },
);
await computeArea(2);
// Trace tree:
// compute_area (FUNCTION)
// └─ square (FUNCTION)
This is the canonical way to compose your agent: one @mlflow.trace
parent per logical step, with auto-traced LLM/tool calls as children.
Sessions and users
Group conversational turns under one session and attribute each turn to a user by tagging the current trace:
import * as mlflow from "mlflow-tracing";
mlflow.update_current_trace({
session_id: req.headers["x-session-id"] as string,
user_id: resolvedUserId,
});
In the MLflow UI, traces grouped by session.id show as a single
conversation; user.id enables per-user filtering and feedback
attribution. The TS SDK's session_id / user_id shorthand keys map
to the same reserved MLflow metadata fields (mlflow.trace.session
/ mlflow.trace.user) that Python writes via
mlflow.update_current_trace(metadata=...).
Call this inside an active trace (typically in your /chat route
handler, after you've opened the request span). The same pattern, plus
the auto-populated environment metadata (mlflow.source.type override
from APP_ENVIRONMENT, agent_version, deployment_id, etc.), is
documented language-agnostically in
F2c — Trace context and environments.
Pairing with end-user feedback
End-user thumbs-up/down lives in sdlc/04c-end-user-feedback.
The Node side returns the trace id to the frontend so feedback can
attach to the right trace:
import * as mlflow from "mlflow-tracing";
app.post("/chat", async (req, res) => {
const result = await tracedAnswer(req.body.question);
// Read the trace id of the just-finished root span
const traceId = mlflow.getCurrentTraceId();
res.json({ answer: result, traceId });
});
The frontend echoes traceId back on the /feedback POST. The Python
contract in 04c-end-user-feedback
calls mlflow.log_feedback(trace_id=...) to attach the assessment.
You can also use client_request_id for correlation if you need the
frontend to issue a stable id before the backend opens the trace — see
04c for that pattern.
Relation to the OTLP fallback path
| Aspect | mlflow-tracing SDK (this skill, default) |
Custom OTLP fallback |
|---|---|---|
| Span shape | First-class MLflow (matches Python) | Generic OpenTelemetry spans |
| Auto LLM tracing | tracedOpenAI covers OpenAI-compatible clients |
Manual or third-party instrumentation |
| Trace UI affordances | Full (gen_ai panes, session grouping, token panes) | Partial (you supply gen_ai.* attrs yourself) |
| Setup | One npm package + mlflow.init |
Multiple @opentelemetry/* packages + collector wiring |
| Best for | Node-native Databricks Apps and AppKit agents | Polyglot platforms with an existing OTel collector |
Treat OTLP as an escape hatch for environments where the npm SDK isn't an option. For greenfield Node agents, start here (F2b).
Validation Checklist
-
mlflow-tracingnpm package installed and pinned inpackage.json. -
mlflow-openaiinstalled if the agent calls any OpenAI-compatible client (Foundation Models / Azure OpenAI). -
mlflow.init({ trackingUri: "databricks", experimentId })called once, at process startup, before the first LLM call. -
DATABRICKS_HOST,DATABRICKS_TOKEN,MLFLOW_EXPERIMENT_IDset viaapp.yaml/ AppKit env wiring. - OpenAI client wrapped with
tracedOpenAI(...)(autolog equivalent). - One
@mlflow.trace/mlflow.trace(...)parent per request handler so nested LLM/tool spans roll up. -
mlflow.update_current_trace({ session_id, user_id })called per turn for session grouping and user attribution. -
traceIdreturned to the frontend (/chatresponse) for end-user feedback correlation. See 04c. - Production deployment uses the env-var matrix in
prod-tracing-deployment.md:ENABLE_MLFLOW_TRACING, SPCAN_EDITon the experiment, Git-folder caveat understood.
Do / Don't
| DO | DON'T |
|---|---|
Use tracedOpenAI for every OpenAI/Foundation Models client. |
Hand-instrument every chat.completions.create call. |
Wrap each request handler in one mlflow.trace(...) parent. |
Open a span per request without a parent — traces become orphaned. |
Set session_id and user_id once via update_current_trace. |
Set them as attributes on every child span — wastes storage. |
Pin mlflow-tracing / mlflow-openai versions in package.json. |
Use latest — span schema can shift between releases. |
Return traceId to the frontend for feedback correlation. |
Force the frontend to guess the trace id from logs. |
Use the OTLP path only when the mlflow-tracing SDK genuinely doesn't fit. |
Default to OTLP because it's "more standard" — you lose the MLflow UI affordances. |
References
Official documentation
- Instrument Node.js applications with MLflow Tracing (TypeScript SDK) — install, init,
tracedOpenAI, manual API, sessions. - Add traces to applications (overview + decision matrix) — auto vs manual vs combined.
- Trace agents deployed on Databricks — production env vars, Production Monitoring → Delta.
mlflow-tracingon npm — release notes and API surface.mlflow-openaion npm — autolog wrapper.
Related skills
- Foundation Step 1: MLflow GenAI Foundation — auth and environment detection (applies to Node too).
- Foundation Step 2: Python tracing — Python sibling, decision matrix, UC OTEL storage.
- Foundation Step 2c: Trace context and environments — canonical user / session / environment metadata +
APP_ENVIRONMENToverride (applies to Node and Python). - Autolog integrations reference — Python autolog equivalents.
- Production tracing deployment reference — env vars, SP
CAN_EDIT, Git-folder caveat. sdlc/04c-end-user-feedback— companion skill for thumbs-up/down logging viamlflow.log_feedback.
Version history
| Version | Date | Changes |
|---|---|---|
| 1.0.0 | 2026-04-24 | Initial skill: install, mlflow.init, tracedOpenAI, manual API (mlflow.trace, @mlflow.trace, withSpan, startSpan), nested spans, sessions/users, end-user feedback pairing, OTLP-fallback comparison table. |