@acarmisc/backstage-plugin-litellm-backend 0.1.11 → 0.1.12
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/dist/client.d.ts +17 -2
- package/dist/client.js +167 -9
- package/dist/index.cjs.js +151 -5604
- package/dist/index.cjs.js.map +4 -4
- package/dist/plugin.js +2 -2
- package/dist/provisioning.d.ts +72 -0
- package/dist/provisioning.js +196 -0
- package/dist/router.d.ts +4 -1
- package/dist/router.js +73 -108
- package/dist/types.d.ts +71 -13
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LiteLLMConfig, UserInfo, VirtualKey, ModelInfo, UsageMetrics, TeamInfo, GenerateKeyRequest, GenerateKeyResponse, DeleteKeyRequest, CreateUserRequest, CreateUserResponse } from './types';
|
|
1
|
+
import { LiteLLMConfig, UserInfo, VirtualKey, ModelInfo, UsageMetrics, TeamInfo, GenerateKeyRequest, GenerateKeyResponse, UpdateKeyRequest, DeleteKeyRequest, CreateUserRequest, CreateUserResponse } from './types';
|
|
2
2
|
export declare class LiteLLMClient {
|
|
3
3
|
private baseUrl;
|
|
4
4
|
private masterKey;
|
|
@@ -13,11 +13,26 @@ export declare class LiteLLMClient {
|
|
|
13
13
|
createUser(payload: CreateUserRequest): Promise<CreateUserResponse>;
|
|
14
14
|
listKeys(userId?: string): Promise<VirtualKey[]>;
|
|
15
15
|
generateKey(request: GenerateKeyRequest): Promise<GenerateKeyResponse>;
|
|
16
|
+
updateKey(request: UpdateKeyRequest): Promise<VirtualKey>;
|
|
16
17
|
deleteKeys(request: DeleteKeyRequest): Promise<{
|
|
17
18
|
success: boolean;
|
|
18
19
|
}>;
|
|
19
20
|
listModels(): Promise<ModelInfo[]>;
|
|
20
21
|
getTeamInfo(teamId: string): Promise<TeamInfo>;
|
|
21
|
-
|
|
22
|
+
private emptyUsage;
|
|
23
|
+
/**
|
|
24
|
+
* Transforms LiteLLM's SpendAnalyticsPaginatedResponse into the flatter
|
|
25
|
+
* UsageMetrics shape consumed by the frontend charts.
|
|
26
|
+
*
|
|
27
|
+
* Source shape (per result row):
|
|
28
|
+
* { date, metrics, breakdown: { models: { [name]: { metrics, api_key_breakdown: { [keyHash]: { metrics, metadata } } } } } }
|
|
29
|
+
*
|
|
30
|
+
* We fan that out into three views the UI consumes:
|
|
31
|
+
* - daily_usage → spend + request trends over time
|
|
32
|
+
* - usage_by_model → which models drove cost / traffic
|
|
33
|
+
* - usage_by_key → which keys drove cost / traffic (with key_alias + team_id from metadata)
|
|
34
|
+
*/
|
|
35
|
+
private transformDailyActivity;
|
|
36
|
+
getUsage(startDate: string, endDate: string, userId?: string, _groupBy?: string): Promise<UsageMetrics>;
|
|
22
37
|
getTeamUsage(teamId: string, startDate: string, endDate: string): Promise<UsageMetrics>;
|
|
23
38
|
}
|
package/dist/client.js
CHANGED
|
@@ -56,11 +56,25 @@ class LiteLLMClient {
|
|
|
56
56
|
}
|
|
57
57
|
async listKeys(userId) {
|
|
58
58
|
const query = userId ? `?user_id=${encodeURIComponent(userId)}` : '';
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
try {
|
|
60
|
+
const response = await this.request(`/key/info${query}`);
|
|
61
|
+
return Array.isArray(response) ? response : (response.info ?? []);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
if (err.status === 404 || err.message.includes('not found')) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
61
69
|
}
|
|
62
70
|
async generateKey(request) {
|
|
63
71
|
return this.request('/key/generate', {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
body: JSON.stringify({ json: request }),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
async updateKey(request) {
|
|
77
|
+
return this.request('/key/update', {
|
|
64
78
|
method: 'POST',
|
|
65
79
|
body: JSON.stringify(request),
|
|
66
80
|
});
|
|
@@ -78,17 +92,161 @@ class LiteLLMClient {
|
|
|
78
92
|
async getTeamInfo(teamId) {
|
|
79
93
|
return this.request(`/team/info?team_id=${encodeURIComponent(teamId)}`);
|
|
80
94
|
}
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
emptyUsage() {
|
|
96
|
+
return {
|
|
97
|
+
total_spend: 0,
|
|
98
|
+
total_tokens: 0,
|
|
99
|
+
prompt_tokens: 0,
|
|
100
|
+
completion_tokens: 0,
|
|
101
|
+
api_requests: 0,
|
|
102
|
+
successful_requests: 0,
|
|
103
|
+
failed_requests: 0,
|
|
104
|
+
usage_by_model: {},
|
|
105
|
+
usage_by_key: {},
|
|
106
|
+
daily_usage: [],
|
|
107
|
+
daily_by_model: [],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Transforms LiteLLM's SpendAnalyticsPaginatedResponse into the flatter
|
|
112
|
+
* UsageMetrics shape consumed by the frontend charts.
|
|
113
|
+
*
|
|
114
|
+
* Source shape (per result row):
|
|
115
|
+
* { date, metrics, breakdown: { models: { [name]: { metrics, api_key_breakdown: { [keyHash]: { metrics, metadata } } } } } }
|
|
116
|
+
*
|
|
117
|
+
* We fan that out into three views the UI consumes:
|
|
118
|
+
* - daily_usage → spend + request trends over time
|
|
119
|
+
* - usage_by_model → which models drove cost / traffic
|
|
120
|
+
* - usage_by_key → which keys drove cost / traffic (with key_alias + team_id from metadata)
|
|
121
|
+
*/
|
|
122
|
+
transformDailyActivity(response) {
|
|
123
|
+
const results = Array.isArray(response?.results) ? response.results : [];
|
|
124
|
+
const meta = response?.metadata ?? {};
|
|
125
|
+
const daily_usage = results
|
|
126
|
+
.map(r => ({
|
|
127
|
+
date: r.date,
|
|
128
|
+
spend: r.metrics?.spend ?? 0,
|
|
129
|
+
total_tokens: r.metrics?.total_tokens ?? 0,
|
|
130
|
+
prompt_tokens: r.metrics?.prompt_tokens ?? 0,
|
|
131
|
+
completion_tokens: r.metrics?.completion_tokens ?? 0,
|
|
132
|
+
api_requests: r.metrics?.api_requests ?? 0,
|
|
133
|
+
successful_requests: r.metrics?.successful_requests ?? 0,
|
|
134
|
+
failed_requests: r.metrics?.failed_requests ?? 0,
|
|
135
|
+
}))
|
|
136
|
+
.sort((a, b) => a.date.localeCompare(b.date));
|
|
137
|
+
const usage_by_model = {};
|
|
138
|
+
const usage_by_key = {};
|
|
139
|
+
const daily_by_model = [];
|
|
140
|
+
const emptyModelBucket = () => ({
|
|
141
|
+
total_spend: 0,
|
|
142
|
+
total_tokens: 0,
|
|
143
|
+
prompt_tokens: 0,
|
|
144
|
+
completion_tokens: 0,
|
|
145
|
+
api_requests: 0,
|
|
146
|
+
successful_requests: 0,
|
|
147
|
+
failed_requests: 0,
|
|
148
|
+
});
|
|
149
|
+
for (const r of results) {
|
|
150
|
+
const models = r.breakdown?.models ?? {};
|
|
151
|
+
for (const [name, entry] of Object.entries(models)) {
|
|
152
|
+
const m = entry?.metrics ?? {};
|
|
153
|
+
const bucket = usage_by_model[name] ?? emptyModelBucket();
|
|
154
|
+
bucket.total_spend += m.spend ?? 0;
|
|
155
|
+
bucket.total_tokens += m.total_tokens ?? 0;
|
|
156
|
+
bucket.prompt_tokens += m.prompt_tokens ?? 0;
|
|
157
|
+
bucket.completion_tokens += m.completion_tokens ?? 0;
|
|
158
|
+
bucket.api_requests += m.api_requests ?? 0;
|
|
159
|
+
bucket.successful_requests += m.successful_requests ?? 0;
|
|
160
|
+
bucket.failed_requests += m.failed_requests ?? 0;
|
|
161
|
+
usage_by_model[name] = bucket;
|
|
162
|
+
daily_by_model.push({
|
|
163
|
+
date: r.date,
|
|
164
|
+
model: name,
|
|
165
|
+
spend: m.spend ?? 0,
|
|
166
|
+
prompt_tokens: m.prompt_tokens ?? 0,
|
|
167
|
+
completion_tokens: m.completion_tokens ?? 0,
|
|
168
|
+
total_tokens: m.total_tokens ?? 0,
|
|
169
|
+
api_requests: m.api_requests ?? 0,
|
|
170
|
+
successful_requests: m.successful_requests ?? 0,
|
|
171
|
+
failed_requests: m.failed_requests ?? 0,
|
|
172
|
+
});
|
|
173
|
+
const keyMap = entry?.api_key_breakdown ?? {};
|
|
174
|
+
for (const [keyHash, keyEntry] of Object.entries(keyMap)) {
|
|
175
|
+
const km = keyEntry?.metrics ?? {};
|
|
176
|
+
const kmeta = keyEntry?.metadata ?? {};
|
|
177
|
+
const kb = usage_by_key[keyHash] ?? {
|
|
178
|
+
key_alias: kmeta.key_alias,
|
|
179
|
+
team_id: kmeta.team_id ?? null,
|
|
180
|
+
models: [],
|
|
181
|
+
...emptyModelBucket(),
|
|
182
|
+
};
|
|
183
|
+
if (!kb.key_alias && kmeta.key_alias)
|
|
184
|
+
kb.key_alias = kmeta.key_alias;
|
|
185
|
+
if (kb.team_id == null && kmeta.team_id)
|
|
186
|
+
kb.team_id = kmeta.team_id;
|
|
187
|
+
if (!kb.models.includes(name))
|
|
188
|
+
kb.models.push(name);
|
|
189
|
+
kb.total_spend += km.spend ?? 0;
|
|
190
|
+
kb.total_tokens += km.total_tokens ?? 0;
|
|
191
|
+
kb.prompt_tokens += km.prompt_tokens ?? 0;
|
|
192
|
+
kb.completion_tokens += km.completion_tokens ?? 0;
|
|
193
|
+
kb.api_requests += km.api_requests ?? 0;
|
|
194
|
+
kb.successful_requests += km.successful_requests ?? 0;
|
|
195
|
+
kb.failed_requests += km.failed_requests ?? 0;
|
|
196
|
+
usage_by_key[keyHash] = kb;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
total_spend: meta.total_spend ?? 0,
|
|
202
|
+
total_tokens: meta.total_tokens ?? 0,
|
|
203
|
+
prompt_tokens: meta.total_prompt_tokens ?? 0,
|
|
204
|
+
completion_tokens: meta.total_completion_tokens ?? 0,
|
|
205
|
+
api_requests: meta.total_api_requests ?? 0,
|
|
206
|
+
successful_requests: meta.total_successful_requests ?? 0,
|
|
207
|
+
failed_requests: meta.total_failed_requests ?? 0,
|
|
208
|
+
usage_by_model,
|
|
209
|
+
usage_by_key,
|
|
210
|
+
daily_usage,
|
|
211
|
+
daily_by_model,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
async getUsage(startDate, endDate, userId, _groupBy) {
|
|
215
|
+
const params = new URLSearchParams({
|
|
216
|
+
start_date: startDate,
|
|
217
|
+
end_date: endDate,
|
|
218
|
+
page_size: '100',
|
|
219
|
+
});
|
|
83
220
|
if (userId)
|
|
84
221
|
params.append('user_id', userId);
|
|
85
|
-
|
|
86
|
-
params.
|
|
87
|
-
|
|
222
|
+
try {
|
|
223
|
+
const response = await this.request(`/user/daily/activity?${params.toString()}`);
|
|
224
|
+
return this.transformDailyActivity(response);
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
if (err.status === 404 || err.message.includes('not found')) {
|
|
228
|
+
return this.emptyUsage();
|
|
229
|
+
}
|
|
230
|
+
throw err;
|
|
231
|
+
}
|
|
88
232
|
}
|
|
89
233
|
async getTeamUsage(teamId, startDate, endDate) {
|
|
90
|
-
const params = new URLSearchParams({
|
|
91
|
-
|
|
234
|
+
const params = new URLSearchParams({
|
|
235
|
+
start_date: startDate,
|
|
236
|
+
end_date: endDate,
|
|
237
|
+
team_ids: teamId,
|
|
238
|
+
page_size: '100',
|
|
239
|
+
});
|
|
240
|
+
try {
|
|
241
|
+
const response = await this.request(`/team/daily/activity?${params.toString()}`);
|
|
242
|
+
return this.transformDailyActivity(response);
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
if (err.status === 404 || err.message.includes('not found')) {
|
|
246
|
+
return this.emptyUsage();
|
|
247
|
+
}
|
|
248
|
+
throw err;
|
|
249
|
+
}
|
|
92
250
|
}
|
|
93
251
|
}
|
|
94
252
|
exports.LiteLLMClient = LiteLLMClient;
|