@llmops/core 0.2.14 → 0.3.0-beta.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.
- package/dist/db/index.d.cts +1 -1
- package/dist/{index-Dm1Gi0cH.d.cts → index-DX05tkNg.d.cts} +35 -35
- package/dist/index.cjs +347 -4
- package/dist/index.d.cts +465 -272
- package/dist/index.d.mts +194 -1
- package/dist/index.mjs +344 -5
- package/package.json +4 -2
package/dist/index.d.mts
CHANGED
|
@@ -2,6 +2,7 @@ import { $ as variantsSchema, A as ProviderConfig, B as VariantVersionsTable, C
|
|
|
2
2
|
import gateway from "@llmops/gateway";
|
|
3
3
|
import { Kysely } from "kysely";
|
|
4
4
|
import pino from "pino";
|
|
5
|
+
import { RulesLogic } from "json-logic-js";
|
|
5
6
|
import * as zod33 from "zod";
|
|
6
7
|
import z$1, { z } from "zod";
|
|
7
8
|
import * as better_auth0 from "better-auth";
|
|
@@ -1392,6 +1393,9 @@ declare const getProviderConfigById: z$1.ZodObject<{
|
|
|
1392
1393
|
declare const getProviderConfigByProviderId: z$1.ZodObject<{
|
|
1393
1394
|
providerId: z$1.ZodString;
|
|
1394
1395
|
}, z$1.core.$strip>;
|
|
1396
|
+
declare const getProviderConfigBySlug: z$1.ZodObject<{
|
|
1397
|
+
slug: z$1.ZodString;
|
|
1398
|
+
}, z$1.core.$strip>;
|
|
1395
1399
|
declare const deleteProviderConfig: z$1.ZodObject<{
|
|
1396
1400
|
id: z$1.ZodUUID;
|
|
1397
1401
|
}, z$1.core.$strip>;
|
|
@@ -1440,6 +1444,16 @@ declare const createProviderConfigsDataLayer: (db: Kysely<Database>) => {
|
|
|
1440
1444
|
providerId: string;
|
|
1441
1445
|
config: Record<string, unknown>;
|
|
1442
1446
|
} | undefined>;
|
|
1447
|
+
getProviderConfigBySlug: (params: z$1.infer<typeof getProviderConfigBySlug>) => Promise<{
|
|
1448
|
+
slug: string | null;
|
|
1449
|
+
name: string | null;
|
|
1450
|
+
id: string;
|
|
1451
|
+
createdAt: Date;
|
|
1452
|
+
updatedAt: Date;
|
|
1453
|
+
enabled: boolean;
|
|
1454
|
+
providerId: string;
|
|
1455
|
+
config: Record<string, unknown>;
|
|
1456
|
+
} | undefined>;
|
|
1443
1457
|
deleteProviderConfig: (params: z$1.infer<typeof deleteProviderConfig>) => Promise<{
|
|
1444
1458
|
slug: string | null;
|
|
1445
1459
|
name: string | null;
|
|
@@ -1974,6 +1988,18 @@ declare const createDataLayer: (db: Kysely<Database>) => Promise<{
|
|
|
1974
1988
|
providerId: string;
|
|
1975
1989
|
config: Record<string, unknown>;
|
|
1976
1990
|
} | undefined>;
|
|
1991
|
+
getProviderConfigBySlug: (params: zod33.infer<zod33.ZodObject<{
|
|
1992
|
+
slug: zod33.ZodString;
|
|
1993
|
+
}, better_auth0.$strip>>) => Promise<{
|
|
1994
|
+
slug: string | null;
|
|
1995
|
+
name: string | null;
|
|
1996
|
+
id: string;
|
|
1997
|
+
createdAt: Date;
|
|
1998
|
+
updatedAt: Date;
|
|
1999
|
+
enabled: boolean;
|
|
2000
|
+
providerId: string;
|
|
2001
|
+
config: Record<string, unknown>;
|
|
2002
|
+
} | undefined>;
|
|
1977
2003
|
deleteProviderConfig: (params: zod33.infer<zod33.ZodObject<{
|
|
1978
2004
|
id: zod33.ZodUUID;
|
|
1979
2005
|
}, better_auth0.$strip>>) => Promise<{
|
|
@@ -2817,4 +2843,171 @@ interface AuthClientOptions {
|
|
|
2817
2843
|
*/
|
|
2818
2844
|
declare const getAuthClientOptions: (options: AuthClientOptions) => BetterAuthOptions;
|
|
2819
2845
|
//#endregion
|
|
2820
|
-
|
|
2846
|
+
//#region src/manifest/types.d.ts
|
|
2847
|
+
/**
|
|
2848
|
+
* Pre-computed variant version data needed for routing
|
|
2849
|
+
*/
|
|
2850
|
+
interface ManifestVariantVersion {
|
|
2851
|
+
id: string;
|
|
2852
|
+
variantId: string;
|
|
2853
|
+
version: number;
|
|
2854
|
+
provider: string;
|
|
2855
|
+
providerConfigId: string | null;
|
|
2856
|
+
modelName: string;
|
|
2857
|
+
jsonData: Record<string, unknown>;
|
|
2858
|
+
}
|
|
2859
|
+
/**
|
|
2860
|
+
* A targeting rule entry in the manifest
|
|
2861
|
+
* Pre-resolved with all necessary data for routing
|
|
2862
|
+
*/
|
|
2863
|
+
interface ManifestTargetingRule {
|
|
2864
|
+
id: string;
|
|
2865
|
+
configVariantId: string;
|
|
2866
|
+
variantVersionId: string | null;
|
|
2867
|
+
weight: number;
|
|
2868
|
+
priority: number;
|
|
2869
|
+
enabled: boolean;
|
|
2870
|
+
conditions: RulesLogic | null;
|
|
2871
|
+
resolvedVersion: ManifestVariantVersion;
|
|
2872
|
+
}
|
|
2873
|
+
/**
|
|
2874
|
+
* Config entry in the manifest
|
|
2875
|
+
*/
|
|
2876
|
+
interface ManifestConfig {
|
|
2877
|
+
id: string;
|
|
2878
|
+
slug: string;
|
|
2879
|
+
name: string | null;
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* Environment entry in the manifest
|
|
2883
|
+
*/
|
|
2884
|
+
interface ManifestEnvironment {
|
|
2885
|
+
id: string;
|
|
2886
|
+
slug: string;
|
|
2887
|
+
name: string;
|
|
2888
|
+
isProd: boolean;
|
|
2889
|
+
}
|
|
2890
|
+
/**
|
|
2891
|
+
* The complete routing manifest
|
|
2892
|
+
* Stored in cache under key: "gateway:manifest"
|
|
2893
|
+
*/
|
|
2894
|
+
interface GatewayManifest {
|
|
2895
|
+
version: number;
|
|
2896
|
+
builtAt: string;
|
|
2897
|
+
configs: Record<string, ManifestConfig>;
|
|
2898
|
+
configsBySlug: Record<string, string>;
|
|
2899
|
+
environments: Record<string, ManifestEnvironment>;
|
|
2900
|
+
environmentsBySlug: Record<string, string>;
|
|
2901
|
+
routingTable: Record<string, Record<string, ManifestTargetingRule[]>>;
|
|
2902
|
+
secretToEnvironment: Record<string, string>;
|
|
2903
|
+
}
|
|
2904
|
+
/**
|
|
2905
|
+
* Context passed to JSONLogic for condition evaluation
|
|
2906
|
+
*/
|
|
2907
|
+
interface RoutingContext {
|
|
2908
|
+
headers?: Record<string, string>;
|
|
2909
|
+
user?: {
|
|
2910
|
+
id?: string;
|
|
2911
|
+
email?: string;
|
|
2912
|
+
groups?: string[];
|
|
2913
|
+
[key: string]: unknown;
|
|
2914
|
+
};
|
|
2915
|
+
request?: {
|
|
2916
|
+
path?: string;
|
|
2917
|
+
method?: string;
|
|
2918
|
+
ip?: string;
|
|
2919
|
+
};
|
|
2920
|
+
custom?: Record<string, unknown>;
|
|
2921
|
+
timestamp?: number;
|
|
2922
|
+
}
|
|
2923
|
+
/**
|
|
2924
|
+
* Result of routing a request
|
|
2925
|
+
*/
|
|
2926
|
+
interface RoutingResult {
|
|
2927
|
+
configId: string;
|
|
2928
|
+
environmentId: string;
|
|
2929
|
+
variantId: string;
|
|
2930
|
+
version: ManifestVariantVersion;
|
|
2931
|
+
rule: ManifestTargetingRule;
|
|
2932
|
+
}
|
|
2933
|
+
//#endregion
|
|
2934
|
+
//#region src/manifest/builder.d.ts
|
|
2935
|
+
/**
|
|
2936
|
+
* Builds the gateway routing manifest from database
|
|
2937
|
+
*/
|
|
2938
|
+
declare class ManifestBuilder {
|
|
2939
|
+
private db;
|
|
2940
|
+
constructor(db: Kysely<Database>);
|
|
2941
|
+
/**
|
|
2942
|
+
* Build the complete routing manifest from database
|
|
2943
|
+
*/
|
|
2944
|
+
build(): Promise<GatewayManifest>;
|
|
2945
|
+
}
|
|
2946
|
+
//#endregion
|
|
2947
|
+
//#region src/manifest/service.d.ts
|
|
2948
|
+
declare class ManifestService {
|
|
2949
|
+
private cache;
|
|
2950
|
+
private ttlMs;
|
|
2951
|
+
private builder;
|
|
2952
|
+
constructor(cache: CacheService, db: Kysely<Database>, ttlMs?: number);
|
|
2953
|
+
/**
|
|
2954
|
+
* Get the current manifest, building if necessary
|
|
2955
|
+
*/
|
|
2956
|
+
getManifest(): Promise<GatewayManifest>;
|
|
2957
|
+
/**
|
|
2958
|
+
* Force invalidate the manifest (called on mutations)
|
|
2959
|
+
*/
|
|
2960
|
+
invalidate(): Promise<void>;
|
|
2961
|
+
/**
|
|
2962
|
+
* Invalidate and immediately rebuild (atomic refresh)
|
|
2963
|
+
*/
|
|
2964
|
+
refresh(): Promise<GatewayManifest>;
|
|
2965
|
+
/**
|
|
2966
|
+
* Get manifest version without fetching full manifest
|
|
2967
|
+
* Useful for checking if manifest is stale
|
|
2968
|
+
*/
|
|
2969
|
+
getVersion(): Promise<number | null>;
|
|
2970
|
+
/**
|
|
2971
|
+
* Check if manifest exists in cache
|
|
2972
|
+
*/
|
|
2973
|
+
hasManifest(): Promise<boolean>;
|
|
2974
|
+
}
|
|
2975
|
+
//#endregion
|
|
2976
|
+
//#region src/manifest/router.d.ts
|
|
2977
|
+
/**
|
|
2978
|
+
* Router for evaluating the gateway manifest and selecting variants
|
|
2979
|
+
*/
|
|
2980
|
+
declare class ManifestRouter {
|
|
2981
|
+
private manifest;
|
|
2982
|
+
constructor(manifest: GatewayManifest);
|
|
2983
|
+
/**
|
|
2984
|
+
* Resolve a config identifier (UUID or slug) to config ID
|
|
2985
|
+
*/
|
|
2986
|
+
resolveConfigId(configIdOrSlug: string): string | null;
|
|
2987
|
+
/**
|
|
2988
|
+
* Resolve environment from secret value
|
|
2989
|
+
*/
|
|
2990
|
+
resolveEnvironmentFromSecret(secretValue: string): string | null;
|
|
2991
|
+
/**
|
|
2992
|
+
* Get production environment ID
|
|
2993
|
+
*/
|
|
2994
|
+
getProductionEnvironmentId(): string | null;
|
|
2995
|
+
/**
|
|
2996
|
+
* Get environment by ID
|
|
2997
|
+
*/
|
|
2998
|
+
getEnvironment(environmentId: string): ManifestEnvironment;
|
|
2999
|
+
/**
|
|
3000
|
+
* Get config by ID
|
|
3001
|
+
*/
|
|
3002
|
+
getConfig(configId: string): ManifestConfig;
|
|
3003
|
+
/**
|
|
3004
|
+
* Route a request to the appropriate variant version (first match wins)
|
|
3005
|
+
*/
|
|
3006
|
+
route(configIdOrSlug: string, environmentId: string, context?: RoutingContext): RoutingResult | null;
|
|
3007
|
+
/**
|
|
3008
|
+
* Route with weighted random selection among matching rules of same priority
|
|
3009
|
+
*/
|
|
3010
|
+
routeWithWeights(configIdOrSlug: string, environmentId: string, context?: RoutingContext): RoutingResult | null;
|
|
3011
|
+
}
|
|
3012
|
+
//#endregion
|
|
3013
|
+
export { type AnthropicProviderConfig, type AnyProviderConfig, AuthClientDatabaseConfig, AuthClientOptions, type AzureAIProviderConfig, type AzureOpenAIProviderConfig, BaseCacheConfig, type BaseProviderConfig, type BedrockProviderConfig, CacheBackend, CacheBackendType, CacheConfig, CacheEntry, CacheOptions, CacheService, CacheStats, ChatCompletionCreateParamsBase, Config, ConfigVariant, type ConfigVariantsTable, type ConfigsTable, type CortexProviderConfig, CostResult, type Database, DatabaseConnection, DatabaseOptions, DatabaseType, Environment, EnvironmentSecret, type EnvironmentSecretsTable, type EnvironmentsTable, FileCacheBackend, FileCacheConfig, type FireworksAIProviderConfig, type GatewayManifest, type GoogleProviderConfig, type HuggingFaceProviderConfig, Insertable, LLMOpsClient, LLMOpsConfig, type LLMOpsConfigInput, LLMRequest, type LLMRequestInsert, LLMRequestsTable, MS, ManifestBuilder, type ManifestConfig, type ManifestEnvironment, ManifestRouter, ManifestService, type ManifestTargetingRule, type ManifestVariantVersion, MemoryCacheBackend, MemoryCacheConfig, MigrationOptions, MigrationResult, type MistralAIProviderConfig, ModelPricing, ModelsDevPricingProvider, type OpenAIProviderConfig, type OracleProviderConfig, Prettify, PricingProvider, ProviderConfig, type ProviderConfigMap, ProviderConfigsTable, type ProvidersConfig, type RoutingContext, type RoutingResult, SCHEMA_METADATA, type SagemakerProviderConfig, Selectable, type StabilityAIProviderConfig, SupportedProviders, type TableName, TargetingRule, type TargetingRulesTable, Updateable, UsageData, type ValidatedLLMOpsConfig, Variant, VariantJsonData, VariantVersion, VariantVersionsTable, type VariantsTable, type VertexAIProviderConfig, type WorkersAIProviderConfig, WorkspaceSettings, WorkspaceSettingsTable, calculateCost, chatCompletionCreateParamsBaseSchema, configVariantsSchema, configsSchema, createDataLayer, createDatabase, createDatabaseFromConnection, createLLMRequestsDataLayer, createNeonDialect, createProviderConfigsDataLayer, createWorkspaceSettingsDataLayer, detectDatabaseType, dollarsToMicroDollars, environmentSecretsSchema, environmentsSchema, executeWithSchema, formatCost, gateway, generateId, getAuthClientOptions, getDefaultPricingProvider, getMigrations, llmRequestsSchema, llmopsConfigSchema, logger, matchType, microDollarsToDollars, parsePartialTableData, parseTableData, providerConfigsSchema, runAutoMigrations, schemas, targetingRulesSchema, validateLLMOpsConfig, validatePartialTableData, validateTableData, variantJsonDataSchema, variantVersionsSchema, variantsSchema, workspaceSettingsSchema };
|
package/dist/index.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import * as fs from "node:fs/promises";
|
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import { createRandomStringGenerator } from "@better-auth/utils/random";
|
|
8
8
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
9
|
+
import jsonLogic from "json-logic-js";
|
|
9
10
|
|
|
10
11
|
//#region src/providers/supported-providers.ts
|
|
11
12
|
/**
|
|
@@ -837,7 +838,10 @@ var CacheService = class {
|
|
|
837
838
|
}
|
|
838
839
|
createBackend(config) {
|
|
839
840
|
switch (config.backend) {
|
|
840
|
-
case "memory": return new MemoryCacheBackend(
|
|
841
|
+
case "memory": return new MemoryCacheBackend({
|
|
842
|
+
maxSize: config.maxSize,
|
|
843
|
+
cleanupIntervalMs: config.cleanupInterval
|
|
844
|
+
});
|
|
841
845
|
case "file": return new FileCacheBackend(config.dataDir, config.fileName, config.saveInterval, config.cleanupInterval);
|
|
842
846
|
default: throw new Error(`Unsupported cache backend: ${config.backend}`);
|
|
843
847
|
}
|
|
@@ -1226,9 +1230,9 @@ const createConfigVariantDataLayer = (db) => {
|
|
|
1226
1230
|
const value = await getVariantJsonDataForConfig.safeParseAsync(params);
|
|
1227
1231
|
if (!value.success) throw new LLMOpsError(`Invalid parameters: ${value.error.message}`);
|
|
1228
1232
|
const { configId: configIdOrSlug, envSecret } = value.data;
|
|
1229
|
-
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1233
|
+
const UUID_REGEX$1 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1230
1234
|
let resolvedConfigId;
|
|
1231
|
-
if (UUID_REGEX.test(configIdOrSlug)) resolvedConfigId = configIdOrSlug;
|
|
1235
|
+
if (UUID_REGEX$1.test(configIdOrSlug)) resolvedConfigId = configIdOrSlug;
|
|
1232
1236
|
else {
|
|
1233
1237
|
const config = await db.selectFrom("configs").select("id").where("slug", "=", configIdOrSlug).executeTakeFirst();
|
|
1234
1238
|
if (!config) throw new LLMOpsError(`Config not found: ${configIdOrSlug}`);
|
|
@@ -1264,7 +1268,7 @@ const createConfigVariantDataLayer = (db) => {
|
|
|
1264
1268
|
if (!versionData) throw new LLMOpsError(`No variant version found for variant ${configVariant.variantId}`);
|
|
1265
1269
|
let finalProvider = versionData.provider;
|
|
1266
1270
|
let providerConfigId = null;
|
|
1267
|
-
if (UUID_REGEX.test(versionData.provider)) {
|
|
1271
|
+
if (UUID_REGEX$1.test(versionData.provider)) {
|
|
1268
1272
|
const providerConfig = await db.selectFrom("provider_configs").select(["id", "providerId"]).where("id", "=", versionData.provider).executeTakeFirst();
|
|
1269
1273
|
if (providerConfig) {
|
|
1270
1274
|
finalProvider = providerConfig.providerId;
|
|
@@ -1828,6 +1832,7 @@ const updateProviderConfig = zod_default.object({
|
|
|
1828
1832
|
});
|
|
1829
1833
|
const getProviderConfigById = zod_default.object({ id: zod_default.uuidv4() });
|
|
1830
1834
|
const getProviderConfigByProviderId = zod_default.object({ providerId: zod_default.string().min(1) });
|
|
1835
|
+
const getProviderConfigBySlug = zod_default.object({ slug: zod_default.string().min(1) });
|
|
1831
1836
|
const deleteProviderConfig = zod_default.object({ id: zod_default.uuidv4() });
|
|
1832
1837
|
const listProviderConfigs = zod_default.object({
|
|
1833
1838
|
limit: zod_default.number().int().positive().optional(),
|
|
@@ -1874,6 +1879,12 @@ const createProviderConfigsDataLayer = (db) => {
|
|
|
1874
1879
|
const { providerId } = value.data;
|
|
1875
1880
|
return db.selectFrom("provider_configs").selectAll().where("providerId", "=", providerId).executeTakeFirst();
|
|
1876
1881
|
},
|
|
1882
|
+
getProviderConfigBySlug: async (params) => {
|
|
1883
|
+
const value = await getProviderConfigBySlug.safeParseAsync(params);
|
|
1884
|
+
if (!value.success) throw new LLMOpsError(`Invalid parameters: ${value.error.message}`);
|
|
1885
|
+
const { slug } = value.data;
|
|
1886
|
+
return db.selectFrom("provider_configs").selectAll().where("slug", "=", slug).executeTakeFirst();
|
|
1887
|
+
},
|
|
1877
1888
|
deleteProviderConfig: async (params) => {
|
|
1878
1889
|
const value = await deleteProviderConfig.safeParseAsync(params);
|
|
1879
1890
|
if (!value.success) throw new LLMOpsError(`Invalid parameters: ${value.error.message}`);
|
|
@@ -2606,4 +2617,332 @@ function getDefaultPricingProvider() {
|
|
|
2606
2617
|
}
|
|
2607
2618
|
|
|
2608
2619
|
//#endregion
|
|
2609
|
-
|
|
2620
|
+
//#region src/manifest/builder.ts
|
|
2621
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2622
|
+
/**
|
|
2623
|
+
* Builds the gateway routing manifest from database
|
|
2624
|
+
*/
|
|
2625
|
+
var ManifestBuilder = class {
|
|
2626
|
+
constructor(db) {
|
|
2627
|
+
this.db = db;
|
|
2628
|
+
}
|
|
2629
|
+
/**
|
|
2630
|
+
* Build the complete routing manifest from database
|
|
2631
|
+
*/
|
|
2632
|
+
async build() {
|
|
2633
|
+
const [configs, environments, environmentSecrets, targetingRules, configVariants, variantVersions, providerConfigs] = await Promise.all([
|
|
2634
|
+
this.db.selectFrom("configs").selectAll().execute(),
|
|
2635
|
+
this.db.selectFrom("environments").selectAll().execute(),
|
|
2636
|
+
this.db.selectFrom("environment_secrets").selectAll().execute(),
|
|
2637
|
+
this.db.selectFrom("targeting_rules").where("enabled", "=", true).selectAll().execute(),
|
|
2638
|
+
this.db.selectFrom("config_variants").selectAll().execute(),
|
|
2639
|
+
this.db.selectFrom("variant_versions").selectAll().execute(),
|
|
2640
|
+
this.db.selectFrom("provider_configs").selectAll().execute()
|
|
2641
|
+
]);
|
|
2642
|
+
const manifestConfigs = {};
|
|
2643
|
+
const configsBySlug = {};
|
|
2644
|
+
for (const config of configs) {
|
|
2645
|
+
manifestConfigs[config.id] = {
|
|
2646
|
+
id: config.id,
|
|
2647
|
+
slug: config.slug,
|
|
2648
|
+
name: config.name ?? null
|
|
2649
|
+
};
|
|
2650
|
+
configsBySlug[config.slug] = config.id;
|
|
2651
|
+
}
|
|
2652
|
+
const manifestEnvironments = {};
|
|
2653
|
+
const environmentsBySlug = {};
|
|
2654
|
+
for (const env of environments) {
|
|
2655
|
+
manifestEnvironments[env.id] = {
|
|
2656
|
+
id: env.id,
|
|
2657
|
+
slug: env.slug,
|
|
2658
|
+
name: env.name,
|
|
2659
|
+
isProd: env.isProd
|
|
2660
|
+
};
|
|
2661
|
+
environmentsBySlug[env.slug] = env.id;
|
|
2662
|
+
}
|
|
2663
|
+
const secretToEnvironment = {};
|
|
2664
|
+
for (const secret of environmentSecrets) secretToEnvironment[secret.keyValue] = secret.environmentId;
|
|
2665
|
+
const configVariantMap = new Map(configVariants.map((cv) => [cv.id, cv]));
|
|
2666
|
+
const providerConfigMap = new Map(providerConfigs.map((pc) => [pc.id, pc]));
|
|
2667
|
+
const versionsByVariant = /* @__PURE__ */ new Map();
|
|
2668
|
+
for (const vv of variantVersions) {
|
|
2669
|
+
const list = versionsByVariant.get(vv.variantId) || [];
|
|
2670
|
+
list.push(vv);
|
|
2671
|
+
versionsByVariant.set(vv.variantId, list);
|
|
2672
|
+
}
|
|
2673
|
+
for (const list of versionsByVariant.values()) list.sort((a, b) => b.version - a.version);
|
|
2674
|
+
const versionById = new Map(variantVersions.map((vv) => [vv.id, vv]));
|
|
2675
|
+
const resolveProvider = (provider) => {
|
|
2676
|
+
if (UUID_REGEX.test(provider)) {
|
|
2677
|
+
const pc = providerConfigMap.get(provider);
|
|
2678
|
+
if (pc) return {
|
|
2679
|
+
providerId: pc.providerId,
|
|
2680
|
+
providerConfigId: pc.id
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2683
|
+
return {
|
|
2684
|
+
providerId: provider,
|
|
2685
|
+
providerConfigId: null
|
|
2686
|
+
};
|
|
2687
|
+
};
|
|
2688
|
+
const buildVersion = (vv) => {
|
|
2689
|
+
const { providerId, providerConfigId } = resolveProvider(vv.provider);
|
|
2690
|
+
const jsonData = typeof vv.jsonData === "string" ? JSON.parse(vv.jsonData) : vv.jsonData;
|
|
2691
|
+
return {
|
|
2692
|
+
id: vv.id,
|
|
2693
|
+
variantId: vv.variantId,
|
|
2694
|
+
version: vv.version,
|
|
2695
|
+
provider: providerId,
|
|
2696
|
+
providerConfigId,
|
|
2697
|
+
modelName: vv.modelName,
|
|
2698
|
+
jsonData
|
|
2699
|
+
};
|
|
2700
|
+
};
|
|
2701
|
+
const routingTable = {};
|
|
2702
|
+
for (const rule of targetingRules) {
|
|
2703
|
+
const configVariant = configVariantMap.get(rule.configVariantId);
|
|
2704
|
+
if (!configVariant) continue;
|
|
2705
|
+
const variantId = configVariant.variantId;
|
|
2706
|
+
let resolvedVersionData;
|
|
2707
|
+
if (rule.variantVersionId) resolvedVersionData = versionById.get(rule.variantVersionId);
|
|
2708
|
+
else resolvedVersionData = versionsByVariant.get(variantId)?.[0];
|
|
2709
|
+
if (!resolvedVersionData) continue;
|
|
2710
|
+
let conditions = null;
|
|
2711
|
+
if (rule.conditions) {
|
|
2712
|
+
const conditionsObj = typeof rule.conditions === "string" ? JSON.parse(rule.conditions) : rule.conditions;
|
|
2713
|
+
if (conditionsObj && Object.keys(conditionsObj).length > 0) conditions = conditionsObj;
|
|
2714
|
+
}
|
|
2715
|
+
const manifestRule = {
|
|
2716
|
+
id: rule.id,
|
|
2717
|
+
configVariantId: rule.configVariantId,
|
|
2718
|
+
variantVersionId: rule.variantVersionId,
|
|
2719
|
+
weight: rule.weight,
|
|
2720
|
+
priority: rule.priority,
|
|
2721
|
+
enabled: rule.enabled,
|
|
2722
|
+
conditions,
|
|
2723
|
+
resolvedVersion: buildVersion(resolvedVersionData)
|
|
2724
|
+
};
|
|
2725
|
+
if (!routingTable[rule.configId]) routingTable[rule.configId] = {};
|
|
2726
|
+
if (!routingTable[rule.configId][rule.environmentId]) routingTable[rule.configId][rule.environmentId] = [];
|
|
2727
|
+
routingTable[rule.configId][rule.environmentId].push(manifestRule);
|
|
2728
|
+
}
|
|
2729
|
+
for (const configRules of Object.values(routingTable)) for (const envRules of Object.values(configRules)) envRules.sort((a, b) => {
|
|
2730
|
+
if (b.priority !== a.priority) return b.priority - a.priority;
|
|
2731
|
+
return b.weight - a.weight;
|
|
2732
|
+
});
|
|
2733
|
+
return {
|
|
2734
|
+
version: Date.now(),
|
|
2735
|
+
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2736
|
+
configs: manifestConfigs,
|
|
2737
|
+
configsBySlug,
|
|
2738
|
+
environments: manifestEnvironments,
|
|
2739
|
+
environmentsBySlug,
|
|
2740
|
+
routingTable,
|
|
2741
|
+
secretToEnvironment
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
};
|
|
2745
|
+
|
|
2746
|
+
//#endregion
|
|
2747
|
+
//#region src/manifest/service.ts
|
|
2748
|
+
const MANIFEST_CACHE_KEY = "manifest";
|
|
2749
|
+
const MANIFEST_NAMESPACE = "gateway";
|
|
2750
|
+
const DEFAULT_TTL_MS = 300 * 1e3;
|
|
2751
|
+
const log = logger.child({ module: "ManifestService" });
|
|
2752
|
+
var ManifestService = class {
|
|
2753
|
+
builder;
|
|
2754
|
+
constructor(cache, db, ttlMs = DEFAULT_TTL_MS) {
|
|
2755
|
+
this.cache = cache;
|
|
2756
|
+
this.ttlMs = ttlMs;
|
|
2757
|
+
this.builder = new ManifestBuilder(db);
|
|
2758
|
+
log.debug({ ttlMs }, "ManifestService initialized");
|
|
2759
|
+
}
|
|
2760
|
+
/**
|
|
2761
|
+
* Get the current manifest, building if necessary
|
|
2762
|
+
*/
|
|
2763
|
+
async getManifest() {
|
|
2764
|
+
log.debug("Getting manifest from cache or building");
|
|
2765
|
+
const manifest = await this.cache.getOrSet(MANIFEST_CACHE_KEY, async () => {
|
|
2766
|
+
log.info("Building new manifest");
|
|
2767
|
+
const built = await this.builder.build();
|
|
2768
|
+
log.info({
|
|
2769
|
+
version: built.version,
|
|
2770
|
+
configCount: Object.keys(built.configs).length,
|
|
2771
|
+
environmentCount: Object.keys(built.environments).length
|
|
2772
|
+
}, "Manifest built successfully");
|
|
2773
|
+
return built;
|
|
2774
|
+
}, {
|
|
2775
|
+
namespace: MANIFEST_NAMESPACE,
|
|
2776
|
+
ttl: this.ttlMs
|
|
2777
|
+
});
|
|
2778
|
+
log.debug({ version: manifest.version }, "Manifest retrieved");
|
|
2779
|
+
return manifest;
|
|
2780
|
+
}
|
|
2781
|
+
/**
|
|
2782
|
+
* Force invalidate the manifest (called on mutations)
|
|
2783
|
+
*/
|
|
2784
|
+
async invalidate() {
|
|
2785
|
+
log.info("Invalidating manifest cache");
|
|
2786
|
+
await this.cache.delete(MANIFEST_CACHE_KEY, MANIFEST_NAMESPACE);
|
|
2787
|
+
}
|
|
2788
|
+
/**
|
|
2789
|
+
* Invalidate and immediately rebuild (atomic refresh)
|
|
2790
|
+
*/
|
|
2791
|
+
async refresh() {
|
|
2792
|
+
log.info("Refreshing manifest (invalidate + rebuild)");
|
|
2793
|
+
await this.invalidate();
|
|
2794
|
+
return this.getManifest();
|
|
2795
|
+
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Get manifest version without fetching full manifest
|
|
2798
|
+
* Useful for checking if manifest is stale
|
|
2799
|
+
*/
|
|
2800
|
+
async getVersion() {
|
|
2801
|
+
const version = (await this.cache.get(MANIFEST_CACHE_KEY, MANIFEST_NAMESPACE))?.version ?? null;
|
|
2802
|
+
log.debug({ version }, "Got manifest version");
|
|
2803
|
+
return version;
|
|
2804
|
+
}
|
|
2805
|
+
/**
|
|
2806
|
+
* Check if manifest exists in cache
|
|
2807
|
+
*/
|
|
2808
|
+
async hasManifest() {
|
|
2809
|
+
const exists = await this.cache.has(MANIFEST_CACHE_KEY, MANIFEST_NAMESPACE);
|
|
2810
|
+
log.debug({ exists }, "Checked manifest existence");
|
|
2811
|
+
return exists;
|
|
2812
|
+
}
|
|
2813
|
+
};
|
|
2814
|
+
|
|
2815
|
+
//#endregion
|
|
2816
|
+
//#region src/manifest/router.ts
|
|
2817
|
+
/**
|
|
2818
|
+
* Router for evaluating the gateway manifest and selecting variants
|
|
2819
|
+
*/
|
|
2820
|
+
var ManifestRouter = class {
|
|
2821
|
+
constructor(manifest) {
|
|
2822
|
+
this.manifest = manifest;
|
|
2823
|
+
}
|
|
2824
|
+
/**
|
|
2825
|
+
* Resolve a config identifier (UUID or slug) to config ID
|
|
2826
|
+
*/
|
|
2827
|
+
resolveConfigId(configIdOrSlug) {
|
|
2828
|
+
if (this.manifest.configs[configIdOrSlug]) return configIdOrSlug;
|
|
2829
|
+
return this.manifest.configsBySlug[configIdOrSlug] ?? null;
|
|
2830
|
+
}
|
|
2831
|
+
/**
|
|
2832
|
+
* Resolve environment from secret value
|
|
2833
|
+
*/
|
|
2834
|
+
resolveEnvironmentFromSecret(secretValue) {
|
|
2835
|
+
return this.manifest.secretToEnvironment[secretValue] ?? null;
|
|
2836
|
+
}
|
|
2837
|
+
/**
|
|
2838
|
+
* Get production environment ID
|
|
2839
|
+
*/
|
|
2840
|
+
getProductionEnvironmentId() {
|
|
2841
|
+
for (const env of Object.values(this.manifest.environments)) if (env.isProd) return env.id;
|
|
2842
|
+
return null;
|
|
2843
|
+
}
|
|
2844
|
+
/**
|
|
2845
|
+
* Get environment by ID
|
|
2846
|
+
*/
|
|
2847
|
+
getEnvironment(environmentId) {
|
|
2848
|
+
return this.manifest.environments[environmentId] ?? null;
|
|
2849
|
+
}
|
|
2850
|
+
/**
|
|
2851
|
+
* Get config by ID
|
|
2852
|
+
*/
|
|
2853
|
+
getConfig(configId) {
|
|
2854
|
+
return this.manifest.configs[configId] ?? null;
|
|
2855
|
+
}
|
|
2856
|
+
/**
|
|
2857
|
+
* Route a request to the appropriate variant version (first match wins)
|
|
2858
|
+
*/
|
|
2859
|
+
route(configIdOrSlug, environmentId, context = {}) {
|
|
2860
|
+
const configId = this.resolveConfigId(configIdOrSlug);
|
|
2861
|
+
if (!configId) return null;
|
|
2862
|
+
const rules = this.manifest.routingTable[configId]?.[environmentId];
|
|
2863
|
+
if (!rules || rules.length === 0) return null;
|
|
2864
|
+
const evalContext = {
|
|
2865
|
+
...context,
|
|
2866
|
+
timestamp: context.timestamp ?? Date.now()
|
|
2867
|
+
};
|
|
2868
|
+
for (const rule of rules) {
|
|
2869
|
+
if (!rule.enabled) continue;
|
|
2870
|
+
if (rule.conditions) try {
|
|
2871
|
+
if (!jsonLogic.apply(rule.conditions, evalContext)) continue;
|
|
2872
|
+
} catch (error) {
|
|
2873
|
+
console.warn(`JSONLogic evaluation error for rule ${rule.id}:`, error);
|
|
2874
|
+
continue;
|
|
2875
|
+
}
|
|
2876
|
+
return {
|
|
2877
|
+
configId,
|
|
2878
|
+
environmentId,
|
|
2879
|
+
variantId: rule.resolvedVersion.variantId,
|
|
2880
|
+
version: rule.resolvedVersion,
|
|
2881
|
+
rule
|
|
2882
|
+
};
|
|
2883
|
+
}
|
|
2884
|
+
return null;
|
|
2885
|
+
}
|
|
2886
|
+
/**
|
|
2887
|
+
* Route with weighted random selection among matching rules of same priority
|
|
2888
|
+
*/
|
|
2889
|
+
routeWithWeights(configIdOrSlug, environmentId, context = {}) {
|
|
2890
|
+
const configId = this.resolveConfigId(configIdOrSlug);
|
|
2891
|
+
if (!configId) return null;
|
|
2892
|
+
const rules = this.manifest.routingTable[configId]?.[environmentId];
|
|
2893
|
+
if (!rules || rules.length === 0) return null;
|
|
2894
|
+
const evalContext = {
|
|
2895
|
+
...context,
|
|
2896
|
+
timestamp: context.timestamp ?? Date.now()
|
|
2897
|
+
};
|
|
2898
|
+
const rulesByPriority = /* @__PURE__ */ new Map();
|
|
2899
|
+
for (const rule$1 of rules) {
|
|
2900
|
+
if (!rule$1.enabled) continue;
|
|
2901
|
+
if (rule$1.conditions) try {
|
|
2902
|
+
if (!jsonLogic.apply(rule$1.conditions, evalContext)) continue;
|
|
2903
|
+
} catch {
|
|
2904
|
+
continue;
|
|
2905
|
+
}
|
|
2906
|
+
const list = rulesByPriority.get(rule$1.priority) || [];
|
|
2907
|
+
list.push(rule$1);
|
|
2908
|
+
rulesByPriority.set(rule$1.priority, list);
|
|
2909
|
+
}
|
|
2910
|
+
const priorities = Array.from(rulesByPriority.keys()).sort((a, b) => b - a);
|
|
2911
|
+
if (priorities.length === 0) return null;
|
|
2912
|
+
const topPriorityRules = rulesByPriority.get(priorities[0]);
|
|
2913
|
+
if (topPriorityRules.length === 1) {
|
|
2914
|
+
const rule$1 = topPriorityRules[0];
|
|
2915
|
+
return {
|
|
2916
|
+
configId,
|
|
2917
|
+
environmentId,
|
|
2918
|
+
variantId: rule$1.resolvedVersion.variantId,
|
|
2919
|
+
version: rule$1.resolvedVersion,
|
|
2920
|
+
rule: rule$1
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
const totalWeight = topPriorityRules.reduce((sum, r) => sum + r.weight, 0);
|
|
2924
|
+
if (totalWeight === 0) return null;
|
|
2925
|
+
let random = Math.random() * totalWeight;
|
|
2926
|
+
for (const rule$1 of topPriorityRules) {
|
|
2927
|
+
random -= rule$1.weight;
|
|
2928
|
+
if (random <= 0) return {
|
|
2929
|
+
configId,
|
|
2930
|
+
environmentId,
|
|
2931
|
+
variantId: rule$1.resolvedVersion.variantId,
|
|
2932
|
+
version: rule$1.resolvedVersion,
|
|
2933
|
+
rule: rule$1
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2936
|
+
const rule = topPriorityRules[0];
|
|
2937
|
+
return {
|
|
2938
|
+
configId,
|
|
2939
|
+
environmentId,
|
|
2940
|
+
variantId: rule.resolvedVersion.variantId,
|
|
2941
|
+
version: rule.resolvedVersion,
|
|
2942
|
+
rule
|
|
2943
|
+
};
|
|
2944
|
+
}
|
|
2945
|
+
};
|
|
2946
|
+
|
|
2947
|
+
//#endregion
|
|
2948
|
+
export { CacheService, FileCacheBackend, MS, ManifestBuilder, ManifestRouter, ManifestService, MemoryCacheBackend, ModelsDevPricingProvider, SCHEMA_METADATA, SupportedProviders, calculateCost, chatCompletionCreateParamsBaseSchema, configVariantsSchema, configsSchema, createDataLayer, createDatabase, createDatabaseFromConnection, createLLMRequestsDataLayer, createNeonDialect, createProviderConfigsDataLayer, createWorkspaceSettingsDataLayer, detectDatabaseType, dollarsToMicroDollars, environmentSecretsSchema, environmentsSchema, executeWithSchema, formatCost, gateway, generateId, getAuthClientOptions, getDefaultPricingProvider, getMigrations, llmRequestsSchema, llmopsConfigSchema, logger, matchType, microDollarsToDollars, parsePartialTableData, parseTableData, providerConfigsSchema, runAutoMigrations, schemas, targetingRulesSchema, validateLLMOpsConfig, validatePartialTableData, validateTableData, variantJsonDataSchema, variantVersionsSchema, variantsSchema, workspaceSettingsSchema };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llmops/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-beta.1",
|
|
4
4
|
"description": "Core LLMOps functionality and utilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -51,12 +51,14 @@
|
|
|
51
51
|
"@neondatabase/serverless": "^1.0.2",
|
|
52
52
|
"better-auth": "^1.4.10",
|
|
53
53
|
"hono": "^4.10.7",
|
|
54
|
+
"json-logic-js": "^2.0.5",
|
|
54
55
|
"kysely": "^0.28.8",
|
|
55
56
|
"kysely-neon": "^2.0.2",
|
|
56
57
|
"pino": "^10.1.0",
|
|
57
|
-
"@llmops/gateway": "^0.
|
|
58
|
+
"@llmops/gateway": "^0.3.0-beta.1"
|
|
58
59
|
},
|
|
59
60
|
"devDependencies": {
|
|
61
|
+
"@types/json-logic-js": "^2.0.8",
|
|
60
62
|
"@types/ws": "^8.18.1"
|
|
61
63
|
},
|
|
62
64
|
"scripts": {
|