Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions web/src/features/system-settings/models/model-pricing-core.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import assert from 'node:assert/strict'
import { describe, test } from 'node:test'

import { buildModelPricingOptionUpdates } from './model-pricing-core'

describe('model pricing option updates', () => {
test('uses per-token fields when switching away from stale per-request price', () => {
const updates = buildModelPricingOptionUpdates({
current: {
modelPrice: JSON.stringify({ 'gpt-test': 0.01 }),
modelRatio: '{}',
cacheRatio: '{}',
createCacheRatio: '{}',
completionRatio: '{}',
imageRatio: '{}',
audioRatio: '{}',
audioCompletionRatio: '{}',
billingMode: JSON.stringify({ 'gpt-test': 'tiered_expr' }),
billingExpr: JSON.stringify({ 'gpt-test': 'tier("old", p)' }),
},
data: {
name: 'gpt-test',
billingMode: 'per-token',
price: '0.01',
ratio: '1.5',
completionRatio: '2',
},
})

assert.deepEqual(JSON.parse(updates.ModelPrice), {})
assert.deepEqual(JSON.parse(updates.ModelRatio), { 'gpt-test': 1.5 })
assert.deepEqual(JSON.parse(updates.CompletionRatio), { 'gpt-test': 2 })
assert.deepEqual(JSON.parse(updates['billing_setting.billing_mode']), {})
assert.deepEqual(JSON.parse(updates['billing_setting.billing_expr']), {})
})

test('uses per-request price when switching away from stale token ratios', () => {
const updates = buildModelPricingOptionUpdates({
current: {
modelPrice: '{}',
modelRatio: JSON.stringify({ 'gpt-test': 1.5 }),
cacheRatio: '{}',
createCacheRatio: '{}',
completionRatio: JSON.stringify({ 'gpt-test': 2 }),
imageRatio: '{}',
audioRatio: '{}',
audioCompletionRatio: '{}',
billingMode: '{}',
billingExpr: '{}',
},
data: {
name: 'gpt-test',
billingMode: 'per-request',
price: '0.01',
ratio: '1.5',
completionRatio: '2',
},
})

assert.deepEqual(JSON.parse(updates.ModelPrice), { 'gpt-test': 0.01 })
assert.deepEqual(JSON.parse(updates.ModelRatio), {})
assert.deepEqual(JSON.parse(updates.CompletionRatio), {})
})

test('keeps fallback price and token ratios when switching into tiered expression mode', () => {
const updates = buildModelPricingOptionUpdates({
current: {
modelPrice: JSON.stringify({ 'gpt-test': 0.01 }),
modelRatio: JSON.stringify({ 'gpt-test': 1.5 }),
cacheRatio: '{}',
createCacheRatio: '{}',
completionRatio: JSON.stringify({ 'gpt-test': 2 }),
imageRatio: '{}',
audioRatio: '{}',
audioCompletionRatio: '{}',
billingMode: '{}',
billingExpr: '{}',
},
data: {
name: 'gpt-test',
billingMode: 'tiered_expr',
price: '0.02',
ratio: '2',
completionRatio: '3',
billingExpr: 'tier("base", p * 0 + c * 0)',
},
})

assert.deepEqual(JSON.parse(updates.ModelPrice), { 'gpt-test': 0.02 })
assert.deepEqual(JSON.parse(updates.ModelRatio), { 'gpt-test': 2 })
assert.deepEqual(JSON.parse(updates.CompletionRatio), { 'gpt-test': 3 })
assert.deepEqual(JSON.parse(updates['billing_setting.billing_mode']), {
'gpt-test': 'tiered_expr',
})
assert.deepEqual(JSON.parse(updates['billing_setting.billing_expr']), {
'gpt-test': 'tier("base", p * 0 + c * 0)',
})
})
})
191 changes: 191 additions & 0 deletions web/src/features/system-settings/models/model-pricing-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import * as z from 'zod'

import { combineBillingExpr } from '@/features/pricing/lib/billing-expr'

import { safeJsonParse } from '../utils/json-parser'
import { formatPricingNumber } from './pricing-format'

export const createModelPricingSchema = (t: (key: string) => string) =>
Expand Down Expand Up @@ -64,6 +65,92 @@ export type ModelRatioData = {
requestRuleExpr?: string
}

export type ModelPricingOptionInput = {
modelPrice: string
modelRatio: string
cacheRatio: string
createCacheRatio: string
completionRatio: string
imageRatio: string
audioRatio: string
audioCompletionRatio: string
billingMode: string
billingExpr: string
}

export type ModelPricingOptionUpdates = {
ModelPrice: string
ModelRatio: string
CacheRatio: string
CreateCacheRatio: string
CompletionRatio: string
ImageRatio: string
AudioRatio: string
AudioCompletionRatio: string
'billing_setting.billing_mode': string
'billing_setting.billing_expr': string
}

type NumericOptionCurrentKey =
| 'modelPrice'
| 'modelRatio'
| 'cacheRatio'
| 'createCacheRatio'
| 'completionRatio'
| 'imageRatio'
| 'audioRatio'
| 'audioCompletionRatio'

