@anyway-sh/node-server-sdk 0.22.8 → 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 +154 -0
- package/dist/index.d.ts +35 -2
- package/dist/index.js +92 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +92 -2
- package/dist/src/lib/interfaces/initialize-options.interface.d.ts +10 -0
- package/dist/src/lib/pricing/calculator.d.ts +24 -0
- package/dist/src/lib/pricing/index.d.ts +4 -0
- package/dist/src/lib/pricing/loader.d.ts +3 -0
- package/dist/src/lib/tracing/span-processor.d.ts +2 -0
- package/package.json +1 -1
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.
|
|
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";
|
|
@@ -3439,6 +3443,9 @@ const onSpanEnd = (originalOnEnd, instrumentationLibraries) => {
|
|
|
3439
3443
|
if (Math.random() < 0.01) {
|
|
3440
3444
|
cleanupExpiredSpanAgentNames();
|
|
3441
3445
|
}
|
|
3446
|
+
if (_pricingCalculator) {
|
|
3447
|
+
_pricingCalculator.addCostAttributes(span);
|
|
3448
|
+
}
|
|
3442
3449
|
const compatibleSpan = ensureSpanCompatibility(span);
|
|
3443
3450
|
originalOnEnd(compatibleSpan);
|
|
3444
3451
|
};
|
|
@@ -3862,6 +3869,80 @@ const initializeRegistry = (options) => {
|
|
|
3862
3869
|
});
|
|
3863
3870
|
};
|
|
3864
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
|
+
|
|
3865
3946
|
let _configuration;
|
|
3866
3947
|
let _client;
|
|
3867
3948
|
/**
|
|
@@ -3930,6 +4011,15 @@ const initialize = (options = {}) => {
|
|
|
3930
4011
|
}
|
|
3931
4012
|
startTracing(_configuration);
|
|
3932
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
|
+
}
|
|
3933
4023
|
initializeRegistry(_configuration);
|
|
3934
4024
|
if (options.apiKey) {
|
|
3935
4025
|
_client = new TraceloopClient({
|
|
@@ -4441,6 +4531,7 @@ exports.isAttachment = isAttachment;
|
|
|
4441
4531
|
exports.isAttachmentReference = isAttachmentReference;
|
|
4442
4532
|
exports.isExternalAttachment = isExternalAttachment;
|
|
4443
4533
|
exports.reportCustomMetric = reportCustomMetric;
|
|
4534
|
+
exports.setPricingCalculator = setPricingCalculator;
|
|
4444
4535
|
exports.task = task;
|
|
4445
4536
|
exports.tool = tool;
|
|
4446
4537
|
exports.traceloopInstrumentationLibraries = traceloopInstrumentationLibraries;
|