@anyway-sh/node-server-sdk 0.22.9 → 0.22.10

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.
package/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # @anyway-sh/node-server-sdk
2
+
3
+ OpenTelemetry-based observability SDK for LLM applications. Automatically instruments LLM provider calls, tracks token usage, and calculates costs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @anyway-sh/node-server-sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { initialize } from "@anyway-sh/node-server-sdk";
15
+
16
+ initialize({
17
+ apiKey: process.env.ANYWAY_API_KEY,
18
+ appName: "my-app",
19
+ });
20
+ ```
21
+
22
+ ## ESM / Next.js
23
+
24
+ ESM projects must pass provider modules explicitly since OpenTelemetry's require-based instrumentation doesn't work with ESM imports:
25
+
26
+ ```typescript
27
+ import { initialize } from "@anyway-sh/node-server-sdk";
28
+ import * as OpenAIModule from "openai";
29
+
30
+ initialize({
31
+ apiKey: process.env.ANYWAY_API_KEY,
32
+ appName: "my-app",
33
+ instrumentModules: {
34
+ openAI: OpenAIModule.OpenAI,
35
+ },
36
+ });
37
+ ```
38
+
39
+ ## Tracing Workflows
40
+
41
+ Use `withWorkflow`, `withTask`, `withAgent`, and `withTool` to create structured trace hierarchies:
42
+
43
+ ```typescript
44
+ import { withWorkflow, withTask } from "@anyway-sh/node-server-sdk";
45
+
46
+ async function main() {
47
+ await withWorkflow({ name: "my-pipeline" }, async () => {
48
+ const result = await withTask({ name: "generate-response" }, async () => {
49
+ return openai.chat.completions.create({
50
+ model: "gpt-4",
51
+ messages: [{ role: "user", content: "Hello" }],
52
+ });
53
+ });
54
+ return result;
55
+ });
56
+ }
57
+ ```
58
+
59
+ ### Class Decorators
60
+
61
+ ```typescript
62
+ import { workflow, task } from "@anyway-sh/node-server-sdk";
63
+
64
+ class MyPipeline {
65
+ @workflow({ name: "run" })
66
+ async run() {
67
+ return this.step();
68
+ }
69
+
70
+ @task({ name: "step" })
71
+ async step() {
72
+ // ...
73
+ }
74
+ }
75
+ ```
76
+
77
+ ### Association Properties
78
+
79
+ Attach metadata to traces for filtering and grouping:
80
+
81
+ ```typescript
82
+ await withWorkflow(
83
+ {
84
+ name: "chat",
85
+ associationProperties: { userId: "user-123", sessionId: "abc" },
86
+ },
87
+ async () => {
88
+ // All spans in this workflow will carry these properties
89
+ },
90
+ );
91
+ ```
92
+
93
+ ## Configuration
94
+
95
+ Key options for `initialize()`:
96
+
97
+ | Option | Default | Description |
98
+ |--------|---------|-------------|
99
+ | `apiKey` | `ANYWAY_API_KEY` env var | API key for sending traces |
100
+ | `appName` | `npm_package_name` | Application name in traces |
101
+ | `baseUrl` | `ANYWAY_BASE_URL` or `https://api.traceloop.com` | Trace collector endpoint |
102
+ | `disableBatch` | `false` | Send spans immediately (for local dev) |
103
+ | `exporter` | OTLP exporter | Custom OpenTelemetry `SpanExporter` |
104
+ | `processor` | `BatchSpanProcessor` | Custom OpenTelemetry `SpanProcessor` |
105
+ | `instrumentModules` | — | Explicit module references for ESM projects |
106
+ | `pricingEnabled` | `true` | Calculate and attach cost attributes to spans |
107
+ | `pricingJsonPath` | — | Path to custom pricing JSON file |
108
+ | `tracingEnabled` | `true` | Enable/disable tracing entirely |
109
+ | `silenceInitializationMessage` | `false` | Suppress startup console message |
110
+
111
+ ## Supported Providers
112
+
113
+ LLM providers and vector databases instrumented automatically:
114
+
115
+ - **OpenAI** — chat completions, embeddings
116
+ - **Anthropic** — messages
117
+ - **AWS Bedrock** — invoke model
118
+ - **Google Vertex AI** — generative models
119
+ - **Cohere** — chat, embed, rerank
120
+ - **Together AI** — chat completions
121
+ - **LangChain** — chains, agents, tools
122
+ - **LlamaIndex** — query engines, retrievers
123
+ - **Pinecone** — vector operations
124
+ - **ChromaDB** — collections, queries
125
+ - **Qdrant** — points, search
126
+ - **MCP** — Model Context Protocol client calls
127
+
128
+ ## Pricing
129
+
130
+ Cost calculation is enabled by default. The SDK matches model names from spans against bundled pricing data and sets these attributes:
131
+
132
+ - `gen_ai.usage.input_cost`
133
+ - `gen_ai.usage.output_cost`
134
+ - `gen_ai.usage.cost`
135
+
136
+ To use custom pricing data:
137
+
138
+ ```typescript
139
+ initialize({
140
+ pricingJsonPath: "./my-pricing.json",
141
+ });
142
+ ```
143
+
144
+ To disable:
145
+
146
+ ```typescript
147
+ initialize({
148
+ pricingEnabled: false,
149
+ });
150
+ ```
151
+
152
+ ## License
153
+
154
+ Apache-2.0
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { SpanExporter, SpanProcessor } from '@opentelemetry/sdk-trace-base';
1
+ import { SpanExporter, SpanProcessor, ReadableSpan } from '@opentelemetry/sdk-trace-base';
2
2
  import * as _opentelemetry_api from '@opentelemetry/api';