type NumericOptionOutputKey =
| 'ModelPrice'
| 'ModelRatio'
| 'CacheRatio'
| 'CreateCacheRatio'
| 'CompletionRatio'
| 'ImageRatio'
| 'AudioRatio'
| 'AudioCompletionRatio'

type NumericOptionDataKey =
| 'price'
| 'ratio'
| 'cacheRatio'
| 'createCacheRatio'
| 'completionRatio'
| 'imageRatio'
| 'audioRatio'
| 'audioCompletionRatio'

const pricingMapFields: Array<{
currentKey: NumericOptionCurrentKey
outputKey: NumericOptionOutputKey
dataKey: NumericOptionDataKey
}> = [
{ currentKey: 'modelPrice', outputKey: 'ModelPrice', dataKey: 'price' },
{ currentKey: 'modelRatio', outputKey: 'ModelRatio', dataKey: 'ratio' },
{ currentKey: 'cacheRatio', outputKey: 'CacheRatio', dataKey: 'cacheRatio' },
{
currentKey: 'createCacheRatio',
outputKey: 'CreateCacheRatio',
dataKey: 'createCacheRatio',
},
{
currentKey: 'completionRatio',
outputKey: 'CompletionRatio',
dataKey: 'completionRatio',
},
{ currentKey: 'imageRatio', outputKey: 'ImageRatio', dataKey: 'imageRatio' },
{ currentKey: 'audioRatio', outputKey: 'AudioRatio', dataKey: 'audioRatio' },
{
currentKey: 'audioCompletionRatio',
outputKey: 'AudioCompletionRatio',
dataKey: 'audioCompletionRatio',
},
]

const priceMapField = pricingMapFields[0]
const ratioMapFields = pricingMapFields.slice(1)

export type PreviewRow = {
key: string
label: string
Expand Down Expand Up @@ -156,6 +243,110 @@ export function toNumberOrNull(value: unknown): number | null {
return Number.isFinite(num) ? num : null
}

export function buildModelPricingOptionUpdates({
current,
data,
targetNames = [data.name],
}: {
current: ModelPricingOptionInput
data: ModelRatioData
targetNames?: string[]
}): ModelPricingOptionUpdates {
const pricingMaps = Object.fromEntries(
pricingMapFields.map((field) => [
field.outputKey,
safeJsonParse<Record<string, number>>(current[field.currentKey], {
fallback: {},
silent: true,
}),
])
) as Record<NumericOptionOutputKey, Record<string, number>>

const priceMap = pricingMaps.ModelPrice

const setFieldIfPresent = (
field: (typeof pricingMapFields)[number],
name: string
) => {
setIfPresent(pricingMaps[field.outputKey], name, data[field.dataKey])
}

const setFieldsIfPresent = (
fields: typeof pricingMapFields,
name: string
) => {
fields.forEach((field) => setFieldIfPresent(field, name))
}

const billingModeMap = safeJsonParse<Record<string, string>>(
current.billingMode,
{ fallback: {}, silent: true }
)
const billingExprMap = safeJsonParse<Record<string, string>>(
current.billingExpr,
{ fallback: {}, silent: true }
)

const setIfPresent = (
target: Record<string, number>,
name: string,
value: string | undefined
) => {
if (!value || value === '') return
const parsed = Number.parseFloat(value)
if (Number.isFinite(parsed)) target[name] = parsed
}

targetNames.forEach((name) => {
pricingMapFields.forEach((field) => {
delete pricingMaps[field.outputKey][name]
})
delete billingModeMap[name]
delete billingExprMap[name]

const mode =
data.billingMode ||
(data.price && data.price !== '' ? 'per-request' : 'per-token')

if (mode === 'tiered_expr') {
const combined = combineBillingExpr(
data.billingExpr || '',
data.requestRuleExpr || ''
)
if (combined) {
billingModeMap[name] = 'tiered_expr'
billingExprMap[name] = combined
}
setFieldsIfPresent(pricingMapFields, name)
return
}

if (mode === 'per-request') {
setFieldIfPresent(priceMapField, name)
return
}

setFieldsIfPresent(ratioMapFields, name)
})

return {
ModelPrice: JSON.stringify(priceMap, null, 2),
ModelRatio: JSON.stringify(pricingMaps.ModelRatio, null, 2),
CacheRatio: JSON.stringify(pricingMaps.CacheRatio, null, 2),
CreateCacheRatio: JSON.stringify(pricingMaps.CreateCacheRatio, null, 2),
CompletionRatio: JSON.stringify(pricingMaps.CompletionRatio, null, 2),
ImageRatio: JSON.stringify(pricingMaps.ImageRatio, null, 2),
AudioRatio: JSON.stringify(pricingMaps.AudioRatio, null, 2),
AudioCompletionRatio: JSON.stringify(
pricingMaps.AudioCompletionRatio,
null,
2
),
'billing_setting.billing_mode': JSON.stringify(billingModeMap, null, 2),
'billing_setting.billing_expr': JSON.stringify(billingExprMap, null, 2),
}
}

function ratioToBasePrice(ratio: unknown): string {
const num = toNumberOrNull(ratio)
if (num === null) return ''
Expand Down
Loading