@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,176 @@
1
+ # Changelog
2
+
3
+ All notable changes to the TypeScript package
4
+ (`@insightfactory.ai/insightfactory-databricks-langgraph-tracer`) are documented here. The format
5
+ is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the
6
+ project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ See [`../python/CHANGELOG.md`](../python/CHANGELOG.md) for changes to the
9
+ separately versioned Python package.
10
+
11
+ <!--
12
+ Releasing: add changes under [Unreleased] as you go. To cut a release, move those
13
+ items into a new `## [x.y.z] - YYYY-MM-DD` section, bump `version` in package.json,
14
+ and add the version's link reference at the bottom. CI requires a matching
15
+ `## [x.y.z]` section whenever package.json is version-bumped on a PR to main.
16
+ Pushing the resulting `typescript-vx.y.z` tag from main publishes the stable npm
17
+ package and opens its GitHub Release; pushes to develop publish a `dev`
18
+ prerelease from this section.
19
+ -->
20
+
21
+ ## [Unreleased]
22
+
23
+ ## [1.0.0] - 2026-09-02
24
+ ### Changed
25
+ - Renamed the npm package from `@insightfactory-ai/databricks-langgraph-tracer` to
26
+ `@insightfactory.ai/insightfactory-databricks-langgraph-tracer`, matching the Python
27
+ distribution name. Update the import specifier when upgrading.
28
+ - Published the stable package on public npm.
29
+ - License and third-party notices now ship inside the tarball (`LICENSE`,
30
+ `THIRD_PARTY_NOTICES`).
31
+ - `@langchain/core` and `@mlflow/core` became peer dependencies instead of
32
+ direct dependencies, so consumers control their own versions.
33
+ - Removed internal client-project and issue-tracker references from shipped
34
+ comments and docs.
35
+
36
+ ### Removed
37
+ - The GitHub Packages deprecation warning added in 0.6.2. The stable public
38
+ package does not warn on import.
39
+ - `@langchain/langgraph` and the unused `zod` dependency, dropped from the
40
+ published dependency set: neither is imported by `src/`, so they no longer
41
+ ship as a peer or direct dependency (`@langchain/langgraph` remains a
42
+ devDependency for tests).
43
+
44
+ ## [0.6.2] - 2026-09-02
45
+ ### Deprecated
46
+ - The GitHub Packages `@insightfactory-ai/databricks-langgraph-tracer` build is deprecated and
47
+ will no longer receive updates. Importing it now emits a `console.warn` directing consumers
48
+ to install `@insightfactory.ai/insightfactory-databricks-langgraph-tracer` from public npm instead.
49
+
50
+ ## [0.6.1] - 2026-07-29
51
+ ### Changed
52
+ - Refreshed the vendored LiteLLM pricing catalog (2,719 → 2,984 entries), adding cost
53
+ coverage for the latest Foundry / Azure OpenAI models — notably the **GPT-5.6 series**
54
+ (`gpt-5.6`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, plus their `azure/us/` and
55
+ `azure/eu/` regional variants). Deployments named after these models now resolve to a
56
+ pricing key and are costed, rather than being tagged `cost.unknown_model`. The manual
57
+ `azure_ai/deepseek-v4-flash` / `azure_ai/deepseek-v4-pro` overrides survive the refresh
58
+ unchanged.
59
+ - The refresh is not purely additive: upstream also revised rates for 21 non-Azure
60
+ entries (e.g. `sambanova/gpt-oss-120b`, `mistral/mistral-medium-latest`) and dropped
61
+ two `fireworks_ai/...whisper-v3*` keys. **No `azure/` or `azure_ai/` rate changed**, and
62
+ `resolvePricingKey` only probes those two prefixes, so default-path costs are
63
+ unaffected; consumers passing an explicit pricing key may see reported costs move.
64
+
65
+ ### Known limitations
66
+ - Long-context tiering is still not applied: `calculateLlmCost` reads only the flat
67
+ per-token rates, never `input_cost_per_token_above_272k_tokens` or the `_priority`
68
+ rates. Pre-existing and affecting all of gpt-5.x, but easier to reach now that the
69
+ GPT-5.6 entries carry 1,050,000-token windows — a 400k-input call under-reports by
70
+ roughly half.
71
+
72
+ ## [0.6.0] - 2026-06-25
73
+ ### Added
74
+ - `maxStringChars` option on `DatabricksLangGraphTracer` and
75
+ `initDatabricksTracing(...)` (env fallback `DATABRICKS_TRACING_MAX_STRING_CHARS`).
76
+ Opt-in (default off): when set, any plain-text string leaf longer than the
77
+ threshold in a span's inputs **or** outputs is replaced with a compact
78
+ `{ _truncated: true, chars: N, bytes: M, preview: "…" }` placeholder, so large
79
+ text (not just multimodal bytes) cannot push a Unity Catalog trace past the SQL
80
+ inline read limit (issue #23). It caps string values only — never keys or
81
+ structural fields — and is a per-leaf mitigation, not a hard per-trace budget, so
82
+ pair it with consumer-side payload reduction for the heaviest spans. A **distinct**
83
+ knob from the removed multimodal `maxContentPartBytes`: that gated inline binary
84
+ payloads; this caps arbitrary text.
85
+
86
+ ### Changed
87
+ - The text cap now also applies to span **outputs**, not only inputs — the spans that
88
+ breach the inline read limit in the reported case (`final_output` /
89
+ `aggregate_results`) are output-side. Multimodal payload externalization stays
90
+ **input-only**: outputs are arbitrary chain / tool JSON, so running the
91
+ `data`/`base64` probe on them would mis-strip legitimate non-binary output. Runs on
92
+ a copy; the live values are untouched.
93
+
94
+ ## [0.5.0] - 2026-06-24
95
+ ### Added
96
+ - `contentRefResolver` option on `DatabricksLangGraphTracer` and
97
+ `initDatabricksTracing(...)`. It is called once per multimodal content
98
+ part (image / PDF / file) with the part and a `ContentPartContext` (part
99
+ index/page, inline byte length, and a shallow copy of the run metadata), and
100
+ returns the reference object to store in the trace — e.g. a Unity Catalog volume
101
+ path — or `undefined` to fall back to the placeholder (issue #21).
102
+
103
+ ### Changed
104
+ - Inline base64 payloads of multimodal parts are now **never** stored in the trace:
105
+ they are always stripped before recording and replaced with a lightweight
106
+ reference (a `contentRefResolver` result, otherwise a
107
+ `{ type: ..., _omitted: true, bytes: N }` placeholder), keeping Unity Catalog
108
+ trace reads under the SQL inline limit. Remote `http(s)://` image URLs are kept
109
+ verbatim. The transform runs on a copy, so the cache-stable message is untouched.
110
+
111
+ ### Removed
112
+ - The per-content-part byte cap, including the
113
+ `DATABRICKS_TRACING_MAX_CONTENT_PART_BYTES` env var and the
114
+ `maxContentPartBytes` option. Inline payloads are now always externalized, so the
115
+ cap is obsolete.
116
+
117
+ ### Security
118
+ - A resolver result that would re-introduce an inline payload — a `data:` URI, a
119
+ recognized base64 content part, or the part's raw base64 echoed back — is rejected
120
+ and the placeholder is used instead, guaranteeing no inline image bytes are
121
+ persisted. `ctx.metadata` is a shallow copy, so a resolver cannot corrupt run
122
+ state.
123
+
124
+ ## [0.4.0] - 2026-06-22
125
+ ### Added
126
+ - Capture multimodal chat-model inputs (image / PDF / file content blocks) on LLM
127
+ spans.
128
+
129
+ ### Fixed
130
+ - Keep multimodal content inline when exporting to Unity Catalog table-backed
131
+ experiments, instead of dropping it to an attachment reference.
132
+
133
+ ## [0.3.0] - 2026-06-17
134
+ ### Added
135
+ - Trace-level per-model cost and token rollup, written to the `cost.by_model` tag
136
+ on the live trace before the root span ends. Unpriceable-but-resolved models
137
+ still contribute their token split (zero cost) so the totals stay complete.
138
+
139
+ ## [0.2.2] - 2026-06-09
140
+ ### Changed
141
+ - Emit OpenInference-style `llm.*` span keys so the Databricks dashboard cost
142
+ charts read token usage and cost correctly.
143
+
144
+ ## [0.2.1] - 2026-06-04
145
+ ### Fixed
146
+ - Chunk the Unity Catalog OTLP span export so payloads stay under the 10 MiB
147
+ ingest cap.
148
+
149
+ ## [0.2.0] - 2026-06-04
150
+ ### Added
151
+ - Export spans to Unity Catalog table-backed experiments.
152
+
153
+ ## [0.1.0] - 2026-06-01
154
+ ### Added
155
+ - Initial release: TypeScript port of the shared LangGraph → Databricks MLflow
156
+ tracer, with a cross-language parity suite asserting the TypeScript generated
157
+ keys equal the shared schema and the committed Python key set. Per-model pricing
158
+ comes from a vendored LiteLLM catalog with overrides and a refresh script.
159
+
160
+ <!-- Historical links (0.1.0-0.4.0) use commit SHAs: those release tags were
161
+ reserved by the repo's immutable-releases policy and cannot be created. Restore
162
+ typescript-vX.Y.Z tag links if the names are ever reclaimed. 0.5.0's tag is
163
+ created out-of-band (its bump predates the release workflow); 0.5.1+ tags are
164
+ minted by release.yml on push to main. -->
165
+ [Unreleased]: https://github.com/insightfactory-ai/if_s_langraph_mlflow_tracer/compare/typescript-v1.0.0...HEAD
166
+ [1.0.0]: https://github.com/insightfactory-ai/if_s_langraph_mlflow_tracer/compare/typescript-v0.6.2...typescript-v1.0.0
167
+ [0.6.2]: https://github.com/insightfactory-ai/if_s_langraph_mlflow_tracer/compare/typescript-v0.6.1...typescript-v0.6.2
168
+ [0.6.1]: https://github.com/insightfactory-ai/if_s_langraph_mlflow_tracer/compare/typescript-v0.6.0...typescript-v0.6.1
169
+ [0.6.0]: https://github.com/insightfactory-ai/if_s_langraph_mlflow_tracer/compare/typescript-v0.5.0...typescript-v0.6.0
170
+ [0.5.0]: https://github.com/insightfactory-ai/if_s_langraph_mlflow_tracer/compare/99462dbc95c43f20085cae0bfd7a2dc0aab319b1...typescript-v0.5.0
171
+ [0.4.0]: https://github.com/insightfactory-ai/if_s_langraph_mlflow_tracer/compare/951a9299c4beffcdd23fc7442bd79b49e2d9b52f...99462dbc95c43f20085cae0bfd7a2dc0aab319b1
172
+ [0.3.0]: https://github.com/insightfactory-ai/if_s_langraph_mlflow_tracer/compare/1e4130acb98eb947f65302e060d6a017da0f2feb...951a9299c4beffcdd23fc7442bd79b49e2d9b52f
173
+ [0.2.2]: https://github.com/insightfactory-ai/if_s_langraph_mlflow_tracer/compare/1a868d88a6227a0528e5725c49b63848f0e03aa9...1e4130acb98eb947f65302e060d6a017da0f2feb
174
+ [0.2.1]: https://github.com/insightfactory-ai/if_s_langraph_mlflow_tracer/compare/03e34a320a8e90e046d574dab0e58cdfe58ab738...1a868d88a6227a0528e5725c49b63848f0e03aa9
175
+ [0.2.0]: https://github.com/insightfactory-ai/if_s_langraph_mlflow_tracer/compare/83c62e28dbd04ef8e987689983e1e5a53e9d204f...03e34a320a8e90e046d574dab0e58cdfe58ab738
176
+ [0.1.0]: https://github.com/insightfactory-ai/if_s_langraph_mlflow_tracer/commit/83c62e28dbd04ef8e987689983e1e5a53e9d204f
package/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Copyright 2026 insightfactory.ai. All rights reserved.
2
+
3
+ This software is proprietary. No permission is granted to use, copy, modify,
4
+ distribute, sublicense, or sell it except under a separate written agreement
5
+ with insightfactory.ai.
package/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # @insightfactory.ai/insightfactory-databricks-langgraph-tracer
2
+
3
+ LangGraph tracer for Databricks MLflow (TypeScript). It uses a custom `@mlflow/core`
4
+ `BaseTracer` to write trace fields, token usage, and a cost rollup on the live trace before
5
+ the root span ends.
6
+
7
+ ## Install
8
+
9
+ Published on public npm:
10
+
11
+ ```bash
12
+ yarn add @insightfactory.ai/insightfactory-databricks-langgraph-tracer
13
+ # or: npm install @insightfactory.ai/insightfactory-databricks-langgraph-tracer
14
+ ```
15
+
16
+ ESM-only. Install the runtime peer dependencies alongside it: `@langchain/core`,
17
+ `@mlflow/core`.
18
+
19
+ ## Quickstart
20
+
21
+ ```ts
22
+ import {
23
+ initDatabricksTracing,
24
+ getTracingCallbacks,
25
+ } from "@insightfactory.ai/insightfactory-databricks-langgraph-tracer";
26
+
27
+ // 1. Bootstrap once at startup (reads env-first; options override).
28
+ initDatabricksTracing({ experimentId: "<mlflow-experiment-id>", source: "my-agent" });
29
+
30
+ // 2. Attach the callbacks to your LangGraph / LangChain run.
31
+ await graph.invoke(state, { callbacks: getTracingCallbacks() });
32
+ ```
33
+
34
+ ## Configuration
35
+
36
+ Env-first; `InitOptions` override the matching env var.
37
+
38
+ | Setting | Env var | Notes |
39
+ |---------|---------|-------|
40
+ | Tracking URI | `MLFLOW_TRACKING_URI` | `databricks` or `databricks://<profile>` (required) |
41
+ | Experiment | `MLFLOW_EXPERIMENT_ID` | by **id** only (required) |
42
+ | Source tag | — | `source` option, default `langgraph` |
43
+ | Multimodal refs | — | inline image/PDF/file bytes are externalized to a reference; `contentRefResolver` chooses it — see below |
44
+ | Text cap | `DATABRICKS_TRACING_MAX_STRING_CHARS` | opt-in; `maxStringChars` truncates over-long plain-text span content — see below |
45
+ | Disable | `TESTING` / `BUILDING` = `true`, or `enabled: false` | no-op — the only non-throwing path |
46
+
47
+ Missing required config **throws** `DatabricksTracingConfigurationError` (fail-fast) — disable
48
+ explicitly for local/dev runs. `initDatabricksTracing` is idempotent (repeat calls return the
49
+ same configuration).
50
+
51
+ ### Multimodal inputs (image / PDF / file)
52
+
53
+ Chat-model spans record their inputs as **structured messages**, preserving the
54
+ multimodal content parts a graph sends to the model — OpenAI / LangChain
55
+ `image_url`, OpenAI `file`, and Anthropic `image` / `document`.
56
+
57
+ The inline base64 of each such part is **never stored** in the trace: it is removed
58
+ before recording and replaced with a lightweight reference, so the Unity Catalog
59
+ trace tables stay readable (multi-MB data URIs previously pushed large invoice
60
+ traces past the SQL inline read limit). A remote `http(s)://` image URL
61
+ is already a small reference, so this step keeps it verbatim (but see the text cap
62
+ below — if enabled, it still truncates *any* string over its threshold, URLs
63
+ included). The transform runs on a copy of the inputs, so the live message sent to
64
+ the model is untouched and prompt caching is unaffected.
65
+
66
+ By default a part becomes a `{ type: ..., _omitted: true, bytes: N }` placeholder.
67
+ To store a meaningful reference instead — e.g. the Unity Catalog volume path the
68
+ image was loaded from, so it can be re-fetched at runtime — pass a
69
+ `contentRefResolver`:
70
+
71
+ ```ts
72
+ import {
73
+ type ContentPartContext,
74
+ initDatabricksTracing,
75
+ } from "@insightfactory.ai/insightfactory-databricks-langgraph-tracer";
76
+
77
+ const imageRef = (part: Record<string, unknown>, ctx: ContentPartContext) => {
78
+ // ctx.metadata is the run metadata — pass per-run data (e.g. a source volume
79
+ // path) via the invoke config's `metadata`, which propagates to the LLM run.
80
+ const path = ctx.metadata.encoded_images_path;
81
+ return typeof path === "string"
82
+ ? { type: part.type, ref: path, page: ctx.index }
83
+ : null; // fall back to the default placeholder
84
+ };
85
+
86
+ initDatabricksTracing({ experimentId: "...", contentRefResolver: imageRef });
87
+ ```
88
+
89
+ The resolver is called once per multimodal part with the part and a
90
+ `ContentPartContext` — `index` (position within the message content array, i.e. the
91
+ page number for a one-image-per-page invoice), `bytes` (the inline payload length),
92
+ and the run `metadata`. Return an object to store as the reference, or
93
+ `undefined` / `null` for the default placeholder. It is also accepted by
94
+ `new DatabricksLangGraphTracer({ contentRefResolver })` for per-graph wiring.
95
+
96
+ The library guarantees no inline image bytes are stored: if a resolver result
97
+ re-introduces an inline payload (a `data:` URI, a recognized base64 content part, or
98
+ the part's own payload echoed back under any key — anywhere in the returned object),
99
+ it is rejected and the placeholder is used. Beyond that, keep the reference
100
+ **compact** — a *fabricated* large string under a custom key is the consumer's
101
+ responsibility (the library strips inline payloads but does not otherwise bound what
102
+ a resolver returns).
103
+ `ctx.metadata` is a shallow copy of the run metadata, so a resolver cannot corrupt run
104
+ state by setting top-level keys (don't mutate its nested values, which are shared).
105
+
106
+ ### Capping large text
107
+
108
+ Multimodal externalization handles inline *bytes*, but large **plain text** can also
109
+ push a trace past the SQL inline read limit — e.g. a classification vocabulary or
110
+ aggregated result set threaded through every fan-out span's inputs and outputs. Set
111
+ `maxStringChars` (or `DATABRICKS_TRACING_MAX_STRING_CHARS`) to truncate it:
112
+
113
+ ```ts
114
+ initDatabricksTracing({ experimentId: "...", maxStringChars: 50_000 });
115
+ ```
116
+
117
+ When set, any string **value** longer than the threshold — in a span's inputs *or*
118
+ outputs — is replaced with a compact placeholder:
119
+
120
+ ```json
121
+ { "_truncated": true, "chars": 812345, "bytes": 812345, "preview": "first 256 chars…" }
122
+ ```
123
+
124
+ It is **opt-in / off by default** (generic truncation costs debuggability, so you
125
+ choose the threshold). It caps string values only — never keys or structural fields —
126
+ and runs on a copy, so the live messages are untouched. Also accepted by
127
+ `new DatabricksLangGraphTracer({ maxStringChars })` for per-graph wiring.
128
+
129
+ This is a **distinct** knob from the multimodal handling above: `contentRefResolver`
130
+ chooses references for inline image/PDF/file bytes; `maxStringChars` caps arbitrary
131
+ text (and never truncates a resolver's reference). Because it is generic, it also
132
+ truncates any other over-threshold string — including a remote `http(s)://` image URL
133
+ the multimodal step keeps verbatim — so set the threshold comfortably above your
134
+ reference / URL lengths. Note it is a **per-leaf** mitigation, not a hard per-trace
135
+ byte budget — enough sub-threshold leaves can still sum past the limit — so for the
136
+ heaviest spans also reduce what you record (pass ids/references through node state
137
+ rather than full payloads).
138
+
139
+ The threshold counts **characters** (UTF-16 units here; the Python package counts
140
+ code points, so the two can differ on non-BMP text), while the inline limit is in
141
+ bytes — multibyte text can be up to ~4× larger in bytes than characters, so for
142
+ CJK/emoji-heavy content size the cap below `limit / 4`. The placeholder's `bytes`
143
+ field always reports the exact UTF-8 size of the original.
144
+
145
+ ## Development
146
+
147
+ Built and tested with **Yarn 4**:
148
+
149
+ ```bash
150
+ cd typescript
151
+ yarn install
152
+ yarn build # tsc -> dist/ + copy vendored pricing data
153
+ yarn test # vitest (incl. cross-language schema parity)
154
+ yarn keys:check # fail if generated-keys.ts drifts from the schema
155
+ yarn pricing:update # refresh the vendored LiteLLM pricing catalog
156
+ ```
157
+
158
+ ## Changelog
159
+
160
+ See [`CHANGELOG.md`](CHANGELOG.md).
@@ -0,0 +1,26 @@
1
+ LiteLLM model pricing data
2
+ ==========================
3
+
4
+ This package includes model pricing data derived from LiteLLM.
5
+
6
+ MIT License
7
+
8
+ Copyright (c) 2023 Berri AI
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
package/dist/cost.d.ts ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Normalized token usage extracted from an LLM run end payload.
3
+ *
4
+ * `cached_tokens` (cache-read) and `cache_creation_tokens` (Anthropic
5
+ * cache-write) are both **subsets** of `input_tokens` after LangChain
6
+ * normalization — see `langchain-anthropic` `_create_usage_metadata`, which adds
7
+ * cache tokens onto Anthropic's cache-excluding base count so the final
8
+ * `input_tokens` is the true total (matching OpenAI's convention).
9
+ */
10
+ export interface TokenUsage {
11
+ input_tokens: number;
12
+ output_tokens: number;
13
+ total_tokens: number;
14
+ cached_tokens: number;
15
+ cache_creation_tokens: number;
16
+ reasoning_tokens: number;
17
+ }
18
+ export interface LLMCost {
19
+ input_cost: number;
20
+ output_cost: number;
21
+ total_cost: number;
22
+ cached_input_cost: number;
23
+ cache_creation_cost: number;
24
+ }
25
+ /** The reserved 3-key `mlflow.llm.cost` / `mlflow.trace.cost` payload. */
26
+ export interface ReservedCostPayload {
27
+ input_cost: number;
28
+ output_cost: number;
29
+ total_cost: number;
30
+ }
31
+ /**
32
+ * The reserved 3-key cost payload (MLflow `CostKey`). The cache-read /
33
+ * cache-creation cost split is written separately to the non-reserved
34
+ * `gen_ai.usage.input_tokens.cached_cost` / `.cache_creation_cost` span
35
+ * attributes — both `total_cost` and those line-items already account for the
36
+ * cached discount and cache-creation premium.
37
+ */
38
+ export declare function spanCostPayload(cost: LLMCost): ReservedCostPayload;
39
+ /** Cheap key-presence probe against the vendored LiteLLM catalog. */
40
+ export declare function hasPricingEntry(key: string): boolean;
41
+ /** Return the catalog entry's `litellm_provider` (`azure`/`azure_ai`). */
42
+ export declare function getPricingProvider(pricingKey: string): string | undefined;
43
+ /**
44
+ * Compute per-call LLM cost in USD from the vendored catalog.
45
+ *
46
+ * Returns `null` when the model is absent from the catalog or the entry lacks
47
+ * the per-token rates needed to price it (e.g. LiteLLM's `sample_spec` schema
48
+ * blob or flat-fee container entries). `null` lets callers distinguish
49
+ * unknown/unpriceable models from genuine zero-cost models.
50
+ *
51
+ * Cache-read and cache-creation tokens are both subsets of `input_tokens`; each
52
+ * is subtracted from the regular input pool and billed at its own catalog rate
53
+ * (`cache_read_input_token_cost` — a discount; and
54
+ * `cache_creation_input_token_cost` — Anthropic's premium), each falling back to
55
+ * the input rate when the catalog has none. OpenAI has no cache-creation charge
56
+ * and returns no cache-creation tokens, so its cache-creation cost is zero.
57
+ */
58
+ export declare function calculateLLMCost(pricingKey: string, usage: TokenUsage): LLMCost | null;
59
+ /**
60
+ * Resolve a model name to its LiteLLM catalog key.
61
+ *
62
+ * Precedence: explicit override → `azure/<name>` (Azure OpenAI) →
63
+ * `azure_ai/<name>` (Azure AI Foundry) → `undefined` (callers surface this via
64
+ * the `cost.unknown_model` trace tag).
65
+ */
66
+ export declare function resolvePricingKey(modelName: string, explicit?: string): string | undefined;
67
+ /**
68
+ * Qualify a deployment name to its catalog key.
69
+ *
70
+ * Already-qualified `azure/` / `azure_ai/` names pass through unchanged.
71
+ * Otherwise {@link resolvePricingKey} is tried, falling back to `azure/<name>`
72
+ * so unknown models still surface a stable identifier for the
73
+ * `cost.unknown_model` aggregation tag.
74
+ */
75
+ export declare function qualifyDeploymentName(name: string): string;
76
+ //# sourceMappingURL=cost.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost.d.ts","sourceRoot":"","sources":["../src/cost.ts"],"names":[],"mappings":"AAiCA;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,OAAO;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IAGnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,mBAAmB,CAMlE;AAMD,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,0EAA0E;AAC1E,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGzE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,GAAG,IAAI,CAiCtF;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK1F;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG1D"}
package/dist/cost.js ADDED
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Per-call LLM cost computation against the vendored LiteLLM pricing catalog.
3
+ *
4
+ * Aligned with the Python `cost.py` (the shared contract). The catalog stores
5
+ * **per-token** rates (`input_cost_per_token`), so the formula multiplies tokens
6
+ * by the rate directly (no `/1e6`).
7
+ *
8
+ * Reserved cost keys are 3-key by MLflow's `CostKey` schema (Databricks strips
9
+ * extras on ingestion): the span `mlflow.llm.cost` payload — and the trace-level
10
+ * `mlflow.trace.cost` rollup — are both exactly
11
+ * `{ input_cost, output_cost, total_cost }` via {@link spanCostPayload}. The
12
+ * cache-read / cache-creation cost split (`cached_input_cost` /
13
+ * `cache_creation_cost`) is persisted separately as **non-reserved span
14
+ * attributes** (`gen_ai.usage.input_tokens.cached_cost` / `.cache_creation_cost`
15
+ * — Option B), which survive Databricks normalization.
16
+ */
17
+ import { readFileSync } from "node:fs";
18
+ import { fileURLToPath } from "node:url";
19
+ const PRICING_URL = new URL("./pricing/model-pricing.json", import.meta.url);
20
+ const pricing = JSON.parse(readFileSync(fileURLToPath(PRICING_URL), "utf8"));
21
+ /**
22
+ * The reserved 3-key cost payload (MLflow `CostKey`). The cache-read /
23
+ * cache-creation cost split is written separately to the non-reserved
24
+ * `gen_ai.usage.input_tokens.cached_cost` / `.cache_creation_cost` span
25
+ * attributes — both `total_cost` and those line-items already account for the
26
+ * cached discount and cache-creation premium.
27
+ */
28
+ export function spanCostPayload(cost) {
29
+ return {
30
+ input_cost: cost.input_cost,
31
+ output_cost: cost.output_cost,
32
+ total_cost: cost.total_cost,
33
+ };
34
+ }
35
+ function isFiniteNumber(v) {
36
+ return typeof v === "number" && Number.isFinite(v);
37
+ }
38
+ /** Cheap key-presence probe against the vendored LiteLLM catalog. */
39
+ export function hasPricingEntry(key) {
40
+ return Object.hasOwn(pricing, key);
41
+ }
42
+ /** Return the catalog entry's `litellm_provider` (`azure`/`azure_ai`). */
43
+ export function getPricingProvider(pricingKey) {
44
+ const entry = pricing[pricingKey];
45
+ return typeof entry?.litellm_provider === "string" ? entry.litellm_provider : undefined;
46
+ }
47
+ /**
48
+ * Compute per-call LLM cost in USD from the vendored catalog.
49
+ *
50
+ * Returns `null` when the model is absent from the catalog or the entry lacks
51
+ * the per-token rates needed to price it (e.g. LiteLLM's `sample_spec` schema
52
+ * blob or flat-fee container entries). `null` lets callers distinguish
53
+ * unknown/unpriceable models from genuine zero-cost models.
54
+ *
55
+ * Cache-read and cache-creation tokens are both subsets of `input_tokens`; each
56
+ * is subtracted from the regular input pool and billed at its own catalog rate
57
+ * (`cache_read_input_token_cost` — a discount; and
58
+ * `cache_creation_input_token_cost` — Anthropic's premium), each falling back to
59
+ * the input rate when the catalog has none. OpenAI has no cache-creation charge
60
+ * and returns no cache-creation tokens, so its cache-creation cost is zero.
61
+ */
62
+ export function calculateLLMCost(pricingKey, usage) {
63
+ // `sample_spec` is LiteLLM's schema/example blob, not a real model.
64
+ if (pricingKey === "sample_spec")
65
+ return null;
66
+ const entry = pricing[pricingKey];
67
+ if (!entry)
68
+ return null;
69
+ const inputRate = entry.input_cost_per_token;
70
+ const outputRate = entry.output_cost_per_token;
71
+ if (!isFiniteNumber(inputRate) || !isFiniteNumber(outputRate))
72
+ return null;
73
+ const cachedRate = isFiniteNumber(entry.cache_read_input_token_cost)
74
+ ? entry.cache_read_input_token_cost
75
+ : inputRate;
76
+ const creationRate = isFiniteNumber(entry.cache_creation_input_token_cost)
77
+ ? entry.cache_creation_input_token_cost
78
+ : inputRate;
79
+ const cached = Math.max(0, usage.cached_tokens || 0);
80
+ const creation = Math.max(0, usage.cache_creation_tokens || 0);
81
+ // Both cache categories are subtracted from the input pool and billed apart.
82
+ const regular = Math.max(0, (usage.input_tokens || 0) - cached - creation);
83
+ const cachedInputCost = cached * cachedRate;
84
+ const cacheCreationCost = creation * creationRate;
85
+ const inputCost = regular * inputRate + cachedInputCost + cacheCreationCost;
86
+ const outputCost = (usage.output_tokens || 0) * outputRate;
87
+ return {
88
+ input_cost: inputCost,
89
+ output_cost: outputCost,
90
+ total_cost: inputCost + outputCost,
91
+ cached_input_cost: cachedInputCost,
92
+ cache_creation_cost: cacheCreationCost,
93
+ };
94
+ }
95
+ /**
96
+ * Resolve a model name to its LiteLLM catalog key.
97
+ *
98
+ * Precedence: explicit override → `azure/<name>` (Azure OpenAI) →
99
+ * `azure_ai/<name>` (Azure AI Foundry) → `undefined` (callers surface this via
100
+ * the `cost.unknown_model` trace tag).
101
+ */
102
+ export function resolvePricingKey(modelName, explicit) {
103
+ if (explicit)
104
+ return explicit;
105
+ if (hasPricingEntry(`azure/${modelName}`))
106
+ return `azure/${modelName}`;
107
+ if (hasPricingEntry(`azure_ai/${modelName}`))
108
+ return `azure_ai/${modelName}`;
109
+ return undefined;
110
+ }
111
+ /**
112
+ * Qualify a deployment name to its catalog key.
113
+ *
114
+ * Already-qualified `azure/` / `azure_ai/` names pass through unchanged.
115
+ * Otherwise {@link resolvePricingKey} is tried, falling back to `azure/<name>`
116
+ * so unknown models still surface a stable identifier for the
117
+ * `cost.unknown_model` aggregation tag.
118
+ */
119
+ export function qualifyDeploymentName(name) {
120
+ if (name.startsWith("azure/") || name.startsWith("azure_ai/"))
121
+ return name;
122
+ return resolvePricingKey(name) ?? `azure/${name}`;
123
+ }
124
+ //# sourceMappingURL=cost.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost.js","sourceRoot":"","sources":["../src/cost.ts"],"names":[],"mappings":"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"}