3
3
  import { TextMapPropagator, ContextManager, Span } from '@opentelemetry/api';
4
4
  import * as openai from 'openai';
@@ -168,6 +168,16 @@ interface InitializeOptions {
168
168
  * This is used to configure the Google Cloud Trace Exporter.
169
169
  */
170
170
  gcpProjectId?: string;
171
+ /**
172
+ * Whether to calculate and add cost attributes to spans. Optional.
173
+ * Defaults to true.
174
+ */
175
+ pricingEnabled?: boolean;
176
+ /**
177
+ * Path to a custom pricing JSON file. Optional.
178
+ * If not set, uses the bundled default pricing data.
179
+ */
180
+ pricingJsonPath?: string;
171
181
  }
172
182
 
173
183
  /**
@@ -1859,8 +1869,31 @@ declare function withAssociationProperties<A extends unknown[], F extends (...ar
1859
1869
  */
1860
1870
  declare const reportCustomMetric: (metricName: string, metricValue: number) => void;
1861
1871
 
1872
+ interface NormalizedPricing {
1873
+ inputCostPerToken: number;
1874
+ outputCostPerToken: number;
1875
+ }
1876
+ interface ModelPricing {
1877
+ promptPrice: number;
1878
+ completionPrice: number;
1879
+ }
1880
+ interface PricingData {
1881
+ chat?: Record<string, ModelPricing>;
1882
+ embeddings?: Record<string, number>;
1883
+ images?: Record<string, unknown>;
1884
+ audio?: Record<string, unknown>;
1885
+ }
1886
+ declare class PricingCalculator {
1887
+ private chatModels;
1888
+ constructor(pricingData: PricingData);
1889
+ findPricing(modelName: string): NormalizedPricing | null;
1890
+ addCostAttributes(span: ReadableSpan): void;
1891
+ private normalize;
1892
+ }
1893
+
1862
1894
  declare const ALL_INSTRUMENTATION_LIBRARIES: "all";
1863
1895
  type AllInstrumentationLibraries = typeof ALL_INSTRUMENTATION_LIBRARIES;
