@llmops/core 0.1.5-beta.1 → 0.1.5
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/{bun-sqlite-dialect-zL8xmYst.cjs → bun-sqlite-dialect-Bp2qbl5F.cjs} +1 -1
- package/dist/db/index.cjs +2 -1
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.mts +2 -2
- package/dist/db/index.mjs +2 -2
- package/dist/{db-CGY-vZ3u.mjs → db-DSzwrW4p.mjs} +108 -2
- package/dist/{db-C9-M-kdS.cjs → db-eEfIe5dO.cjs} +115 -3
- package/dist/{index-DTHo2J3v.d.cts → index-BO7DYWFs.d.cts} +338 -22
- package/dist/{index-D3ncxgf2.d.mts → index-mUSLoeGU.d.mts} +338 -22
- package/dist/index.cjs +500 -2
- package/dist/index.d.cts +600 -3
- package/dist/index.d.mts +600 -3
- package/dist/index.mjs +493 -3
- package/dist/{node-sqlite-dialect-CQlHW438.cjs → node-sqlite-dialect-b2V910TJ.cjs} +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
const require_db = require('./db-
|
|
1
|
+
const require_db = require('./db-eEfIe5dO.cjs');
|
|
2
2
|
let __llmops_gateway = require("@llmops/gateway");
|
|
3
3
|
__llmops_gateway = require_db.__toESM(__llmops_gateway);
|
|
4
|
+
let kysely = require("kysely");
|
|
4
5
|
let node_fs_promises = require("node:fs/promises");
|
|
5
6
|
node_fs_promises = require_db.__toESM(node_fs_promises);
|
|
6
7
|
let node_path = require("node:path");
|
|
@@ -1282,7 +1283,11 @@ const createConfigVariantDataLayer = (db) => {
|
|
|
1282
1283
|
"version"
|
|
1283
1284
|
]).where("variantId", "=", configVariant.variantId).orderBy("version", "desc").limit(1).executeTakeFirst();
|
|
1284
1285
|
if (!versionData) throw new LLMOpsError(`No variant version found for variant ${configVariant.variantId}`);
|
|
1285
|
-
return
|
|
1286
|
+
return {
|
|
1287
|
+
...versionData,
|
|
1288
|
+
configId: resolvedConfigId,
|
|
1289
|
+
variantId: configVariant.variantId
|
|
1290
|
+
};
|
|
1286
1291
|
}
|
|
1287
1292
|
};
|
|
1288
1293
|
};
|
|
@@ -1442,6 +1447,287 @@ const createEnvironmentSecretDataLayer = (db) => {
|
|
|
1442
1447
|
};
|
|
1443
1448
|
};
|
|
1444
1449
|
|
|
1450
|
+
//#endregion
|
|
1451
|
+
//#region src/datalayer/llmRequests.ts
|
|
1452
|
+
/**
|
|
1453
|
+
* Schema for inserting a new LLM request log
|
|
1454
|
+
*/
|
|
1455
|
+
const insertLLMRequestSchema = require_db.zod_default.object({
|
|
1456
|
+
requestId: require_db.zod_default.string().uuid(),
|
|
1457
|
+
configId: require_db.zod_default.string().uuid().nullable().optional(),
|
|
1458
|
+
variantId: require_db.zod_default.string().uuid().nullable().optional(),
|
|
1459
|
+
provider: require_db.zod_default.string(),
|
|
1460
|
+
model: require_db.zod_default.string(),
|
|
1461
|
+
promptTokens: require_db.zod_default.number().int().default(0),
|
|
1462
|
+
completionTokens: require_db.zod_default.number().int().default(0),
|
|
1463
|
+
totalTokens: require_db.zod_default.number().int().default(0),
|
|
1464
|
+
cachedTokens: require_db.zod_default.number().int().default(0),
|
|
1465
|
+
cost: require_db.zod_default.number().int().default(0),
|
|
1466
|
+
inputCost: require_db.zod_default.number().int().default(0),
|
|
1467
|
+
outputCost: require_db.zod_default.number().int().default(0),
|
|
1468
|
+
endpoint: require_db.zod_default.string(),
|
|
1469
|
+
statusCode: require_db.zod_default.number().int(),
|
|
1470
|
+
latencyMs: require_db.zod_default.number().int().default(0),
|
|
1471
|
+
isStreaming: require_db.zod_default.boolean().default(false),
|
|
1472
|
+
userId: require_db.zod_default.string().nullable().optional(),
|
|
1473
|
+
tags: require_db.zod_default.record(require_db.zod_default.string(), require_db.zod_default.string()).default({})
|
|
1474
|
+
});
|
|
1475
|
+
/**
|
|
1476
|
+
* Schema for listing LLM requests
|
|
1477
|
+
*/
|
|
1478
|
+
const listRequestsSchema = require_db.zod_default.object({
|
|
1479
|
+
limit: require_db.zod_default.number().int().positive().max(1e3).default(100),
|
|
1480
|
+
offset: require_db.zod_default.number().int().nonnegative().default(0),
|
|
1481
|
+
configId: require_db.zod_default.string().uuid().optional(),
|
|
1482
|
+
provider: require_db.zod_default.string().optional(),
|
|
1483
|
+
model: require_db.zod_default.string().optional(),
|
|
1484
|
+
startDate: require_db.zod_default.date().optional(),
|
|
1485
|
+
endDate: require_db.zod_default.date().optional()
|
|
1486
|
+
});
|
|
1487
|
+
/**
|
|
1488
|
+
* Schema for date range queries
|
|
1489
|
+
*/
|
|
1490
|
+
const dateRangeSchema = require_db.zod_default.object({
|
|
1491
|
+
startDate: require_db.zod_default.date(),
|
|
1492
|
+
endDate: require_db.zod_default.date()
|
|
1493
|
+
});
|
|
1494
|
+
/**
|
|
1495
|
+
* Schema for cost summary with grouping
|
|
1496
|
+
*/
|
|
1497
|
+
const costSummarySchema = require_db.zod_default.object({
|
|
1498
|
+
startDate: require_db.zod_default.date(),
|
|
1499
|
+
endDate: require_db.zod_default.date(),
|
|
1500
|
+
groupBy: require_db.zod_default.enum([
|
|
1501
|
+
"day",
|
|
1502
|
+
"hour",
|
|
1503
|
+
"model",
|
|
1504
|
+
"provider",
|
|
1505
|
+
"config"
|
|
1506
|
+
]).optional()
|
|
1507
|
+
});
|
|
1508
|
+
/**
|
|
1509
|
+
* Helper to create column reference for SQL
|
|
1510
|
+
* Uses sql.ref() to properly quote column names for the database
|
|
1511
|
+
*/
|
|
1512
|
+
const col = (name) => kysely.sql.ref(name);
|
|
1513
|
+
const tableCol = (table, name) => kysely.sql.ref(`${table}.${name}`);
|
|
1514
|
+
const createLLMRequestsDataLayer = (db) => {
|
|
1515
|
+
return {
|
|
1516
|
+
batchInsertRequests: async (requests) => {
|
|
1517
|
+
if (requests.length === 0) return { count: 0 };
|
|
1518
|
+
const validatedRequests = await Promise.all(requests.map(async (req) => {
|
|
1519
|
+
const result = await insertLLMRequestSchema.safeParseAsync(req);
|
|
1520
|
+
if (!result.success) throw new LLMOpsError(`Invalid request data: ${result.error.message}`);
|
|
1521
|
+
return result.data;
|
|
1522
|
+
}));
|
|
1523
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1524
|
+
const values = validatedRequests.map((req) => ({
|
|
1525
|
+
id: (0, node_crypto.randomUUID)(),
|
|
1526
|
+
requestId: req.requestId,
|
|
1527
|
+
configId: req.configId ?? null,
|
|
1528
|
+
variantId: req.variantId ?? null,
|
|
1529
|
+
provider: req.provider,
|
|
1530
|
+
model: req.model,
|
|
1531
|
+
promptTokens: req.promptTokens,
|
|
1532
|
+
completionTokens: req.completionTokens,
|
|
1533
|
+
totalTokens: req.totalTokens,
|
|
1534
|
+
cachedTokens: req.cachedTokens,
|
|
1535
|
+
cost: req.cost,
|
|
1536
|
+
inputCost: req.inputCost,
|
|
1537
|
+
outputCost: req.outputCost,
|
|
1538
|
+
endpoint: req.endpoint,
|
|
1539
|
+
statusCode: req.statusCode,
|
|
1540
|
+
latencyMs: req.latencyMs,
|
|
1541
|
+
isStreaming: req.isStreaming,
|
|
1542
|
+
userId: req.userId ?? null,
|
|
1543
|
+
tags: JSON.stringify(req.tags),
|
|
1544
|
+
createdAt: now,
|
|
1545
|
+
updatedAt: now
|
|
1546
|
+
}));
|
|
1547
|
+
await db.insertInto("llm_requests").values(values).execute();
|
|
1548
|
+
return { count: values.length };
|
|
1549
|
+
},
|
|
1550
|
+
insertRequest: async (request) => {
|
|
1551
|
+
const result = await insertLLMRequestSchema.safeParseAsync(request);
|
|
1552
|
+
if (!result.success) throw new LLMOpsError(`Invalid request data: ${result.error.message}`);
|
|
1553
|
+
const req = result.data;
|
|
1554
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1555
|
+
return db.insertInto("llm_requests").values({
|
|
1556
|
+
id: (0, node_crypto.randomUUID)(),
|
|
1557
|
+
requestId: req.requestId,
|
|
1558
|
+
configId: req.configId ?? null,
|
|
1559
|
+
variantId: req.variantId ?? null,
|
|
1560
|
+
provider: req.provider,
|
|
1561
|
+
model: req.model,
|
|
1562
|
+
promptTokens: req.promptTokens,
|
|
1563
|
+
completionTokens: req.completionTokens,
|
|
1564
|
+
totalTokens: req.totalTokens,
|
|
1565
|
+
cachedTokens: req.cachedTokens,
|
|
1566
|
+
cost: req.cost,
|
|
1567
|
+
inputCost: req.inputCost,
|
|
1568
|
+
outputCost: req.outputCost,
|
|
1569
|
+
endpoint: req.endpoint,
|
|
1570
|
+
statusCode: req.statusCode,
|
|
1571
|
+
latencyMs: req.latencyMs,
|
|
1572
|
+
isStreaming: req.isStreaming,
|
|
1573
|
+
userId: req.userId ?? null,
|
|
1574
|
+
tags: JSON.stringify(req.tags),
|
|
1575
|
+
createdAt: now,
|
|
1576
|
+
updatedAt: now
|
|
1577
|
+
}).returningAll().executeTakeFirst();
|
|
1578
|
+
},
|
|
1579
|
+
listRequests: async (params) => {
|
|
1580
|
+
const result = await listRequestsSchema.safeParseAsync(params || {});
|
|
1581
|
+
if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
|
|
1582
|
+
const { limit, offset, configId, provider, model, startDate, endDate } = result.data;
|
|
1583
|
+
let baseQuery = db.selectFrom("llm_requests");
|
|
1584
|
+
if (configId) baseQuery = baseQuery.where("configId", "=", configId);
|
|
1585
|
+
if (provider) baseQuery = baseQuery.where("provider", "=", provider);
|
|
1586
|
+
if (model) baseQuery = baseQuery.where("model", "=", model);
|
|
1587
|
+
if (startDate) baseQuery = baseQuery.where(kysely.sql`${col("createdAt")} >= ${startDate.toISOString()}`);
|
|
1588
|
+
if (endDate) baseQuery = baseQuery.where(kysely.sql`${col("createdAt")} <= ${endDate.toISOString()}`);
|
|
1589
|
+
const countResult = await baseQuery.select(kysely.sql`COUNT(*)`.as("total")).executeTakeFirst();
|
|
1590
|
+
const total = Number(countResult?.total ?? 0);
|
|
1591
|
+
return {
|
|
1592
|
+
data: await baseQuery.selectAll().orderBy("createdAt", "desc").limit(limit).offset(offset).execute(),
|
|
1593
|
+
total,
|
|
1594
|
+
limit,
|
|
1595
|
+
offset
|
|
1596
|
+
};
|
|
1597
|
+
},
|
|
1598
|
+
getRequestByRequestId: async (requestId) => {
|
|
1599
|
+
return db.selectFrom("llm_requests").selectAll().where("requestId", "=", requestId).executeTakeFirst();
|
|
1600
|
+
},
|
|
1601
|
+
getTotalCost: async (params) => {
|
|
1602
|
+
const result = await dateRangeSchema.safeParseAsync(params);
|
|
1603
|
+
if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
|
|
1604
|
+
const { startDate, endDate } = result.data;
|
|
1605
|
+
return await db.selectFrom("llm_requests").select([
|
|
1606
|
+
kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
|
|
1607
|
+
kysely.sql`COALESCE(SUM(${col("inputCost")}), 0)`.as("totalInputCost"),
|
|
1608
|
+
kysely.sql`COALESCE(SUM(${col("outputCost")}), 0)`.as("totalOutputCost"),
|
|
1609
|
+
kysely.sql`COALESCE(SUM(${col("promptTokens")}), 0)`.as("totalPromptTokens"),
|
|
1610
|
+
kysely.sql`COALESCE(SUM(${col("completionTokens")}), 0)`.as("totalCompletionTokens"),
|
|
1611
|
+
kysely.sql`COALESCE(SUM(${col("totalTokens")}), 0)`.as("totalTokens"),
|
|
1612
|
+
kysely.sql`COUNT(*)`.as("requestCount")
|
|
1613
|
+
]).where(kysely.sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(kysely.sql`${col("createdAt")} <= ${endDate.toISOString()}`).executeTakeFirst();
|
|
1614
|
+
},
|
|
1615
|
+
getCostByModel: async (params) => {
|
|
1616
|
+
const result = await dateRangeSchema.safeParseAsync(params);
|
|
1617
|
+
if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
|
|
1618
|
+
const { startDate, endDate } = result.data;
|
|
1619
|
+
return db.selectFrom("llm_requests").select([
|
|
1620
|
+
"provider",
|
|
1621
|
+
"model",
|
|
1622
|
+
kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
|
|
1623
|
+
kysely.sql`COALESCE(SUM(${col("inputCost")}), 0)`.as("totalInputCost"),
|
|
1624
|
+
kysely.sql`COALESCE(SUM(${col("outputCost")}), 0)`.as("totalOutputCost"),
|
|
1625
|
+
kysely.sql`COALESCE(SUM(${col("totalTokens")}), 0)`.as("totalTokens"),
|
|
1626
|
+
kysely.sql`COUNT(*)`.as("requestCount"),
|
|
1627
|
+
kysely.sql`AVG(${col("latencyMs")})`.as("avgLatencyMs")
|
|
1628
|
+
]).where(kysely.sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(kysely.sql`${col("createdAt")} <= ${endDate.toISOString()}`).groupBy(["provider", "model"]).orderBy(kysely.sql`SUM(${col("cost")})`, "desc").execute();
|
|
1629
|
+
},
|
|
1630
|
+
getCostByProvider: async (params) => {
|
|
1631
|
+
const result = await dateRangeSchema.safeParseAsync(params);
|
|
1632
|
+
if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
|
|
1633
|
+
const { startDate, endDate } = result.data;
|
|
1634
|
+
return db.selectFrom("llm_requests").select([
|
|
1635
|
+
"provider",
|
|
1636
|
+
kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
|
|
1637
|
+
kysely.sql`COALESCE(SUM(${col("inputCost")}), 0)`.as("totalInputCost"),
|
|
1638
|
+
kysely.sql`COALESCE(SUM(${col("outputCost")}), 0)`.as("totalOutputCost"),
|
|
1639
|
+
kysely.sql`COALESCE(SUM(${col("totalTokens")}), 0)`.as("totalTokens"),
|
|
1640
|
+
kysely.sql`COUNT(*)`.as("requestCount"),
|
|
1641
|
+
kysely.sql`AVG(${col("latencyMs")})`.as("avgLatencyMs")
|
|
1642
|
+
]).where(kysely.sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(kysely.sql`${col("createdAt")} <= ${endDate.toISOString()}`).groupBy("provider").orderBy(kysely.sql`SUM(${col("cost")})`, "desc").execute();
|
|
1643
|
+
},
|
|
1644
|
+
getCostByConfig: async (params) => {
|
|
1645
|
+
const result = await dateRangeSchema.safeParseAsync(params);
|
|
1646
|
+
if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
|
|
1647
|
+
const { startDate, endDate } = result.data;
|
|
1648
|
+
return db.selectFrom("llm_requests").leftJoin("configs", "llm_requests.configId", "configs.id").select([
|
|
1649
|
+
"llm_requests.configId",
|
|
1650
|
+
"configs.name as configName",
|
|
1651
|
+
"configs.slug as configSlug",
|
|
1652
|
+
kysely.sql`COALESCE(SUM(${tableCol("llm_requests", "cost")}), 0)`.as("totalCost"),
|
|
1653
|
+
kysely.sql`COALESCE(SUM(${tableCol("llm_requests", "inputCost")}), 0)`.as("totalInputCost"),
|
|
1654
|
+
kysely.sql`COALESCE(SUM(${tableCol("llm_requests", "outputCost")}), 0)`.as("totalOutputCost"),
|
|
1655
|
+
kysely.sql`COALESCE(SUM(${tableCol("llm_requests", "totalTokens")}), 0)`.as("totalTokens"),
|
|
1656
|
+
kysely.sql`COUNT(*)`.as("requestCount")
|
|
1657
|
+
]).where(kysely.sql`${tableCol("llm_requests", "createdAt")} >= ${startDate.toISOString()}`).where(kysely.sql`${tableCol("llm_requests", "createdAt")} <= ${endDate.toISOString()}`).groupBy([
|
|
1658
|
+
"llm_requests.configId",
|
|
1659
|
+
"configs.name",
|
|
1660
|
+
"configs.slug"
|
|
1661
|
+
]).orderBy(kysely.sql`SUM(${tableCol("llm_requests", "cost")})`, "desc").execute();
|
|
1662
|
+
},
|
|
1663
|
+
getDailyCosts: async (params) => {
|
|
1664
|
+
const result = await dateRangeSchema.safeParseAsync(params);
|
|
1665
|
+
if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
|
|
1666
|
+
const { startDate, endDate } = result.data;
|
|
1667
|
+
return db.selectFrom("llm_requests").select([
|
|
1668
|
+
kysely.sql`DATE(${col("createdAt")})`.as("date"),
|
|
1669
|
+
kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
|
|
1670
|
+
kysely.sql`COALESCE(SUM(${col("inputCost")}), 0)`.as("totalInputCost"),
|
|
1671
|
+
kysely.sql`COALESCE(SUM(${col("outputCost")}), 0)`.as("totalOutputCost"),
|
|
1672
|
+
kysely.sql`COALESCE(SUM(${col("totalTokens")}), 0)`.as("totalTokens"),
|
|
1673
|
+
kysely.sql`COUNT(*)`.as("requestCount")
|
|
1674
|
+
]).where(kysely.sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(kysely.sql`${col("createdAt")} <= ${endDate.toISOString()}`).groupBy(kysely.sql`DATE(${col("createdAt")})`).orderBy(kysely.sql`DATE(${col("createdAt")})`, "asc").execute();
|
|
1675
|
+
},
|
|
1676
|
+
getCostSummary: async (params) => {
|
|
1677
|
+
const result = await costSummarySchema.safeParseAsync(params);
|
|
1678
|
+
if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
|
|
1679
|
+
const { startDate, endDate, groupBy } = result.data;
|
|
1680
|
+
const baseQuery = db.selectFrom("llm_requests").where(kysely.sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(kysely.sql`${col("createdAt")} <= ${endDate.toISOString()}`);
|
|
1681
|
+
switch (groupBy) {
|
|
1682
|
+
case "day": return baseQuery.select([
|
|
1683
|
+
kysely.sql`DATE(${col("createdAt")})`.as("groupKey"),
|
|
1684
|
+
kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
|
|
1685
|
+
kysely.sql`COUNT(*)`.as("requestCount")
|
|
1686
|
+
]).groupBy(kysely.sql`DATE(${col("createdAt")})`).orderBy(kysely.sql`DATE(${col("createdAt")})`, "asc").execute();
|
|
1687
|
+
case "hour": return baseQuery.select([
|
|
1688
|
+
kysely.sql`DATE_TRUNC('hour', ${col("createdAt")})`.as("groupKey"),
|
|
1689
|
+
kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
|
|
1690
|
+
kysely.sql`COUNT(*)`.as("requestCount")
|
|
1691
|
+
]).groupBy(kysely.sql`DATE_TRUNC('hour', ${col("createdAt")})`).orderBy(kysely.sql`DATE_TRUNC('hour', ${col("createdAt")})`, "asc").execute();
|
|
1692
|
+
case "model": return baseQuery.select([
|
|
1693
|
+
kysely.sql`${col("provider")} || '/' || ${col("model")}`.as("groupKey"),
|
|
1694
|
+
kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
|
|
1695
|
+
kysely.sql`COUNT(*)`.as("requestCount")
|
|
1696
|
+
]).groupBy(["provider", "model"]).orderBy(kysely.sql`SUM(${col("cost")})`, "desc").execute();
|
|
1697
|
+
case "provider": return baseQuery.select([
|
|
1698
|
+
kysely.sql`${col("provider")}`.as("groupKey"),
|
|
1699
|
+
kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
|
|
1700
|
+
kysely.sql`COUNT(*)`.as("requestCount")
|
|
1701
|
+
]).groupBy("provider").orderBy(kysely.sql`SUM(${col("cost")})`, "desc").execute();
|
|
1702
|
+
case "config": return baseQuery.select([
|
|
1703
|
+
kysely.sql`COALESCE(${col("configId")}::text, 'no-config')`.as("groupKey"),
|
|
1704
|
+
kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
|
|
1705
|
+
kysely.sql`COUNT(*)`.as("requestCount")
|
|
1706
|
+
]).groupBy("configId").orderBy(kysely.sql`SUM(${col("cost")})`, "desc").execute();
|
|
1707
|
+
default: return baseQuery.select([
|
|
1708
|
+
kysely.sql`'total'`.as("groupKey"),
|
|
1709
|
+
kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
|
|
1710
|
+
kysely.sql`COUNT(*)`.as("requestCount")
|
|
1711
|
+
]).execute();
|
|
1712
|
+
}
|
|
1713
|
+
},
|
|
1714
|
+
getRequestStats: async (params) => {
|
|
1715
|
+
const result = await dateRangeSchema.safeParseAsync(params);
|
|
1716
|
+
if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
|
|
1717
|
+
const { startDate, endDate } = result.data;
|
|
1718
|
+
return await db.selectFrom("llm_requests").select([
|
|
1719
|
+
kysely.sql`COUNT(*)`.as("totalRequests"),
|
|
1720
|
+
kysely.sql`COUNT(CASE WHEN ${col("statusCode")} >= 200 AND ${col("statusCode")} < 300 THEN 1 END)`.as("successfulRequests"),
|
|
1721
|
+
kysely.sql`COUNT(CASE WHEN ${col("statusCode")} >= 400 THEN 1 END)`.as("failedRequests"),
|
|
1722
|
+
kysely.sql`COUNT(CASE WHEN ${col("isStreaming")} = true THEN 1 END)`.as("streamingRequests"),
|
|
1723
|
+
kysely.sql`AVG(${col("latencyMs")})`.as("avgLatencyMs"),
|
|
1724
|
+
kysely.sql`MAX(${col("latencyMs")})`.as("maxLatencyMs"),
|
|
1725
|
+
kysely.sql`MIN(${col("latencyMs")})`.as("minLatencyMs")
|
|
1726
|
+
]).where(kysely.sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(kysely.sql`${col("createdAt")} <= ${endDate.toISOString()}`).executeTakeFirst();
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1729
|
+
};
|
|
1730
|
+
|
|
1445
1731
|
//#endregion
|
|
1446
1732
|
//#region src/datalayer/targetingRules.ts
|
|
1447
1733
|
const createTargetingRule = require_db.zod_default.object({
|
|
@@ -1868,6 +2154,7 @@ const createDataLayer = async (db) => {
|
|
|
1868
2154
|
...createConfigVariantDataLayer(db),
|
|
1869
2155
|
...createEnvironmentDataLayer(db),
|
|
1870
2156
|
...createEnvironmentSecretDataLayer(db),
|
|
2157
|
+
...createLLMRequestsDataLayer(db),
|
|
1871
2158
|
...createTargetingRulesDataLayer(db),
|
|
1872
2159
|
...createVariantDataLayer(db),
|
|
1873
2160
|
...createVariantVersionsDataLayer(db),
|
|
@@ -1875,22 +2162,230 @@ const createDataLayer = async (db) => {
|
|
|
1875
2162
|
};
|
|
1876
2163
|
};
|
|
1877
2164
|
|
|
2165
|
+
//#endregion
|
|
2166
|
+
//#region src/pricing/calculator.ts
|
|
2167
|
+
/**
|
|
2168
|
+
* Calculate the cost of an LLM request in micro-dollars
|
|
2169
|
+
*
|
|
2170
|
+
* Micro-dollars are used to avoid floating-point precision issues:
|
|
2171
|
+
* - 1 dollar = 1,000,000 micro-dollars
|
|
2172
|
+
* - $0.001 = 1,000 micro-dollars
|
|
2173
|
+
* - $0.000001 = 1 micro-dollar
|
|
2174
|
+
*
|
|
2175
|
+
* @param usage - Token usage data from the LLM response
|
|
2176
|
+
* @param pricing - Model pricing information
|
|
2177
|
+
* @returns Cost breakdown in micro-dollars
|
|
2178
|
+
*
|
|
2179
|
+
* @example
|
|
2180
|
+
* ```typescript
|
|
2181
|
+
* const usage = { promptTokens: 1000, completionTokens: 500 };
|
|
2182
|
+
* const pricing = { inputCostPer1M: 2.5, outputCostPer1M: 10.0 };
|
|
2183
|
+
* const cost = calculateCost(usage, pricing);
|
|
2184
|
+
* // cost = { inputCost: 2500, outputCost: 5000, totalCost: 7500 }
|
|
2185
|
+
* // In dollars: $0.0025 input + $0.005 output = $0.0075 total
|
|
2186
|
+
* ```
|
|
2187
|
+
*/
|
|
2188
|
+
function calculateCost(usage, pricing) {
|
|
2189
|
+
const inputCost = Math.round(usage.promptTokens * pricing.inputCostPer1M);
|
|
2190
|
+
const outputCost = Math.round(usage.completionTokens * pricing.outputCostPer1M);
|
|
2191
|
+
return {
|
|
2192
|
+
inputCost,
|
|
2193
|
+
outputCost,
|
|
2194
|
+
totalCost: inputCost + outputCost
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
/**
|
|
2198
|
+
* Convert micro-dollars to dollars
|
|
2199
|
+
*
|
|
2200
|
+
* @param microDollars - Amount in micro-dollars
|
|
2201
|
+
* @returns Amount in dollars
|
|
2202
|
+
*
|
|
2203
|
+
* @example
|
|
2204
|
+
* ```typescript
|
|
2205
|
+
* microDollarsToDollars(7500); // 0.0075
|
|
2206
|
+
* microDollarsToDollars(1000000); // 1.0
|
|
2207
|
+
* ```
|
|
2208
|
+
*/
|
|
2209
|
+
function microDollarsToDollars(microDollars) {
|
|
2210
|
+
return microDollars / 1e6;
|
|
2211
|
+
}
|
|
2212
|
+
/**
|
|
2213
|
+
* Convert dollars to micro-dollars
|
|
2214
|
+
*
|
|
2215
|
+
* @param dollars - Amount in dollars
|
|
2216
|
+
* @returns Amount in micro-dollars (rounded to nearest integer)
|
|
2217
|
+
*
|
|
2218
|
+
* @example
|
|
2219
|
+
* ```typescript
|
|
2220
|
+
* dollarsToMicroDollars(0.0075); // 7500
|
|
2221
|
+
* dollarsToMicroDollars(1.0); // 1000000
|
|
2222
|
+
* ```
|
|
2223
|
+
*/
|
|
2224
|
+
function dollarsToMicroDollars(dollars) {
|
|
2225
|
+
return Math.round(dollars * 1e6);
|
|
2226
|
+
}
|
|
2227
|
+
/**
|
|
2228
|
+
* Format micro-dollars as a human-readable dollar string
|
|
2229
|
+
*
|
|
2230
|
+
* @param microDollars - Amount in micro-dollars
|
|
2231
|
+
* @param decimals - Number of decimal places (default: 6)
|
|
2232
|
+
* @returns Formatted dollar string
|
|
2233
|
+
*
|
|
2234
|
+
* @example
|
|
2235
|
+
* ```typescript
|
|
2236
|
+
* formatCost(7500); // "$0.007500"
|
|
2237
|
+
* formatCost(1234567, 2); // "$1.23"
|
|
2238
|
+
* ```
|
|
2239
|
+
*/
|
|
2240
|
+
function formatCost(microDollars, decimals = 6) {
|
|
2241
|
+
return `$${microDollarsToDollars(microDollars).toFixed(decimals)}`;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
//#endregion
|
|
2245
|
+
//#region src/pricing/provider.ts
|
|
2246
|
+
const MODELS_DEV_API = "https://models.dev/api.json";
|
|
2247
|
+
/**
|
|
2248
|
+
* Pricing provider that fetches data from models.dev API
|
|
2249
|
+
*
|
|
2250
|
+
* Features:
|
|
2251
|
+
* - Caches pricing data with configurable TTL (default 5 minutes)
|
|
2252
|
+
* - Supports fallback to local cache on fetch failure
|
|
2253
|
+
* - Thread-safe cache refresh
|
|
2254
|
+
*/
|
|
2255
|
+
var ModelsDevPricingProvider = class {
|
|
2256
|
+
cache = /* @__PURE__ */ new Map();
|
|
2257
|
+
lastFetch = 0;
|
|
2258
|
+
cacheTTL;
|
|
2259
|
+
fetchPromise = null;
|
|
2260
|
+
ready = false;
|
|
2261
|
+
/**
|
|
2262
|
+
* Create a new ModelsDevPricingProvider
|
|
2263
|
+
*
|
|
2264
|
+
* @param cacheTTL - Cache TTL in milliseconds (default: 5 minutes)
|
|
2265
|
+
*/
|
|
2266
|
+
constructor(cacheTTL = 300 * 1e3) {
|
|
2267
|
+
this.cacheTTL = cacheTTL;
|
|
2268
|
+
}
|
|
2269
|
+
/**
|
|
2270
|
+
* Generate a cache key for a provider/model combination
|
|
2271
|
+
*/
|
|
2272
|
+
getCacheKey(provider, model) {
|
|
2273
|
+
return `${provider.toLowerCase()}:${model.toLowerCase()}`;
|
|
2274
|
+
}
|
|
2275
|
+
/**
|
|
2276
|
+
* Fetch pricing data from models.dev API
|
|
2277
|
+
*/
|
|
2278
|
+
async fetchPricingData() {
|
|
2279
|
+
try {
|
|
2280
|
+
require_db.logger.debug("[Pricing] Fetching pricing data from models.dev");
|
|
2281
|
+
const response = await fetch(MODELS_DEV_API);
|
|
2282
|
+
if (!response.ok) throw new Error(`Failed to fetch models.dev API: ${response.status}`);
|
|
2283
|
+
const data = await response.json();
|
|
2284
|
+
this.cache.clear();
|
|
2285
|
+
for (const [providerId, provider] of Object.entries(data)) {
|
|
2286
|
+
if (!provider.models) continue;
|
|
2287
|
+
for (const [_modelId, model] of Object.entries(provider.models)) {
|
|
2288
|
+
if (!model.cost) continue;
|
|
2289
|
+
const cacheKey = this.getCacheKey(providerId, model.id);
|
|
2290
|
+
this.cache.set(cacheKey, {
|
|
2291
|
+
inputCostPer1M: model.cost.input ?? 0,
|
|
2292
|
+
outputCostPer1M: model.cost.output ?? 0,
|
|
2293
|
+
cacheReadCostPer1M: model.cost.cache_read,
|
|
2294
|
+
cacheWriteCostPer1M: model.cost.cache_write,
|
|
2295
|
+
reasoningCostPer1M: model.cost.reasoning
|
|
2296
|
+
});
|
|
2297
|
+
const nameKey = this.getCacheKey(providerId, model.name);
|
|
2298
|
+
if (nameKey !== cacheKey) this.cache.set(nameKey, this.cache.get(cacheKey));
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
this.lastFetch = Date.now();
|
|
2302
|
+
this.ready = true;
|
|
2303
|
+
require_db.logger.debug(`[Pricing] Cached pricing for ${this.cache.size} models from models.dev`);
|
|
2304
|
+
} catch (error) {
|
|
2305
|
+
require_db.logger.error(`[Pricing] Failed to fetch pricing data: ${error instanceof Error ? error.message : String(error)}`);
|
|
2306
|
+
if (this.cache.size === 0) throw error;
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
/**
|
|
2310
|
+
* Ensure cache is fresh, fetching if necessary
|
|
2311
|
+
*/
|
|
2312
|
+
async ensureFreshCache() {
|
|
2313
|
+
if (!(Date.now() - this.lastFetch > this.cacheTTL) && this.cache.size > 0) return;
|
|
2314
|
+
if (!this.fetchPromise) this.fetchPromise = this.fetchPricingData().finally(() => {
|
|
2315
|
+
this.fetchPromise = null;
|
|
2316
|
+
});
|
|
2317
|
+
await this.fetchPromise;
|
|
2318
|
+
}
|
|
2319
|
+
/**
|
|
2320
|
+
* Get pricing for a specific model
|
|
2321
|
+
*/
|
|
2322
|
+
async getModelPricing(provider, model) {
|
|
2323
|
+
await this.ensureFreshCache();
|
|
2324
|
+
const cacheKey = this.getCacheKey(provider, model);
|
|
2325
|
+
const pricing = this.cache.get(cacheKey);
|
|
2326
|
+
if (!pricing) {
|
|
2327
|
+
require_db.logger.debug(`[Pricing] No pricing found for ${provider}/${model}, trying partial match`);
|
|
2328
|
+
for (const [key, value] of this.cache.entries()) if (key.startsWith(`${provider.toLowerCase()}:`)) {
|
|
2329
|
+
const modelPart = key.split(":")[1];
|
|
2330
|
+
if (model.toLowerCase().includes(modelPart)) {
|
|
2331
|
+
require_db.logger.debug(`[Pricing] Found partial match: ${key}`);
|
|
2332
|
+
return value;
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
return null;
|
|
2336
|
+
}
|
|
2337
|
+
return pricing;
|
|
2338
|
+
}
|
|
2339
|
+
/**
|
|
2340
|
+
* Force refresh the pricing cache
|
|
2341
|
+
*/
|
|
2342
|
+
async refreshCache() {
|
|
2343
|
+
this.lastFetch = 0;
|
|
2344
|
+
await this.ensureFreshCache();
|
|
2345
|
+
}
|
|
2346
|
+
/**
|
|
2347
|
+
* Check if the provider is ready
|
|
2348
|
+
*/
|
|
2349
|
+
isReady() {
|
|
2350
|
+
return this.ready;
|
|
2351
|
+
}
|
|
2352
|
+
/**
|
|
2353
|
+
* Get the number of cached models (for debugging)
|
|
2354
|
+
*/
|
|
2355
|
+
getCacheSize() {
|
|
2356
|
+
return this.cache.size;
|
|
2357
|
+
}
|
|
2358
|
+
};
|
|
2359
|
+
let defaultProvider = null;
|
|
2360
|
+
/**
|
|
2361
|
+
* Get the default pricing provider instance
|
|
2362
|
+
*/
|
|
2363
|
+
function getDefaultPricingProvider() {
|
|
2364
|
+
if (!defaultProvider) defaultProvider = new ModelsDevPricingProvider();
|
|
2365
|
+
return defaultProvider;
|
|
2366
|
+
}
|
|
2367
|
+
|
|
1878
2368
|
//#endregion
|
|
1879
2369
|
exports.CacheService = CacheService;
|
|
1880
2370
|
exports.FileCacheBackend = FileCacheBackend;
|
|
1881
2371
|
exports.MS = MS;
|
|
1882
2372
|
exports.MemoryCacheBackend = MemoryCacheBackend;
|
|
2373
|
+
exports.ModelsDevPricingProvider = ModelsDevPricingProvider;
|
|
1883
2374
|
exports.SCHEMA_METADATA = require_db.SCHEMA_METADATA;
|
|
1884
2375
|
exports.SupportedProviders = SupportedProviders;
|
|
2376
|
+
exports.calculateCost = calculateCost;
|
|
1885
2377
|
exports.chatCompletionCreateParamsBaseSchema = chatCompletionCreateParamsBaseSchema;
|
|
1886
2378
|
exports.configVariantsSchema = require_db.configVariantsSchema;
|
|
1887
2379
|
exports.configsSchema = require_db.configsSchema;
|
|
1888
2380
|
exports.createDataLayer = createDataLayer;
|
|
1889
2381
|
exports.createDatabase = require_db.createDatabase;
|
|
1890
2382
|
exports.createDatabaseFromConnection = require_db.createDatabaseFromConnection;
|
|
2383
|
+
exports.createLLMRequestsDataLayer = createLLMRequestsDataLayer;
|
|
1891
2384
|
exports.detectDatabaseType = require_db.detectDatabaseType;
|
|
2385
|
+
exports.dollarsToMicroDollars = dollarsToMicroDollars;
|
|
1892
2386
|
exports.environmentSecretsSchema = require_db.environmentSecretsSchema;
|
|
1893
2387
|
exports.environmentsSchema = require_db.environmentsSchema;
|
|
2388
|
+
exports.formatCost = formatCost;
|
|
1894
2389
|
Object.defineProperty(exports, 'gateway', {
|
|
1895
2390
|
enumerable: true,
|
|
1896
2391
|
get: function () {
|
|
@@ -1898,10 +2393,13 @@ Object.defineProperty(exports, 'gateway', {
|
|
|
1898
2393
|
}
|
|
1899
2394
|
});
|
|
1900
2395
|
exports.generateId = generateId;
|
|
2396
|
+
exports.getDefaultPricingProvider = getDefaultPricingProvider;
|
|
1901
2397
|
exports.getMigrations = require_db.getMigrations;
|
|
2398
|
+
exports.llmRequestsSchema = require_db.llmRequestsSchema;
|
|
1902
2399
|
exports.llmopsConfigSchema = llmopsConfigSchema;
|
|
1903
2400
|
exports.logger = require_db.logger;
|
|
1904
2401
|
exports.matchType = require_db.matchType;
|
|
2402
|
+
exports.microDollarsToDollars = microDollarsToDollars;
|
|
1905
2403
|
exports.parsePartialTableData = require_db.parsePartialTableData;
|
|
1906
2404
|
exports.parseTableData = require_db.parseTableData;
|
|
1907
2405
|
exports.runAutoMigrations = require_db.runAutoMigrations;
|