@insightfactory.ai/insightfactory-databricks-langgraph-tracer 1.0.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +176 -0
  2. package/LICENSE +5 -0
  3. package/README.md +160 -0
  4. package/THIRD_PARTY_NOTICES +26 -0
  5. package/dist/cost.d.ts +76 -0
  6. package/dist/cost.d.ts.map +1 -0
  7. package/dist/cost.js +124 -0
  8. package/dist/cost.js.map +1 -0
  9. package/dist/databricks-tracer.d.ts +235 -0
  10. package/dist/databricks-tracer.d.ts.map +1 -0
  11. package/dist/databricks-tracer.js +841 -0
  12. package/dist/databricks-tracer.js.map +1 -0
  13. package/dist/errors.d.ts +5 -0
  14. package/dist/errors.d.ts.map +1 -0
  15. package/dist/errors.js +8 -0
  16. package/dist/errors.js.map +1 -0
  17. package/dist/generated-keys.d.ts +39 -0
  18. package/dist/generated-keys.d.ts.map +1 -0
  19. package/dist/generated-keys.js +39 -0
  20. package/dist/generated-keys.js.map +1 -0
  21. package/dist/index.d.ts +65 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +130 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/pricing/model-pricing-overrides.json +28 -0
  26. package/dist/pricing/model-pricing.json +1 -0
  27. package/dist/token-aggregate.d.ts +29 -0
  28. package/dist/token-aggregate.d.ts.map +1 -0
  29. package/dist/token-aggregate.js +63 -0
  30. package/dist/token-aggregate.js.map +1 -0
  31. package/dist/trace-metadata.d.ts +14 -0
  32. package/dist/trace-metadata.d.ts.map +1 -0
  33. package/dist/trace-metadata.js +33 -0
  34. package/dist/trace-metadata.js.map +1 -0
  35. package/dist/uc-export.d.ts +48 -0
  36. package/dist/uc-export.d.ts.map +1 -0
  37. package/dist/uc-export.js +243 -0
  38. package/dist/uc-export.js.map +1 -0
  39. package/package.json +68 -0
  40. package/src/pricing/model-pricing-overrides.json +28 -0
  41. package/src/pricing/model-pricing.json +1 -0
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Custom LangChain `BaseTracer` that emits the shared schema to Databricks
3
+ * MLflow via `@mlflow/core`.
4
+ *
5
+ * This is the TypeScript counterpart of the Python `DatabricksLangGraphTracer`
6
+ * and emits the **identical** shared trace/span schema
7
+ * (`schema/trace-schema.json`):
8
+ *
9
+ * - **Root run create** → trace **tags** (`source`, `langgraph.run_id` /
10
+ * `graph_id` / `env` / `api_revision`, conditional `langgraph.thread_id`) and
11
+ * trace **metadata** (`mlflow.trace.session` / `mlflow.trace.user` when a
12
+ * thread / user is present).
13
+ * - **Child run create** → `langgraph.node` / `langgraph.step` on the live node
14
+ * span (from LangGraph's run metadata).
15
+ * - **LLM run update (end)** → resolved `mlflow.llm.model` / `mlflow.llm.provider`,
16
+ * the reserved `mlflow.chat.tokenUsage` (incl. the cache-read / cache-creation
17
+ * slots), the non-reserved token-count attrs, the reserved 3-key
18
+ * `mlflow.llm.cost` plus the non-reserved cache cost line-items, and the
19
+ * OpenInference-style `llm.*` dashboard keys (`llm.model_name` /
20
+ * `llm.model_provider` / `llm.usage.prompt_tokens_cost` /
21
+ * `llm.usage.completion_tokens_cost` — the keys the Databricks experiment
22
+ * Overview cost charts aggregate) — accumulating a per-root cost aggregate
23
+ * (totals **and** a per-model cost/token rollup).
24
+ * - **Root run update (end), before `span.end()`** → the reserved 3-key
25
+ * `mlflow.trace.cost` metadata rollup, the per-model `cost.by_model` tag
26
+ * (JSON), and the `cost.unknown_model` tag.
27
+ *
28
+ * Uses `startSpan` with an explicit `parent` ({@link LiveSpan}) so nested runs
29
+ * keep hierarchy without relying on the OTEL active context (callbacks are not
30
+ * executed inside `withSpan`). {@link BaseTracer} invokes `persistRun` only for
31
+ * root runs; every run (root included) is closed from `onRunUpdate`.
32
+ */
33
+ import { SpanType } from "@mlflow/core";
34
+ import { BaseTracer, type Run } from "@langchain/core/tracers/base";
35
+ import { type TokenUsage } from "./cost.js";
36
+ /** Maps LangChain `run_type` strings to MLflow {@link SpanType} (default CHAIN). */
37
+ export declare function mapLangChainRunTypeToMlflowSpanType(runType: string): SpanType;
38
+ /** Best-effort JSON-serializable copy for span inputs/outputs (handles cycles). */
39
+ export declare function serializeTracePayload(value: unknown): unknown;
40
+ /**
41
+ * Multimodal content-reference externalization (issues #16, #21). Chat-model span
42
+ * inputs preserve multimodal image / PDF / file content parts, but the inline
43
+ * base64 payload of each is removed before recording: the part is replaced with a
44
+ * lightweight reference so the Unity Catalog trace tables never carry the multi-MB
45
+ * data URIs that pushed large invoice traces past the SQL inline read limit.
46
+ *
47
+ * By default a part becomes a `{ type, _omitted: true, bytes: N }` placeholder. A
48
+ * consumer can instead supply a {@link ContentRefResolver} mapping a part to its
49
+ * own reference object — for example, the Unity Catalog volume path the image
50
+ * was loaded from, so the original can be re-fetched at runtime. The resolver runs
51
+ * only on the stored copy of the inputs; the live message sent to the model is
52
+ * untouched (so prompt caching is unaffected).
53
+ *
54
+ * When `maxStringChars` is set, any over-long *plain-text* leaf — in span **inputs or
55
+ * outputs** — is replaced with a compact `{ _truncated: true, chars: N, bytes: M,
56
+ * preview: "…" }` placeholder before recording: large text, not just multimodal
57
+ * bytes, can also push a trace past the SQL inline read limit. The
58
+ * multimodal payload externalization above runs on inputs only (LLM messages);
59
+ * outputs are arbitrary chain / tool JSON, so only the text cap is applied to them
60
+ * (see {@link externalizeContentRefs}). Both run on a copy; the live values are never
61
+ * mutated.
62
+ */
63
+ /** Context passed to a {@link ContentRefResolver} for one multimodal part. */
64
+ export interface ContentPartContext {
65
+ /** Position within the message `content` array (the page number for a pure-image message). */
66
+ index: number;
67
+ /** Encoded length of the inline payload being externalized. */
68
+ bytes: number;
69
+ /**
70
+ * The run's metadata (`run.extra.metadata` — LangGraph / RunnableConfig metadata).
71
+ * This is a **shallow copy** of the run metadata, so adding or replacing top-level
72
+ * keys is harmless (it cannot corrupt the live run state shared with sibling/child
73
+ * spans); its nested values are still shared, so do not mutate those.
74
+ */
75
+ metadata: Record<string, unknown>;
76
+ }
77
+ /**
78
+ * Consumer hook: maps a multimodal content part (+ its context) to the reference
79
+ * object stored in the trace, or `undefined` / `null` to fall back to the default
80
+ * `{ type, _omitted: true, bytes: N }` placeholder.
81
+ */
82
+ export type ContentRefResolver = (part: Record<string, unknown>, ctx: ContentPartContext) => Record<string, unknown> | undefined | null;
83
+ /**
84
+ * Resolve the text cap: explicit option wins, else `DATABRICKS_TRACING_MAX_STRING_CHARS`.
85
+ * Returns `undefined` (cap disabled) when neither is set, or when the value is not a
86
+ * positive integer. A non-positive / unparseable env value is ignored (warned), not
87
+ * thrown, so a stray override never breaks tracing startup.
88
+ */
89
+ export declare function resolveMaxStringChars(explicit?: number): number | undefined;
90
+ /**
91
+ * Externalize multimodal content parts' inline payloads and cap text.
92
+ * Public entry point. Takes a **shallow copy** of `metadata` once (when payloads are
93
+ * externalized with a resolver) so a resolver mutating `ctx.metadata` cannot corrupt
94
+ * the live run metadata, which the caller passes by reference and which is shared with
95
+ * sibling / child spans (resilient by design — a faulty resolver must not affect
96
+ * tracing). Then walks via {@link externalizeWalk}.
97
+ *
98
+ * `externalizePayloads` controls the multimodal-part stripping:
99
+ * - **inputs** (`true`) — the #21 behaviour: inline image / PDF / file payloads in
100
+ * message `content` arrays are replaced with references (inputs are LLM messages,
101
+ * where the `content` / `data` / `base64` shapes are genuinely multimodal).
102
+ * - **outputs** (`false`) — only the text cap is applied. Span outputs are arbitrary
103
+ * chain / tool return values; the multimodal probe matches a bare `data` / `base64`
104
+ * string in any `content` list, so running it would strip legitimate non-binary
105
+ * output to an `_omitted` placeholder. A genuinely huge inline payload in an output
106
+ * is still bounded by the text cap when it is set.
107
+ */
108
+ export declare function externalizeContentRefs(obj: unknown, resolver: ContentRefResolver | undefined, metadata: Record<string, unknown>, maxStringChars?: number, externalizePayloads?: boolean): unknown;
109
+ /**
110
+ * Extract normalized token usage from an LLM run end payload, extended for the
111
+ * Anthropic `cache_creation` category to match the Python `_extract_usage`.
112
+ *
113
+ * Prefers LangChain normalized `usage_metadata`
114
+ * (`input_token_details.cache_read` / `.cache_creation`,
115
+ * `output_token_details.reasoning`); falls back to raw `response_metadata.usage`
116
+ * (`input_tokens_details.cached_tokens` / `cache_read_input_tokens`,
117
+ * `cache_creation_input_tokens`, `output_tokens_details.reasoning_tokens`).
118
+ * Missing cache/reasoning sub-fields default to `0`. Returns `undefined` when
119
+ * neither source is present.
120
+ */
121
+ export declare function extractUsage(run: Run): TokenUsage | undefined;
122
+ /**
123
+ * Derive the canonical LiteLLM catalog key for an LLM run, aligned with the
124
+ * Python `_extract_model`.
125
+ *
126
+ * Sources, in order: invocation params `model` (probed both top-level on
127
+ * `run.extra.invocation_params` and nested under `run.extra.metadata`), then
128
+ * `response_metadata.model_name` — each qualified via `qualify` (default
129
+ * {@link qualifyDeploymentName}; consumers with non-canonical deployment
130
+ * names supply a resolver via the tracer's `pricingKeyResolver` option).
131
+ */
132
+ export declare function extractModel(run: Run, qualify?: (name: string) => string): string | undefined;
133
+ export declare class DatabricksLangGraphTracer extends BaseTracer {
134
+ name: string;
135
+ private readonly source;
136
+ private readonly qualifyModel;
137
+ private readonly contentRefResolver;
138
+ private readonly maxStringChars;
139
+ private readonly spanMap;
140
+ private readonly traceAggregates;
141
+ private readonly rootByRunId;
142
+ constructor(options?: {
143
+ source?: string;
144
+ /**
145
+ * Maps a deployment/model name to its LiteLLM catalog key, for
146
+ * deployments whose names diverge from the catalog (e.g. a tenant
147
+ * alias, or third-party models like `kimi-*` priced under another
148
+ * provider). Return `undefined` to fall through to the default
149
+ * `azure/<name>` → `azure_ai/<name>` probe.
150
+ */
151
+ pricingKeyResolver?: (name: string) => string | undefined;
152
+ /**
153
+ * Maps each multimodal content part to the reference stored in the trace in
154
+ * place of its inline base64 (issues #16/#21). When omitted, parts are
155
+ * externalized to a `{ type, _omitted: true, bytes: N }` placeholder. Runs on
156
+ * a copy of the inputs only — the live message sent to the model is untouched.
157
+ */
158
+ contentRefResolver?: ContentRefResolver;
159
+ /**
160
+ * Opt into capping over-long *plain-text* leaves in span inputs / outputs to a
161
+ * `{ _truncated: true, … }` placeholder (large text, not just
162
+ * multimodal bytes, can push a trace past the SQL inline read limit). Falls
163
+ * back to `DATABRICKS_TRACING_MAX_STRING_CHARS`, else off. A distinct knob from
164
+ * the multimodal reference handling above.
165
+ */
166
+ maxStringChars?: number;
167
+ });
168
+ onRunCreate(run: Run): Promise<void>;
169
+ onRunUpdate(run: Run): Promise<void>;
170
+ /**
171
+ * Write the root trace tags + session/user metadata (Python
172
+ * `_write_root_trace_fields`). Always emits `source` + the `langgraph.run_id` /
173
+ * `graph_id` / `env` / `api_revision` tags. A thread id additionally produces
174
+ * the `langgraph.thread_id` tag and the reserved `mlflow.trace.session`
175
+ * metadata; a user identity produces the reserved `mlflow.trace.user` metadata.
176
+ */
177
+ private writeRootTraceFields;
178
+ /**
179
+ * Write `langgraph.node` / `langgraph.step` onto a child span. LangGraph stamps
180
+ * these onto every per-node run's metadata (including the node's nested LLM
181
+ * run), mirroring the Python `_write_child_langgraph_context`.
182
+ */
183
+ private writeChildLangGraphContext;
184
+ /**
185
+ * Write model / provider / token-usage / cost attrs on a live LLM span (Python
186
+ * `_enrich_llm_span`). Token usage is written whenever usage is extractable;
187
+ * model / provider whenever the model resolves. Cost needs both: the reserved
188
+ * 3-key `mlflow.llm.cost` plus the non-reserved cache-read / cache-creation
189
+ * cost line-items (Option B) are written and folded into the root aggregate; a
190
+ * resolved-but-unpriceable model is recorded for the `cost.unknown_model` tag.
191
+ * Whenever the model resolves with usage, the span is also folded into the
192
+ * per-model `cost.by_model` rollup (tokens always; cost when priceable).
193
+ *
194
+ * The OpenInference-style `llm.*` duplicates (`llm.model_name` /
195
+ * `llm.model_provider` / `llm.usage.prompt_tokens_cost` /
196
+ * `llm.usage.completion_tokens_cost`) mirror the MLflow-convention attributes
197
+ * because the Databricks experiment Overview dashboard aggregates cost from
198
+ * those keys via `variant_get` on the UC span table — without them, Cost
199
+ * Breakdown / Cost Over Time render $0.00. The cost values must
200
+ * stay numbers: `variant_get(..., 'DOUBLE')` returns NULL for strings.
201
+ */
202
+ private enrichLLMSpan;
203
+ /**
204
+ * Write token-usage span attributes. The reserved `mlflow.chat.tokenUsage`
205
+ * attribute carries MLflow's `TokenUsageKey` shape extended with the two cache
206
+ * slots — `{ input_tokens, output_tokens, total_tokens, cache_read_input_tokens,
207
+ * cache_creation_input_tokens }` — so they survive Databricks ingestion and
208
+ * render in the UI usage breakdown. The non-reserved `gen_ai.usage.*`
209
+ * token-count attrs mirror the Python `_write_token_usage`.
210
+ */
211
+ private writeTokenUsage;
212
+ /**
213
+ * Write the trace-level cost rollups + `cost.unknown_model` tag (Python
214
+ * `_write_root_trace_rollup`). `mlflow.trace.cost` is the reserved MLflow
215
+ * `CostKey` shape — exactly `{ input_cost, output_cost, total_cost }`;
216
+ * Databricks strips any extra key on ingestion. `total_cost` already accounts
217
+ * for the cached discount and cache-creation premium; the per-line-item split
218
+ * lives on the spans.
219
+ *
220
+ * `cost.by_model` is the non-reserved per-model breakdown: emitted
221
+ * as a JSON string under a **trace tag** (like `cost.unknown_model`) rather
222
+ * than a custom metadata key — tags are proven to survive Databricks UC
223
+ * ingestion and stay queryable from the `*_trace_unified` view. Each bucket
224
+ * carries cost and tokens; the per-model totals reconcile with
225
+ * `mlflow.trace.cost` / `mlflow.chat.tokenUsage` within rounding. Models are
226
+ * emitted in sorted order, mirroring the Python tracer (the JSON parses to the
227
+ * same structure; the serialized bytes differ — Python `json.dumps` inserts
228
+ * `", "` / `": "` separators and renders integral zero floats as `0.0`).
229
+ *
230
+ * Must run before the root `span.end()` (see {@link onRunUpdate}).
231
+ */
232
+ private writeRootTraceRollup;
233
+ protected persistRun(_run: Run): Promise<void>;
234
+ }
235
+ //# sourceMappingURL=databricks-tracer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"databricks-tracer.d.ts","sourceRoot":"","sources":["../src/databricks-tracer.ts"],"names":[],"mappings":"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"}