@revenium/litellm 0.0.1

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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +630 -0
  3. package/dist/client.d.ts +17 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/client.js +713 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/config.d.ts +42 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +332 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/constants.d.ts +15 -0
  12. package/dist/constants.d.ts.map +1 -0
  13. package/dist/constants.js +101 -0
  14. package/dist/constants.js.map +1 -0
  15. package/dist/index.d.ts +42 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +189 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/prompt-extraction.d.ts +11 -0
  20. package/dist/prompt-extraction.d.ts.map +1 -0
  21. package/dist/prompt-extraction.js +201 -0
  22. package/dist/prompt-extraction.js.map +1 -0
  23. package/dist/tracking.d.ts +47 -0
  24. package/dist/tracking.d.ts.map +1 -0
  25. package/dist/tracking.js +299 -0
  26. package/dist/tracking.js.map +1 -0
  27. package/dist/types.d.ts +348 -0
  28. package/dist/types.d.ts.map +1 -0
  29. package/dist/types.js +3 -0
  30. package/dist/types.js.map +1 -0
  31. package/dist/utils/circuit-breaker.d.ts +114 -0
  32. package/dist/utils/circuit-breaker.d.ts.map +1 -0
  33. package/dist/utils/circuit-breaker.js +216 -0
  34. package/dist/utils/circuit-breaker.js.map +1 -0
  35. package/dist/utils/error-handling.d.ts +166 -0
  36. package/dist/utils/error-handling.d.ts.map +1 -0
  37. package/dist/utils/error-handling.js +306 -0
  38. package/dist/utils/error-handling.js.map +1 -0
  39. package/dist/utils/logger-types.d.ts +171 -0
  40. package/dist/utils/logger-types.d.ts.map +1 -0
  41. package/dist/utils/logger-types.js +210 -0
  42. package/dist/utils/logger-types.js.map +1 -0
  43. package/dist/utils/provider-detection.d.ts +43 -0
  44. package/dist/utils/provider-detection.d.ts.map +1 -0
  45. package/dist/utils/provider-detection.js +103 -0
  46. package/dist/utils/provider-detection.js.map +1 -0
  47. package/dist/utils/stop-reason.d.ts +58 -0
  48. package/dist/utils/stop-reason.d.ts.map +1 -0
  49. package/dist/utils/stop-reason.js +136 -0
  50. package/dist/utils/stop-reason.js.map +1 -0
  51. package/dist/utils/summary-printer.d.ts +23 -0
  52. package/dist/utils/summary-printer.d.ts.map +1 -0
  53. package/dist/utils/summary-printer.js +234 -0
  54. package/dist/utils/summary-printer.js.map +1 -0
  55. package/dist/utils/trace-fields.d.ts +10 -0
  56. package/dist/utils/trace-fields.d.ts.map +1 -0
  57. package/dist/utils/trace-fields.js +117 -0
  58. package/dist/utils/trace-fields.js.map +1 -0
  59. package/dist/utils/validation.d.ts +121 -0
  60. package/dist/utils/validation.d.ts.map +1 -0
  61. package/dist/utils/validation.js +451 -0
  62. package/dist/utils/validation.js.map +1 -0
  63. package/examples/README.md +321 -0
  64. package/examples/litellm-basic.ts +240 -0
  65. package/examples/litellm-streaming.ts +309 -0
  66. package/examples/prompt-capture.ts +128 -0
  67. package/package.json +85 -0
