Skip to content

Commit 9e2b578

Browse files
committed
give the llm model registry a short window to become ready when the server first tries to enrich spans so we don't miss enriching the first Xms of spans every time a server boots
1 parent d5a32be commit 9e2b578

File tree

5 files changed

+47
-13
lines changed

5 files changed

+47
-13
lines changed

apps/webapp/app/env.server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,7 @@ const EnvironmentSchema = z
12871287
LLM_COST_TRACKING_ENABLED: BoolEnv.default(true),
12881288
LLM_PRICING_RELOAD_INTERVAL_MS: z.coerce.number().int().default(5 * 60 * 1000), // 5 minutes
12891289
LLM_PRICING_SEED_ON_STARTUP: BoolEnv.default(false),
1290+
LLM_PRICING_READY_TIMEOUT_MS: z.coerce.number().int().default(500),
12901291

12911292
// Bootstrap
12921293
TRIGGER_BOOTSTRAP_ENABLED: z.string().default("0"),

apps/webapp/app/v3/llmPricingRegistry.server.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { ModelPricingRegistry, seedLlmPricing } from "@internal/llm-pricing";
22
import { prisma, $replica } from "~/db.server";
33
import { env } from "~/env.server";
4+
import { signalsEmitter } from "~/services/signals.server";
45
import { singleton } from "~/utils/singleton";
56
import { setLlmPricingRegistry } from "./utils/enrichCreatableEvents.server";
67

78
async function initRegistry(registry: ModelPricingRegistry) {
89
if (env.LLM_PRICING_SEED_ON_STARTUP) {
9-
const result = await seedLlmPricing(prisma);
10+
await seedLlmPricing(prisma);
1011
}
1112

1213
await registry.loadFromDatabase();
@@ -28,15 +29,34 @@ export const llmPricingRegistry = singleton("llmPricingRegistry", () => {
2829

2930
// Periodic reload
3031
const reloadInterval = env.LLM_PRICING_RELOAD_INTERVAL_MS;
31-
setInterval(() => {
32-
registry
33-
.reload()
34-
.then(() => {
35-
})
36-
.catch((err) => {
37-
console.error("Failed to reload LLM pricing registry", err);
38-
});
32+
const interval = setInterval(() => {
33+
registry.reload().catch((err) => {
34+
console.error("Failed to reload LLM pricing registry", err);
35+
});
3936
}, reloadInterval);
4037

38+
signalsEmitter.on("SIGTERM", () => {
39+
clearInterval(interval);
40+
});
41+
signalsEmitter.on("SIGINT", () => {
42+
clearInterval(interval);
43+
});
44+
4145
return registry;
4246
});
47+
48+
/**
49+
* Wait for the LLM pricing registry to finish its initial load, with a timeout.
50+
* After the first call resolves (or times out), subsequent calls are no-ops.
51+
*/
52+
export async function waitForLlmPricingReady(): Promise<void> {
53+
if (!llmPricingRegistry || llmPricingRegistry.isLoaded) return;
54+
55+
const timeoutMs = env.LLM_PRICING_READY_TIMEOUT_MS;
56+
if (timeoutMs <= 0) return;
57+
58+
await Promise.race([
59+
llmPricingRegistry.isReady,
60+
new Promise<void>((resolve) => setTimeout(resolve, timeoutMs)),
61+
]);
62+
}

apps/webapp/app/v3/otlpExporter.server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import type {
3737
} from "./eventRepository/eventRepository.types";
3838
import { startSpan } from "./tracing.server";
3939
import { enrichCreatableEvents } from "./utils/enrichCreatableEvents.server";
40-
import "./llmPricingRegistry.server"; // Initialize LLM pricing registry on startup
40+
import { waitForLlmPricingReady } from "./llmPricingRegistry.server";
4141
import { env } from "~/env.server";
4242
import { detectBadJsonStrings } from "~/utils/detectBadJsonStrings";
4343
import { singleton } from "~/utils/singleton";
@@ -129,6 +129,7 @@ class OTLPExporter {
129129
for (const [store, events] of Object.entries(eventsGroupedByStore)) {
130130
const eventRepository = this.#getEventRepositoryForStore(store);
131131

132+
await waitForLlmPricingReady();
132133
const enrichedEvents = enrichCreatableEvents(events);
133134

134135
this.#logEventsVerbose(enrichedEvents, `exportEvents ${store}`);

apps/webapp/app/v3/utils/enrichCreatableEvents.server.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ type CostRegistry = {
2020

2121
let _registry: CostRegistry | undefined;
2222

23+
const ENRICHABLE_KINDS = new Set(["INTERNAL", "SERVER", "CLIENT", "CONSUMER", "PRODUCER"]);
24+
2325
export function setLlmPricingRegistry(registry: CostRegistry): void {
2426
_registry = registry;
2527
}
@@ -46,8 +48,7 @@ function enrichLlmMetrics(event: CreateEventInput): void {
4648
if (!props) return;
4749

4850
// Only enrich span-like events (INTERNAL, SERVER, CLIENT, CONSUMER, PRODUCER — not LOG, UNSPECIFIED)
49-
const enrichableKinds = new Set(["INTERNAL", "SERVER", "CLIENT", "CONSUMER", "PRODUCER"]);
50-
if (!enrichableKinds.has(event.kind as string)) return;
51+
if (!ENRICHABLE_KINDS.has(event.kind as string)) return;
5152

5253
// Skip partial spans (they don't have final token counts)
5354
if (event.isPartial) return;

internal-packages/llm-pricing/src/registry.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,16 @@ export class ModelPricingRegistry {
2424
private _patterns: CompiledPattern[] = [];
2525
private _exactMatchCache: Map<string, LlmModelWithPricing | null> = new Map();
2626
private _loaded = false;
27+
private _readyResolve!: () => void;
28+
29+
/** Resolves once the initial `loadFromDatabase()` completes successfully. */
30+
readonly isReady: Promise<void>;
2731

2832
constructor(prisma: PrismaClient | PrismaReplicaClient) {
2933
this._prisma = prisma;
34+
this.isReady = new Promise<void>((resolve) => {
35+
this._readyResolve = resolve;
36+
});
3037
}
3138

3239
get isLoaded(): boolean {
@@ -81,7 +88,11 @@ export class ModelPricingRegistry {
8188

8289
this._patterns = compiled;
8390
this._exactMatchCache.clear();
84-
this._loaded = true;
91+
92+
if (!this._loaded) {
93+
this._loaded = true;
94+
this._readyResolve();
95+
}
8596
}
8697

8798
async reload(): Promise<void> {

0 commit comments

Comments
 (0)