Skip to content

Commit 4c3f4ac

Browse files
committed
Make genai prices auto-update in the worker
1 parent 4e677db commit 4c3f4ac

File tree

4 files changed

+78
-5
lines changed

4 files changed

+78
-5
lines changed

gateway/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"@opentelemetry/api": "^1.9.0",
1212
"@opentelemetry/exporter-trace-otlp-http": "^0.203.0",
1313
"@opentelemetry/resources": "^2.0.1",
14-
"@pydantic/genai-prices": "~0.0.36",
14+
"@pydantic/genai-prices": "~0.0.38",
1515
"@pydantic/logfire-api": "^0.9.0",
1616
"eventsource-parser": "^3.0.6",
1717
"mime-types": "^3.0.1",

gateway/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type { KeysDb, LimitDb } from './db'
1919
import { gateway } from './gateway'
2020
import type { DefaultProviderProxy, Middleware, Next } from './providers/default'
2121
import type { RateLimiter } from './rateLimiter'
22+
import { setupPriceAutoUpdate } from './setupPriceAutoUpdate'
2223
import type { SubFetch } from './types'
2324
import { ctHeader, ResponseError, response405, textResponse } from './utils'
2425

@@ -48,6 +49,7 @@ export async function gatewayFetch(
4849
ctx: ExecutionContext,
4950
options: GatewayOptions,
5051
): Promise<Response> {
52+
await setupPriceAutoUpdate()
5153
let { pathname: proxyPath, search: queryString } = url
5254
if (options.proxyPrefixLength) {
5355
proxyPath = proxyPath.slice(options.proxyPrefixLength)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { type Provider, updatePrices, waitForUpdate } from '@pydantic/genai-prices'
2+
3+
// data will be refetched every 30 minutes
4+
const PRICE_TTL = 1000 * 60 * 30
5+
let genaiData: Provider[] | null = null
6+
let genaiDataTimestamp: number | null = null
7+
let isFetching = false
8+
let isSetup = false
9+
10+
export async function setupPriceAutoUpdate() {
11+
if (!isSetup) {
12+
doSetupPriceAutoUpdate()
13+
isSetup = true
14+
}
15+
16+
// this will await the eventual fresh genai price fetching
17+
// Note: if the data is fresh, the updatePrices will immediately resolve, so there's no performance cost to always await this.
18+
await waitForUpdate()
19+
}
20+
21+
function doSetupPriceAutoUpdate() {
22+
updatePrices(({ setProviderData, remoteDataUrl }) => {
23+
if (genaiDataTimestamp !== null) {
24+
console.debug('genai prices local storage data found')
25+
26+
if (genaiData !== null) {
27+
setProviderData(genaiData)
28+
}
29+
30+
if (Date.now() - genaiDataTimestamp < PRICE_TTL) {
31+
// this will be the most frequent, cheap path
32+
console.debug('genai prices local storage data is fresh')
33+
return
34+
} else {
35+
console.debug('genai prices local storage data is stale, attempting to fetch remote data')
36+
}
37+
}
38+
39+
if (isFetching) {
40+
console.debug('genai-prices data fetch already in progress, skipping')
41+
return
42+
}
43+
44+
console.debug('genai-prices data is stale')
45+
isFetching = true
46+
47+
// It's important **not** to await this promise
48+
const freshDataPromise = fetch(remoteDataUrl)
49+
.then(async (response) => {
50+
if (!response.ok) {
51+
console.error('Failed fetching provider data, response status %d', response.status)
52+
return null
53+
}
54+
55+
const freshData = (await response.json()) as Provider[]
56+
console.debug('Updated genai prices data, %d providers', freshData.length)
57+
genaiDataTimestamp = Date.now()
58+
genaiData = freshData
59+
return freshData
60+
})
61+
.catch((error: unknown) => {
62+
console.error('Failed fetching provider data err: %o', error)
63+
return null
64+
})
65+
.finally(() => {
66+
isFetching = false
67+
})
68+
69+
setProviderData(freshDataPromise)
70+
})
71+
}

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)