@@ -0,0 +1,309 @@
1
+ /**
2
+ * LiteLLM Streaming Example
3
+ *
4
+ * This example demonstrates streaming responses and advanced features with metadata tracking.
5
+ * Shows streaming chat completions, multi-provider support, and advanced metadata usage.
6
+ */
7
+
8
+ // Load environment variables from .env file
9
+ import "dotenv/config";
10
+
11
+ // Step 1: Import the middleware (this enables automatic tracking)
12
+ import "@revenium/litellm";
13
+
14
+ async function streamingExample() {
15
+ console.log("Starting streaming Revenium LiteLLM middleware example...\n");
16
+
17
+ // Check environment variables
18
+ const requiredVars = ["REVENIUM_METERING_API_KEY", "LITELLM_PROXY_URL"];
19
+ const missing = requiredVars.filter((key) => !process.env[key]);
20
+
21
+ if (missing.length > 0) {
22
+ console.error("❌ Missing required environment variables:");
23
+ missing.forEach((key) => console.error(` ${key}`));
24
+ console.error("\nPlease set them in a .env file in the project root.");
25
+ process.exit(1);
26
+ }
27
+
28
+ const proxyUrl = process.env.LITELLM_PROXY_URL!;
29
+ const apiKey = process.env.LITELLM_API_KEY || "sk-1234";
30
+
31
+ // Handle proxy URL - remove endpoint if already included
32
+ const baseProxyUrl = proxyUrl.replace(
33
+ /\/(chat\/completions|embeddings)$/,
34
+ ""
35
+ );
36
+
37
+ // Debug: Show loaded configuration (partially obfuscated)
38
+ console.log("Configuration loaded:");
39
+ console.log(
40
+ ` Revenium API Key: ${process.env.REVENIUM_METERING_API_KEY?.substring(
41
+ 0,
42
+ 8
43
+ )}...${process.env.REVENIUM_METERING_API_KEY?.slice(-4)}`
44
+ );
45
+ console.log(
46
+ ` Revenium Base URL: ${process.env.REVENIUM_METERING_BASE_URL}`
47
+ );
48
+ console.log(` LiteLLM Proxy URL: ${proxyUrl}`);
49
+ console.log(` Base Proxy URL: ${baseProxyUrl}`);
50
+ console.log(
51
+ ` LiteLLM API Key: ${apiKey.substring(0, 8)}...${apiKey.slice(-4)}\n`
52
+ );
53
+
54
+ let successCount = 0;
55
+ let totalRequests = 3;
56
+
57
+ try {
58
+ // Example 1: Basic streaming with metadata
59
+ console.log("Example 1: Basic streaming response with metadata...");
60
+
61
+ const streamResponse = await fetch(`${baseProxyUrl}/chat/completions`, {
62
+ method: "POST",
63
+ headers: {
64
+ "Content-Type": "application/json",
65
+ Authorization: `Bearer ${apiKey}`,
66
+ // Custom metadata for this streaming request
67
+ "x-revenium-subscriber-id": "artist-456",
68
+ "x-revenium-subscriber-email": "artist@creative-ai-inc.com",
69
+ "x-revenium-subscriber-credential-name": "creative-api-key",
70
+ "x-revenium-subscriber-credential": "creative-credential-value",
71
+ "x-revenium-organization-id": "creative-ai-inc",
72
+ "x-revenium-product-id": "story-generator",
73
+ "x-revenium-task-type": "creative-writing",
74
+ "x-revenium-agent": "litellm-node-streaming",
75
+ },
76
+ body: JSON.stringify({
77
+ model: "openai/gpt-4o-mini",
78
+ max_tokens: 150,
79
+ messages: [
80
+ {
81
+ role: "user",
82
+ content: "Tell me a short story about a robot learning to paint.",
83
+ },
84
+ ],
85
+ stream: true,
86
+ }),
87
+ });
88
+
89
+ if (streamResponse.ok && streamResponse.body) {
90
+ console.log("Streaming response:");
91
+
92
+ const reader = streamResponse.body.getReader();
93
+ const decoder = new TextDecoder();
94
+
95
+ while (true) {
96
+ const { done, value } = await reader.read();
97
+ if (done) break;
98
+
99
+ const chunk = decoder.decode(value);
100
+ const lines = chunk.split("\n").filter((line) => line.trim());
101
+
102
+ for (const line of lines) {
103
+ if (line.startsWith("data: ")) {
104
+ const data = line.slice(6);
105
+ if (data === "[DONE]") continue;
106
+
107
+ try {
108
+ const parsed = JSON.parse(data);
109
+ const content = parsed.choices?.[0]?.delta?.content;
110
+ if (content) {
111
+ process.stdout.write(content);
112
+ }
113
+ } catch (e) {
114
+ // Ignore parsing errors for malformed chunks
115
+ }
116
+ }
117
+ }
118
+ }
119
+
120
+ console.log("\n ✅ Stream completed and automatically tracked!\n");
121
+ successCount++;
122
+ } else {
123
+ console.log(
124
+ "❌ Streaming request failed:",
125
+ streamResponse.status,
126
+ streamResponse.statusText
127
+ );
128
+ }
129
+
130
+ // Example 2: Multi-provider streaming
131
+ console.log(
132
+ "Example 2: Multi-provider streaming with different metadata..."
133
+ );
134
+
135
+ const anthropicStreamResponse = await fetch(
136
+ `${baseProxyUrl}/chat/completions`,
137
+ {
138
+ method: "POST",
139
+ headers: {
140
+ "Content-Type": "application/json",
141
+ Authorization: `Bearer ${apiKey}`,
142
+ // Metadata for research provider
143
+ "x-revenium-subscriber-id": "researcher-789",
144
+ "x-revenium-subscriber-email": "researcher@research-users.com",
145
+ "x-revenium-subscriber-credential-name": "research-api-key",
146
+ "x-revenium-subscriber-credential": "research-credential-value",
147
+ "x-revenium-organization-id": "research-users",
148
+ "x-revenium-product-id": "research-assistant",
149
+ "x-revenium-task-type": "research-query",
150
+ "x-revenium-agent": "litellm-node-streaming-multi",
151
+ },
152
+ body: JSON.stringify({
153
+ model: "openai/gpt-4o-mini",
154
+ max_tokens: 100,
155
+ messages: [
156
+ {
157
+ role: "user",
158
+ content: "What are three benefits of renewable energy?",
159
+ },
160
+ ],
161
+ stream: true,
162
+ }),
163
+ }
164
+ );
165
+
166
+ if (anthropicStreamResponse.ok && anthropicStreamResponse.body) {
167
+ console.log("Research streaming response:");
168
+
169
+ const reader = anthropicStreamResponse.body.getReader();
170
+ const decoder = new TextDecoder();
171
+
172
+ while (true) {
173
+ const { done, value } = await reader.read();
174
+ if (done) break;
175
+
176
+ const chunk = decoder.decode(value);
177
+ const lines = chunk.split("\n").filter((line) => line.trim());
178
+
179
+ for (const line of lines) {
180
+ if (line.startsWith("data: ")) {
181
+ const data = line.slice(6);
182
+ if (data === "[DONE]") continue;
183
+
184
+ try {
185
+ const parsed = JSON.parse(data);
186
+ const content = parsed.choices?.[0]?.delta?.content;
187
+ if (content) {
188
+ process.stdout.write(content);
189
+ }
190
+ } catch (e) {
191
+ // Ignore parsing errors
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ console.log(
198
+ "\n ✅ Research stream completed and automatically tracked!\n"
199
+ );
200
+ successCount++;
201
+ } else {
202
+ console.log(
203
+ "❌ Research streaming request failed:",
204
+ anthropicStreamResponse.status,
205
+ anthropicStreamResponse.statusText
206
+ );
207
+ }
208
+
209
+ // Example 3: Advanced metadata with embeddings
210
+ console.log(
211
+ "Example 3: Advanced embeddings with comprehensive metadata..."
212
+ );
213
+
214
+ const advancedEmbeddingResponse = await fetch(
215
+ `${baseProxyUrl}/embeddings`,
216
+ {
217
+ method: "POST",
218
+ headers: {
219
+ "Content-Type": "application/json",
220
+ Authorization: `Bearer ${apiKey}`,
221
+ // Comprehensive metadata for embeddings
222
+ "x-revenium-subscriber-id": "search-user-101",
223
+ "x-revenium-subscriber-email": "user@search-company.com",
224
+ "x-revenium-subscriber-credential-name": "search-api-key",
225
+ "x-revenium-subscriber-credential": "search-credential-value",
226
+ "x-revenium-organization-id": "search-company",
227
+ "x-revenium-product-id": "semantic-search-engine",
228
+ "x-revenium-task-type": "document-indexing",
229
+ "x-revenium-agent": "litellm-node-streaming-embeddings",
230
+ "x-revenium-trace-id": "batch-embedding-session-456",
231
+ },
232
+ body: JSON.stringify({
233
+ model: "text-embedding-ada-002",
234
+ input: [
235
+ "Advanced machine learning techniques for natural language processing",
236
+ "Deep learning architectures for computer vision applications",
237
+ "Reinforcement learning algorithms for autonomous systems",
238
+ ],
239
+ }),
240
+ }
241
+ );
242
+
243
+ if (advancedEmbeddingResponse.ok) {
244
+ const embeddingData = await advancedEmbeddingResponse.json();
245
+ console.log(
246
+ "Advanced embedding response: Multiple vectors generated successfully"
247
+ );
248
+ console.log(` Vectors: ${embeddingData.data?.length || 0}`);
249
+ console.log(
250
+ ` Dimensions: ${
251
+ embeddingData.data?.[0]?.embedding?.length || "Unknown"
252
+ }`
253
+ );
254
+ console.log(` Tokens: ${embeddingData.usage?.prompt_tokens} input\n`);
255
+ successCount++;
256
+ } else {
257
+ console.log(
258
+ "❌ Advanced embedding request failed:",
259
+ advancedEmbeddingResponse.status,
260
+ advancedEmbeddingResponse.statusText
261
+ );
262
+ }
263
+
264
+ // Report results
265
+ console.log(
266
+ `\nResults: ${successCount}/${totalRequests} requests successful`
267
+ );
268
+
269
+ if (successCount === totalRequests) {
270
+ console.log(
271
+ "✅ All streaming requests successful and automatically tracked to Revenium!"
272
+ );
273
+ console.log(
274
+ "Check your Revenium dashboard to see the detailed analytics"
275
+ );
276
+ } else if (successCount > 0) {
277
+ console.log(
278
+ "⚠️ Some streaming requests successful and tracked to Revenium."
279
+ );
280
+ console.log("Check your Revenium dashboard to see the tracked usage.");
281
+ } else {
282
+ console.log(
283
+ "❌ No requests were successful. Check your LiteLLM Proxy configuration."
284
+ );
285
+ console.log(
286
+ "Ensure your LiteLLM Proxy is running and accessible at:",
287
+ proxyUrl
288
+ );
289
+ }
290
+ } catch (error) {
291
+ console.error("❌ Error in streaming example:", error);
292
+ throw error;
293
+ }
294
+ }
295
+
296
+ // Run the example
297
+ if (require.main === module) {
298
+ streamingExample()
299
+ .then(() => {
300
+ console.log("\nStreaming example completed successfully!");
301
+ console.log(
302
+ "Enable REVENIUM_DEBUG=true to see detailed request tracking logs"
303
+ );
304
+ })
305
+ .catch((error) => {
306
+ console.error("\nStreaming example failed:", error);
307
+ process.exit(1);
308
+ });
309
+ }
@@ -0,0 +1,128 @@
1
+ import { configure, UsageMetadata } from "../src/index.js";
2
+
3
+ async function main() {
4
+ console.log("=== LiteLLM Prompt Capture Example ===\n");
5
+
6
+ // Configure the middleware with prompt capture enabled
7
+ configure({
8
+ reveniumMeteringApiKey: process.env.REVENIUM_METERING_API_KEY || "test-key",
9
+ reveniumMeteringBaseUrl:
10
+ process.env.REVENIUM_METERING_BASE_URL || "https://api.revenium.ai",
11
+ litellmProxyUrl: process.env.LITELLM_PROXY_URL || "http://localhost:4000",
12
+ litellmApiKey: process.env.LITELLM_API_KEY,
13
+ capturePrompts: true,
14
+ });
15
+
16
+ console.log("Example 1: Prompt capture enabled via config");
17
+ console.log("Making request with prompt capture enabled...\n");
18
+
19
+ try {
20
+ const metadata: UsageMetadata = {
21
+ organizationId: "org-prompt-capture-demo",
22
+ productId: "prod-litellm-prompt-capture",
23
+ };
24
+
25
+ const response = await fetch(
26
+ `${
27
+ process.env.LITELLM_PROXY_URL || "http://localhost:4000"
28
+ }/chat/completions`,
29
+ {
30
+ method: "POST",
31
+ headers: {
32
+ "Content-Type": "application/json",
33
+ Authorization: `Bearer ${process.env.LITELLM_API_KEY || ""}`,
34
+ "X-Revenium-Organization-Id": metadata.organizationId || "",
35
+ "X-Revenium-Product-Id": metadata.productId || "",
36
+ },
37
+ body: JSON.stringify({
38
+ model: "gpt-4o-mini",
39
+ messages: [
40
+ {
41
+ role: "system",
42
+ content:
43
+ "You are a helpful assistant that provides concise answers.",
44
+ },
45
+ {
46
+ role: "user",
47
+ content: "What is the capital of France?",
48
+ },
49
+ ],
50
+ max_tokens: 100,
51
+ }),
52
+ }
53
+ );
54
+
55
+ const data = await response.json();
56
+ console.log("Response:", data.choices?.[0]?.message?.content);
57
+ console.log("\nPrompts captured and sent to Revenium API!");
58
+ } catch (error) {
59
+ console.error(
60
+ "Error:",
61
+ error instanceof Error ? error.message : String(error)
62
+ );
63
+ }
64
+
65
+ console.log("\n" + "=".repeat(50) + "\n");
66
+ console.log("Example 2: Prompt capture disabled via metadata override");
67
+ console.log("Making request with prompt capture disabled...\n");
68
+
69
+ try {
70
+ const metadata2: UsageMetadata = {
71
+ organizationId: "org-prompt-capture-demo",
72
+ productId: "prod-litellm-prompt-capture",
73
+ capturePrompts: false,
74
+ };
75
+
76
+ const response2 = await fetch(
77
+ `${
78
+ process.env.LITELLM_PROXY_URL || "http://localhost:4000"
79
+ }/chat/completions`,
80
+ {
81
+ method: "POST",
82
+ headers: {
83
+ "Content-Type": "application/json",
84
+ Authorization: `Bearer ${process.env.LITELLM_API_KEY || ""}`,
85
+ "X-Revenium-Organization-Id": metadata2.organizationId || "",
86
+ "X-Revenium-Product-Id": metadata2.productId || "",
87
+ "X-Revenium-Capture-Prompts": "false",
88
+ },
89
+ body: JSON.stringify({
90
+ model: "gpt-4o-mini",
91
+ messages: [
92
+ {
93
+ role: "system",
94
+ content: "You are a helpful assistant.",
95
+ },
96
+ {
97
+ role: "user",
98
+ content: "What is 2+2?",
99
+ },
100
+ ],
101
+ max_tokens: 100,
102
+ }),
103
+ }
104
+ );
105
+
106
+ const data2 = await response2.json();
107
+ console.log("Response:", data2.choices?.[0]?.message?.content);
108
+ console.log("\nPrompts NOT captured (overridden via metadata)!");
109
+ } catch (error) {
110
+ console.error(
111
+ "Error:",
112
+ error instanceof Error ? error.message : String(error)
113
+ );
114
+ }
115
+
116
+ console.log("\n" + "=".repeat(50) + "\n");
117
+ console.log("Example 3: Prompt capture with environment variable");
118
+ console.log("Set REVENIUM_CAPTURE_PROMPTS=true in your .env file\n");
119
+
120
+ console.log("✅ Prompt capture examples completed!");
121
+ console.log("\nConfiguration hierarchy:");
122
+ console.log("1. Per-call metadata (highest priority)");
123
+ console.log("2. Global config");
124
+ console.log("3. Environment variable REVENIUM_CAPTURE_PROMPTS");
125
+ console.log("4. Default: false (lowest priority)");
126
+ }
127
+
128
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@revenium/litellm",
3
+ "version": "0.0.1",
4
+ "description": "Comprehensive middleware for Node.js applications using LiteLLM Proxy to automatically track LLM usage, costs, and performance metrics with Revenium",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "dev": "tsc --watch",
10
+ "clean": "rm -rf dist",
11
+ "prepublishOnly": "npm run build",
12
+ "test": "jest",
13
+ "test:unit": "jest --testPathPatterns=tests/unit",
14
+ "test:integration": "jest --testPathPatterns=tests/integration --passWithNoTests",
15
+ "test:performance": "jest --testPathPatterns=tests/performance --passWithNoTests",
16
+ "test:watch": "jest --watch",
17
+ "test:coverage": "jest --coverage",
18
+ "test:e2e": "npm run build && tsx tests/integration/09-trace-fields-integration.ts",
19
+ "test:basic": "REVENIUM_DEBUG=true npx tsx tests/integration/01-basic-connectivity.ts",
20
+ "test:proxy": "REVENIUM_DEBUG=true npx tsx tests/integration/02-proxy-requests.ts",
21
+ "test:metadata": "REVENIUM_DEBUG=true npx tsx tests/integration/03-metadata-tracking.ts",
22
+ "test:providers": "REVENIUM_DEBUG=true npx tsx tests/integration/04-multi-provider.ts",
23
+ "test:streaming": "REVENIUM_DEBUG=true npx tsx tests/integration/05-streaming.ts",
24
+ "test:embeddings": "REVENIUM_DEBUG=true npx tsx tests/integration/06-embeddings.ts",
25
+ "test:model-source": "REVENIUM_DEBUG=true npx tsx tests/integration/07-model-source-test.ts",
26
+ "test:all-integration": "npm run test:basic && npm run test:proxy && npm run test:metadata && npm run test:providers && npm run test:streaming && npm run test:embeddings && npm run test:model-source"
27
+ },
28
+ "keywords": [
29
+ "revenium",
30
+ "litellm",
31
+ "llm",
32
+ "ai",
33
+ "middleware",
34
+ "tracking",
35
+ "analytics",
36
+ "openai",
37
+ "anthropic",
38
+ "streaming",
39
+ "usage",
40
+ "metrics",
41
+ "monitoring"
42
+ ],
43
+ "author": "Revenium Inc.",
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/revenium/revenium-middleware-litellm-node.git"
48
+ },
49
+ "bugs": {
50
+ "url": "https://github.com/revenium/revenium-middleware-litellm-node/issues"
51
+ },
52
+ "homepage": "https://github.com/revenium/revenium-middleware-litellm-node#readme",
53
+ "files": [
54
+ "dist/**/*.js",
55
+ "dist/**/*.d.ts",
56
+ "dist/**/*.map",
57
+ "examples/**/*",
58
+ "README.md",
59
+ "LICENSE"
60
+ ],
61
+ "engines": {
62
+ "node": ">=18.0.0"
63
+ },
64
+ "devDependencies": {
65
+ "@types/jest": "^30.0.0",
66
+ "@types/node": "^20.0.0",
67
+ "@types/node-fetch": "^2.6.4",
68
+ "dotenv": "^16.0.0",
69
+ "jest": "^30.2.0",
70
+ "node-fetch": "^2.6.7",
71
+ "ts-jest": "^29.4.6",
72
+ "ts-node": "^10.9.0",
73
+ "tsx": "^4.0.0",
74
+ "typescript": "^5.0.0"
75
+ },
76
+ "dependencies": {},
77
+ "peerDependencies": {
78
+ "node-fetch": "^2.6.0 || ^3.0.0"
79
+ },
80
+ "peerDependenciesMeta": {
81
+ "node-fetch": {
82
+ "optional": true
83
+ }
84
+ }
85
+ }