1896
+ declare const setPricingCalculator: (calculator: PricingCalculator | null) => void;
1864
1897
  interface SpanProcessorOptions {
1865
1898
  /**
1866
1899
  * The API Key for sending traces data. Optional.
@@ -1953,5 +1986,5 @@ declare class ImageUploader {
1953
1986
  private uploadImageData;
1954
1987
  }
1955
1988
 
1956
- export { ALL_INSTRUMENTATION_LIBRARIES, ArgumentNotProvidedError, AssociationProperty, Attachment, AttachmentReference, AttachmentUploader, Column, Dataset, Datasets, Evaluator, EvaluatorMadeByTraceloop, Experiment, ExternalAttachment, ImageUploader, InitializationError, LLMSpan, NotInitializedError, PromptNotFoundError, Row, SEVERITY, TraceloopClient, TraceloopError, VectorSpan, agent, attachment, conversation, createEvaluator, createSpanProcessor, forceFlush, getAvailableEvaluatorSlugs, getClient, getEvaluatorSchemaInfo, getPrompt, getTraceloopTracer, initialize, isAnyAttachment, isAttachment, isAttachmentReference, isExternalAttachment, reportCustomMetric, task, tool, traceloopInstrumentationLibraries, validateEvaluatorInput, waitForInitialization, withAgent, withAssociationProperties, withConversation, withLLMCall, withTask, withTool, withVectorDBCall, withWorkflow, workflow };
1989
+ export { ALL_INSTRUMENTATION_LIBRARIES, ArgumentNotProvidedError, AssociationProperty, Attachment, AttachmentReference, AttachmentUploader, Column, Dataset, Datasets, Evaluator, EvaluatorMadeByTraceloop, Experiment, ExternalAttachment, ImageUploader, InitializationError, LLMSpan, NotInitializedError, PromptNotFoundError, Row, SEVERITY, TraceloopClient, TraceloopError, VectorSpan, agent, attachment, conversation, createEvaluator, createSpanProcessor, forceFlush, getAvailableEvaluatorSlugs, getClient, getEvaluatorSchemaInfo, getPrompt, getTraceloopTracer, initialize, isAnyAttachment, isAttachment, isAttachmentReference, isExternalAttachment, reportCustomMetric, setPricingCalculator, task, tool, traceloopInstrumentationLibraries, validateEvaluatorInput, waitForInitialization, withAgent, withAssociationProperties, withConversation, withLLMCall, withTask, withTool, withVectorDBCall, withWorkflow, workflow };
1957
1990
  export type { AnnotationCreateOptions, AttachmentMetadata, AttachmentOptions, CSVImportOptions, ColumnDefinition, ColumnResponse, ColumnUpdateOptions, DatasetCreateOptions, DatasetListResponse, DatasetPublishOptions, DatasetResponse, DatasetUpdateOptions, DatasetVersion, DatasetVersionsResponse, DecoratorConfig, EvaluatorDetails, EvaluatorWithConfig, EvaluatorWithVersion, ExecutionResponse, ExperimentRunOptions, ExperimentRunResult, ExperimentTaskFunction, ExternalAttachmentOptions, FileCellType, FileStorageType, GithubContext, InitializeOptions, RowData, RowResponse, RowUpdateOptions, RunInGithubOptions, RunInGithubResponse, SSEStreamEvent, Severity, SpanProcessorOptions, StreamEvent, TaskInput, TaskOutput, TaskResponse, TaskResult, TraceloopClientOptions, UploadUrlResponse };
package/dist/index.js CHANGED
@@ -90,7 +90,7 @@ class PromptNotFoundError extends TraceloopError {
90
90
  }
91
91
  }
92
92
 
93
- var version = "0.22.9";
93
+ var version = "0.22.10";
94
94
 
95
95
  /**
96
96
  * Base class for handling annotation operations with the Traceloop API.
@@ -3262,6 +3262,10 @@ function parseKeyPairsIntoRecord(keyPairs) {
3262
3262
  }
3263
3263
 
3264
3264
  const ALL_INSTRUMENTATION_LIBRARIES = "all";
3265
+ let _pricingCalculator = null;
3266
+ const setPricingCalculator = (calculator) => {
3267
+ _pricingCalculator = calculator;
3268
+ };
3265
3269
  const spanAgentNames = new Map();
3266
3270
  const SPAN_AGENT_NAME_TTL = 5 * 60 * 1000;
3267
3271
  const AI_TELEMETRY_METADATA_AGENT = "ai.telemetry.metadata.agent";
@@ -3411,13 +3415,9 @@ const ensureSpanCompatibility = (span) => {
3411
3415
  const onSpanEnd = (originalOnEnd, instrumentationLibraries) => {
3412
3416
  return (span) => {
3413
3417
  var _a, _b, _c;
3414
- const libName = ((_a = span.instrumentationScope) === null || _a === void 0 ? void 0 : _a.name) ||
3415
- ((_b = span.instrumentationLibrary) === null || _b === void 0 ? void 0 : _b.name) ||
3416
- "unknown";
3417
- console.log(`[Anyway Debug] onSpanEnd: span="${span.name}" lib="${libName}"`);
3418
3418
  if (instrumentationLibraries &&
3419
- !instrumentationLibraries.includes(libName)) {
3420
- console.log(`[Anyway Debug] Dropping span="${span.name}" - lib="${libName}" not in allowed list`);
3419
+ !instrumentationLibraries.includes(((_a = span.instrumentationScope) === null || _a === void 0 ? void 0 : _a.name) ||
3420
+ ((_b = span.instrumentationLibrary) === null || _b === void 0 ? void 0 : _b.name))) {
3421
3421
  return;
3422
3422
  }
3423
3423
  transformAiSdkSpanAttributes(span);
@@ -3443,8 +3443,10 @@ const onSpanEnd = (originalOnEnd, instrumentationLibraries) => {
3443
3443
  if (Math.random() < 0.01) {
3444
3444
  cleanupExpiredSpanAgentNames();
3445
3445
  }
3446
+ if (_pricingCalculator) {
3447
+ _pricingCalculator.addCostAttributes(span);
3448
+ }
3446
3449
  const compatibleSpan = ensureSpanCompatibility(span);
3447
- console.log(`[Anyway Debug] Span queued for export: "${span.name}" lib="${libName}"`);
3448
3450
  originalOnEnd(compatibleSpan);
3449
3451
  };
3450
3452
  };
@@ -3730,19 +3732,6 @@ const startTracing = (options) => {
3730
3732
  url: `${baseUrl}/v1/traces`,
3731
3733
  headers,
3732
3734
  }));
3733
- // [Anyway Debug] Wrap exporter to log outgoing calls
3734
- const origExport = traceExporter.export.bind(traceExporter);
3735
- traceExporter.export = (spans, resultCallback) => {
3736
- var _a, _b;
3737
- console.log(`[Anyway Debug] Exporting ${spans.length} span(s) to ${baseUrl}/v1/traces`);
3738
- for (const s of spans) {
3739
- console.log(`[Anyway Debug] - ${s.name} (${((_a = s.instrumentationLibrary) === null || _a === void 0 ? void 0 : _a.name) || ((_b = s.instrumentationScope) === null || _b === void 0 ? void 0 : _b.name) || "unknown"})`);
3740
- }
3741
- return origExport(spans, (result) => {
3742
- console.log(`[Anyway Debug] Export result: code=${result.code}${result.error ? ` error=${result.error.message}` : ""}`);
3743
- resultCallback(result);
3744
- });
3745
- };
3746
3735
  _spanProcessor = createSpanProcessor({
3747
3736
  apiKey: options.apiKey,
3748
3737
  baseUrl: options.baseUrl,
@@ -3880,6 +3869,80 @@ const initializeRegistry = (options) => {
3880
3869
  });
3881
3870
  };
3882
3871
 
3872
+ const DATE_SUFFIX_PATTERN = /-\d{4}-\d{2}-\d{2}$/;
3873
+ const DATE_COMPACT_SUFFIX_PATTERN = /-\d{8}$/;
3874
+ class PricingCalculator {
3875
+ constructor(pricingData) {
3876
+ var _a;
3877
+ this.chatModels = (_a = pricingData.chat) !== null && _a !== void 0 ? _a : {};
3878
+ }
3879
+ findPricing(modelName) {
3880
+ if (!modelName)
3881
+ return null;
3882
+ const models = this.chatModels;
3883
+ // 1. Exact match
3884
+ if (models[modelName]) {
3885
+ return this.normalize(models[modelName]);
3886
+ }
3887
+ // 2. Strip date suffix and retry
3888
+ let stripped = modelName.replace(DATE_SUFFIX_PATTERN, "");
3889
+ stripped = stripped.replace(DATE_COMPACT_SUFFIX_PATTERN, "");
3890
+ if (stripped !== modelName && models[stripped]) {
3891
+ return this.normalize(models[stripped]);
3892
+ }
3893
+ // 3. Prefix match (longest wins)
3894
+ let bestMatch = null;
3895
+ let bestLen = 0;
3896
+ for (const baseModel of Object.keys(models)) {
3897
+ if (modelName.startsWith(baseModel) && baseModel.length > bestLen) {
3898
+ bestMatch = baseModel;
3899
+ bestLen = baseModel.length;
3900
+ }
3901
+ }
3902
+ if (bestMatch) {
3903
+ return this.normalize(models[bestMatch]);
3904
+ }
3905
+ return null;
3906
+ }
3907
+ addCostAttributes(span) {
3908
+ const attrs = span.attributes;
3909
+ if (!attrs)
3910
+ return;
3911
+ const model = attrs[incubating.ATTR_GEN_AI_RESPONSE_MODEL] ||
3912
+ attrs[incubating.ATTR_GEN_AI_REQUEST_MODEL];
3913
+ if (!model)
3914
+ return;
3915
+ const inputTokens = attrs[incubating.ATTR_GEN_AI_USAGE_INPUT_TOKENS];
3916
+ const outputTokens = attrs[incubating.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS];
3917
+ if (inputTokens == null && outputTokens == null)
3918
+ return;
3919
+ const pricing = this.findPricing(model);
3920
+ if (!pricing)
3921
+ return;
3922
+ const inputCost = (inputTokens !== null && inputTokens !== void 0 ? inputTokens : 0) * pricing.inputCostPerToken;
3923
+ const outputCost = (outputTokens !== null && outputTokens !== void 0 ? outputTokens : 0) * pricing.outputCostPerToken;
3924
+ span.attributes["gen_ai.usage.input_cost"] = inputCost;
3925
+ span.attributes["gen_ai.usage.output_cost"] = outputCost;
3926
+ span.attributes["gen_ai.usage.cost"] = inputCost + outputCost;
3927
+ }
3928
+ normalize(pricing) {
3929
+ var _a, _b;
3930
+ return {
3931
+ inputCostPerToken: ((_a = pricing.promptPrice) !== null && _a !== void 0 ? _a : 0) / 1000,
3932
+ outputCostPerToken: ((_b = pricing.completionPrice) !== null && _b !== void 0 ? _b : 0) / 1000,
3933
+ };
3934
+ }
3935
+ }
3936
+
3937
+ function loadPricing(pricingJsonPath) {
3938
+ if (pricingJsonPath) {
3939
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
3940
+ const fs = require("fs");
3941
+ return JSON.parse(fs.readFileSync(pricingJsonPath, "utf-8"));
3942
+ }
3943
+ return require("./data/default_pricing.json");
3944
+ }
3945
+
3883
3946
  let _configuration;
3884
3947
  let _client;
3885
3948
  /**
@@ -3942,15 +4005,21 @@ const initialize = (options = {}) => {
3942
4005
  if (!options.silenceInitializationMessage) {
3943
4006
  console.log(`Traceloop exporting traces to ${_configuration.exporter ? "a custom exporter" : _configuration.baseUrl}`);
3944
4007
  }
3945
- console.log(`[Anyway Debug] Trace endpoint: ${_configuration.baseUrl}/v1/traces`);
3946
- console.log(`[Anyway Debug] API key present: ${!!_configuration.apiKey}`);
3947
- console.log(`[Anyway Debug] Using custom exporter: ${!!_configuration.exporter}`);
3948
4008
  if (options.tracingEnabled === undefined || options.tracingEnabled) {
3949
4009
  if (options.logLevel) {
3950
4010
  api.diag.setLogger(new api.DiagConsoleLogger(), logLevelToOtelLogLevel(options.logLevel));
3951
4011
  }
3952
4012
  startTracing(_configuration);
3953
4013
  }
4014
+ if (options.pricingEnabled !== false) {
4015
+ try {
4016
+ const pricingData = loadPricing(options.pricingJsonPath);
4017
+ setPricingCalculator(new PricingCalculator(pricingData));
4018
+ }
4019
+ catch (e) {
4020
+ console.warn(`[Traceloop] Failed to initialize pricing: ${e.message}`);
4021
+ }
4022
+ }
3954
4023
  initializeRegistry(_configuration);
3955
4024
  if (options.apiKey) {
3956
4025
  _client = new TraceloopClient({
@@ -4462,6 +4531,7 @@ exports.isAttachment = isAttachment;
4462
4531
  exports.isAttachmentReference = isAttachmentReference;
4463
4532
  exports.isExternalAttachment = isExternalAttachment;
4464
4533
  exports.reportCustomMetric = reportCustomMetric;
4534
+ exports.setPricingCalculator = setPricingCalculator;
4465
4535
  exports.task = task;
4466
4536
  exports.tool = tool;
4467
4537
  exports.traceloopInstrumentationLibraries = traceloopInstrumentationLibraries;