1
/** @jest-environment node */
3
import { pruneDatabase } from "@/src/__tests__/test-utils";
4
import { ModelUsageUnit } from "@/src/constants";
5
import { prisma } from "@/src/server/db";
7
describe("cost retrieval tests", () => {
8
beforeEach(async () => await pruneDatabase());
12
testDescription: "prompt and completion tokens",
14
completionTokens: 3000,
15
totalTokens: undefined,
16
inputPrice: "0.0000010",
17
outputPrice: "0.0000020",
18
totalPrice: undefined,
19
expectedPromptTokens: 200,
20
expectedCompletionTokens: 3000,
21
expectedTotalTokens: 0,
22
expectedInputCost: "0.0002", // 200 / 1000 * 0.0010
23
expectedOutputCost: "0.006", // 3000 / 1000 * 0.0020
24
expectedTotalCost: "0.0062", // 0.0002 + 0.006
27
testDescription: "missing completion tokens",
29
completionTokens: undefined,
30
totalTokens: undefined,
31
inputPrice: "0.0000010",
32
outputPrice: "0.0000020",
33
totalPrice: undefined,
34
expectedPromptTokens: 200,
35
expectedCompletionTokens: 0,
36
expectedTotalTokens: 0,
37
expectedInputCost: "0.0002",
38
expectedOutputCost: "0", // completionTokens are set to 0 when ingesting undefined, hence 0 cost
39
expectedTotalCost: "0.0002",
42
testDescription: "missing prompt tokens",
43
promptTokens: undefined,
44
completionTokens: 3000,
45
totalTokens: undefined,
46
inputPrice: "0.0000010",
47
outputPrice: "0.0000020",
48
totalPrice: undefined,
49
expectedPromptTokens: 0,
50
expectedCompletionTokens: 3000,
51
expectedTotalTokens: 0,
52
expectedInputCost: "0", // promptTokens are set to 0 when ingesting undefined, hence 0 cost
53
expectedOutputCost: "0.006",
54
expectedTotalCost: "0.006",
57
testDescription: "prompt and completion and total",
59
completionTokens: 3000,
61
inputPrice: "0.0000010",
62
outputPrice: "0.0000020",
63
totalPrice: undefined,
64
expectedPromptTokens: 200,
65
expectedCompletionTokens: 3000,
66
expectedTotalTokens: 3200,
67
expectedInputCost: "0.0002", // 200 / 1000 * 0.0010
68
expectedOutputCost: "0.006", // 3000 / 1000 * 0.0020
69
expectedTotalCost: "0.0062", // 0.0002 + 0.006
72
testDescription: "total only without price",
73
promptTokens: undefined,
74
completionTokens: undefined,
76
inputPrice: "0.0000010",
77
outputPrice: "0.0000020",
78
totalPrice: undefined,
79
expectedPromptTokens: 0,
80
expectedCompletionTokens: 0,
81
expectedTotalTokens: 3200,
82
expectedInputCost: "0",
83
expectedOutputCost: "0",
84
expectedTotalCost: "0",
87
testDescription: "total only",
88
promptTokens: undefined,
89
completionTokens: undefined,
91
inputPrice: "0.0000010",
92
outputPrice: "0.0000020",
94
expectedPromptTokens: 0,
95
expectedCompletionTokens: 0,
96
expectedTotalTokens: 3200,
97
expectedInputCost: "0",
98
expectedOutputCost: "0",
99
expectedTotalCost: "320",
101
].forEach((input) => {
102
it(`should calculate cost correctly ${input.testDescription}`, async () => {
103
await pruneDatabase();
105
await prisma.model.create({
107
modelName: "gpt-3.5-turbo",
108
inputPrice: input.inputPrice,
109
outputPrice: input.outputPrice,
110
totalPrice: input.totalPrice,
111
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
113
startDate: new Date("2023-12-01"),
114
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
115
unit: ModelUsageUnit.Tokens,
119
const dbTrace = await prisma.trace.create({
122
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
126
await prisma.observation.create({
130
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
131
model: "gpt-3.5-turbo",
132
internalModel: "gpt-3.5-turbo",
133
startTime: new Date("2024-01-01T00:00:00.000Z"),
134
unit: ModelUsageUnit.Tokens,
135
promptTokens: input.promptTokens,
136
completionTokens: input.completionTokens,
137
totalTokens: input.totalTokens,
141
const view = await prisma.observationView.findFirst({
142
where: { traceId: dbTrace.id },
145
expect(view?.promptTokens).toBe(input.expectedPromptTokens);
146
expect(view?.completionTokens).toBe(input.expectedCompletionTokens);
147
expect(view?.totalTokens).toBe(input.expectedTotalTokens);
149
// calculated cost fields
150
expect(view?.calculatedInputCost?.toString()).toBe(
151
input.expectedInputCost,
153
expect(view?.calculatedOutputCost?.toString()).toBe(
154
input.expectedOutputCost,
156
expect(view?.calculatedTotalCost?.toString()).toBe(
157
input.expectedTotalCost,
164
testDescription: "overwriting project specific model",
165
expectedInputCost: "0.0004", // 200 / 1000 * 0.0010
166
expectedOutputCost: "0.012", // 3000 / 1000 * 0.0020
167
expectedTotalCost: "0.0124", // 0.0002 + 0.006
168
expectedModelId: "model-2",
170
].forEach((input) => {
171
it(`should calculate cost correctly with multiple models ${input.testDescription}`, async () => {
172
await pruneDatabase();
174
await prisma.model.create({
177
modelName: "gpt-3.5-turbo",
178
inputPrice: "0.0000010",
179
outputPrice: "0.0000020",
181
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
183
startDate: new Date("2023-12-01"),
184
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
185
unit: ModelUsageUnit.Tokens,
188
await prisma.model.create({
191
modelName: "gpt-3.5-turbo",
192
inputPrice: "0.0000020",
193
outputPrice: "0.0000040",
194
totalPrice: undefined,
195
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
196
startDate: new Date("2023-12-01"),
197
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
198
unit: ModelUsageUnit.Tokens,
199
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
203
const dbTrace = await prisma.trace.create({
206
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
210
await prisma.observation.create({
214
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
215
model: "gpt-3.5-turbo",
216
internalModel: "gpt-3.5-turbo",
217
startTime: new Date("2024-01-01T00:00:00.000Z"),
218
unit: ModelUsageUnit.Tokens,
220
completionTokens: 3000,
221
totalTokens: undefined,
225
const view = await prisma.observationView.findFirst({
226
where: { traceId: dbTrace.id },
229
// calculated cost fields
230
expect(view?.modelId).toBe(input.expectedModelId);
231
expect(view?.calculatedInputCost?.toString()).toBe(
232
input.expectedInputCost,
234
expect(view?.calculatedOutputCost?.toString()).toBe(
235
input.expectedOutputCost,
237
expect(view?.calculatedTotalCost?.toString()).toBe(
238
input.expectedTotalCost,
243
it(`should prioritize latest models`, async () => {
244
await pruneDatabase();
245
await prisma.model.create({
248
modelName: "gpt-3.5-turbo",
249
inputPrice: "0.0000000",
250
outputPrice: "0.0000000",
252
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
255
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
256
unit: ModelUsageUnit.Tokens,
260
await prisma.model.create({
263
modelName: "gpt-3.5-turbo",
264
inputPrice: "0.0000010",
265
outputPrice: "0.0000020",
267
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
269
startDate: new Date("2023-12-01"),
270
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
271
unit: ModelUsageUnit.Tokens,
274
await prisma.model.create({
277
modelName: "gpt-3.5-turbo",
278
inputPrice: "0.0000020",
279
outputPrice: "0.0000040",
280
totalPrice: undefined,
281
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
282
startDate: new Date("2023-12-02"),
283
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
284
unit: ModelUsageUnit.Tokens,
288
const dbTrace = await prisma.trace.create({
291
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
295
await prisma.observation.create({
299
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
300
model: "gpt-3.5-turbo",
301
internalModel: "gpt-3.5-turbo",
302
startTime: new Date("2024-01-01T00:00:00.000Z"),
303
unit: ModelUsageUnit.Tokens,
305
completionTokens: 3000,
306
totalTokens: undefined,
310
const view = await prisma.observationView.findFirst({
311
where: { traceId: dbTrace.id },
316
// calculated cost fields
317
expect(view?.modelId).toBe("model-2");
318
expect(view?.calculatedInputCost?.toString()).toBe("0.0004");
319
expect(view?.calculatedOutputCost?.toString()).toBe("0.012");
320
expect(view?.calculatedTotalCost?.toString()).toBe("0.0124");
323
it(`should take old model for old observations`, async () => {
324
await prisma.model.create({
327
modelName: "gpt-3.5-turbo",
328
inputPrice: "0.0000000",
329
outputPrice: "0.0000000",
331
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
334
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
335
unit: ModelUsageUnit.Tokens,
339
await prisma.model.create({
342
modelName: "gpt-3.5-turbo",
343
inputPrice: "0.0000010",
344
outputPrice: "0.0000020",
346
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
348
startDate: new Date("2023-12-01"),
349
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
350
unit: ModelUsageUnit.Tokens,
353
await prisma.model.create({
356
modelName: "gpt-3.5-turbo",
357
inputPrice: "0.0000020",
358
outputPrice: "0.0000040",
359
totalPrice: undefined,
360
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
361
startDate: new Date("2023-12-02"),
362
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
363
unit: ModelUsageUnit.Tokens,
367
const dbTrace = await prisma.trace.create({
370
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
374
await prisma.observation.create({
378
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
379
model: "gpt-3.5-turbo",
380
internalModel: "gpt-3.5-turbo",
381
startTime: new Date("2023-01-01T00:00:00.000Z"),
382
unit: ModelUsageUnit.Tokens,
384
completionTokens: 3000,
385
totalTokens: undefined,
389
const view = await prisma.observationView.findFirst({
390
where: { traceId: dbTrace.id },
395
// calculated cost fields
396
expect(view?.modelId).toBe("model-0");
397
expect(view?.calculatedInputCost?.toString()).toBe("0");
398
expect(view?.calculatedOutputCost?.toString()).toBe("0");
399
expect(view?.calculatedTotalCost?.toString()).toBe("0");
402
it(`should prioritize own models`, async () => {
403
await pruneDatabase();
404
await prisma.model.create({
407
modelName: "gpt-3.5-turbo",
408
inputPrice: "0.0000000",
409
outputPrice: "0.0000000",
411
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
414
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
415
unit: ModelUsageUnit.Tokens,
419
await prisma.model.create({
422
modelName: "gpt-3.5-turbo",
423
inputPrice: "0.0000010",
424
outputPrice: "0.0000020",
426
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
428
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
429
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
430
unit: ModelUsageUnit.Tokens,
434
const dbTrace = await prisma.trace.create({
437
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
441
await prisma.observation.create({
445
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
446
model: "gpt-3.5-turbo",
447
internalModel: "gpt-3.5-turbo",
448
startTime: new Date("2024-01-01T00:00:00.000Z"),
449
unit: ModelUsageUnit.Tokens,
451
completionTokens: 3000,
452
totalTokens: undefined,
456
const view = await prisma.observationView.findFirst({
457
where: { traceId: dbTrace.id },
462
// calculated cost fields
463
expect(view?.modelId).toBe("model-1");
466
it(`should prioritize old model if the latest model is not own one`, async () => {
467
await prisma.model.create({
470
modelName: "gpt-3.5-turbo",
471
inputPrice: "0.0010",
472
outputPrice: "0.0020",
474
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
475
startDate: new Date("2023-12-02"),
476
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
477
unit: ModelUsageUnit.Tokens,
480
await prisma.model.create({
483
modelName: "gpt-3.5-turbo",
484
inputPrice: "0.0000020",
485
outputPrice: "0.0000040",
486
totalPrice: undefined,
487
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
488
startDate: new Date("2023-12-01"),
489
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
490
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
491
unit: ModelUsageUnit.Tokens,
495
const dbTrace = await prisma.trace.create({
498
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
502
await prisma.observation.create({
506
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
507
model: "gpt-3.5-turbo",
508
internalModel: "gpt-3.5-turbo",
509
startTime: new Date("2024-01-01T00:00:00.000Z"),
510
unit: ModelUsageUnit.Tokens,
512
completionTokens: 3000,
513
totalTokens: undefined,
517
const view = await prisma.observationView.findFirst({
518
where: { traceId: dbTrace.id },
523
// calculated cost fields
524
expect(view?.modelId).toBe("model-2");
525
expect(view?.calculatedInputCost?.toString()).toBe("0.0004");
526
expect(view?.calculatedOutputCost?.toString()).toBe("0.012");
527
expect(view?.calculatedTotalCost?.toString()).toBe("0.0124");
530
it(`should prioritize new model if the latest model is own one`, async () => {
531
await pruneDatabase();
533
await prisma.model.create({
536
modelName: "gpt-3.5-turbo",
537
inputPrice: "0.0010",
538
outputPrice: "0.0020",
540
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
542
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
543
unit: ModelUsageUnit.Tokens,
546
await prisma.model.create({
549
modelName: "gpt-3.5-turbo",
550
inputPrice: "0.0000020",
551
outputPrice: "0.0000040",
552
totalPrice: undefined,
553
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
554
startDate: new Date("2023-12-01"),
555
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
556
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
557
unit: ModelUsageUnit.Tokens,
561
const dbTrace = await prisma.trace.create({
564
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
568
await prisma.observation.create({
572
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
573
model: "gpt-3.5-turbo",
574
internalModel: "gpt-3.5-turbo",
575
startTime: new Date("2024-01-01T00:00:00.000Z"),
576
unit: ModelUsageUnit.Tokens,
578
completionTokens: 3000,
579
totalTokens: undefined,
583
const view = await prisma.observationView.findFirst({
584
where: { traceId: dbTrace.id },
589
// calculated cost fields
590
expect(view?.modelId).toBe("model-2");
591
expect(view?.calculatedInputCost?.toString()).toBe("0.0004");
592
expect(view?.calculatedOutputCost?.toString()).toBe("0.012");
593
expect(view?.calculatedTotalCost?.toString()).toBe("0.0124");
596
it(`should prioritize user provided cost`, async () => {
597
await pruneDatabase();
599
await prisma.model.create({
602
modelName: "gpt-3.5-turbo",
603
inputPrice: "0.0010",
604
outputPrice: "0.0020",
606
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
607
startDate: new Date("2023-12-02"),
608
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
609
unit: ModelUsageUnit.Tokens,
612
await prisma.model.create({
615
modelName: "gpt-3.5-turbo",
616
inputPrice: "0.0020",
617
outputPrice: "0.0040",
618
totalPrice: undefined,
619
matchPattern: "(.*)(gpt-)(35|3.5)(-turbo)?(.*)",
620
startDate: new Date("2023-12-01"),
621
tokenizerConfig: { tokensPerMessage: 3, tokensPerName: 1 },
622
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
623
unit: ModelUsageUnit.Tokens,
627
const dbTrace = await prisma.trace.create({
630
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
634
await prisma.observation.create({
638
project: { connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" } },
639
model: "gpt-3.5-turbo",
640
internalModel: "gpt-3.5-turbo",
641
startTime: new Date("2024-01-01T00:00:00.000Z"),
642
unit: ModelUsageUnit.Tokens,
644
completionTokens: 3000,
645
totalTokens: undefined,
652
const view = await prisma.observationView.findFirst({
653
where: { traceId: dbTrace.id },
658
// calculated cost fields
659
expect(view?.modelId).toBe("model-2");
660
expect(view?.calculatedInputCost?.toString()).toBe("1");
661
expect(view?.calculatedOutputCost?.toString()).toBe("2");
662
expect(view?.calculatedTotalCost?.toString()).toBe("3");