fix provider model pricing

This commit is contained in:
Shroominic
2026-01-29 08:50:17 +08:00
parent 5e12a7e92d
commit 73e3d34623
4 changed files with 62 additions and 75 deletions
+6 -6
View File
@@ -2516,7 +2516,7 @@ async def get_provider_model(provider_id: int, model_id: str) -> dict[str, objec
status_code=404, detail="Model not found for this provider"
)
return _row_to_model(
row, apply_provider_fee=True, provider_fee=provider.provider_fee
row, apply_provider_fee=False, provider_fee=provider.provider_fee
).dict() # type: ignore
@@ -2729,7 +2729,10 @@ async def get_provider_models(provider_id: int) -> dict[str, object]:
raise HTTPException(status_code=404, detail="Provider not found")
db_models = await list_models(
session=session, upstream_id=provider_id, include_disabled=True
session=session,
upstream_id=provider_id,
include_disabled=True,
apply_fees=False,
)
upstream_models = []
@@ -2737,10 +2740,7 @@ async def get_provider_models(provider_id: int) -> dict[str, object]:
if upstream_instance:
try:
raw_models = await upstream_instance.fetch_models()
upstream_models = [
upstream_instance._apply_provider_fee_to_model(m)
for m in raw_models
]
upstream_models = raw_models
except Exception as e:
logger.error(
f"Failed to fetch models from {provider.provider_type}: {e}"
+2 -1
View File
@@ -230,6 +230,7 @@ async def list_models(
session: AsyncSession,
upstream_id: int,
include_disabled: bool = False,
apply_fees: bool = True,
) -> list[Model]:
from sqlmodel import select
@@ -247,7 +248,7 @@ async def list_models(
return [
_row_to_model(
r,
apply_provider_fee=True,
apply_provider_fee=apply_fees,
provider_fee=providers_by_id[r.upstream_provider_id].provider_fee
if r.upstream_provider_id in providers_by_id
else 1.01,
-39
View File
@@ -808,45 +808,6 @@ export function AddProviderModelDialog({
</FormItem>
)}
/>
<FormField
control={form.control}
name='max_prompt_cost'
render={({ field }) => (
<FormItem>
<FormLabel>Max Prompt Cost</FormLabel>
<FormControl>
<Input type='number' step='0.0001' {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='max_completion_cost'
render={({ field }) => (
<FormItem>
<FormLabel>Max Completion Cost</FormLabel>
<FormControl>
<Input type='number' step='0.0001' {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='max_cost'
render={({ field }) => (
<FormItem>
<FormLabel>Max Total Cost</FormLabel>
<FormControl>
<Input type='number' step='0.0001' {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
+54 -29
View File
@@ -139,18 +139,26 @@ export class AdminService {
): Record<string, unknown> {
if (!pricing) return pricing;
const result = { ...pricing };
if (typeof result.prompt === 'number') {
result.prompt = result.prompt * 1000000;
}
if (typeof result.completion === 'number') {
result.completion = result.completion * 1000000;
}
if (typeof result.request === 'number') {
result.request = result.request * 1000000;
}
if (typeof result.image === 'number') {
result.image = result.image * 1000000;
}
// Only prompt and completion are per-token and need scaling to per-1M
const convertField = (field: string) => {
const val = result[field];
if (val !== undefined && val !== null) {
const num = typeof val === 'string' ? parseFloat(val) : (val as number);
if (!isNaN(num)) {
// Multiply by 1M and round to avoid floating point artifacts (e.g. 0.40399999999999997)
// 9 decimals is plenty for USD/1M tokens (0.000000001)
result[field] = parseFloat((num * 1000000).toFixed(9));
}
}
};
convertField('prompt');
convertField('completion');
// Other fields (request, image, etc.) are already flat fees (per item)
// so we do NOT scale them.
return result;
}
@@ -159,18 +167,23 @@ export class AdminService {
): Record<string, unknown> {
if (!pricing) return pricing;
const result = { ...pricing };
if (typeof result.prompt === 'number') {
result.prompt = result.prompt / 1000000;
}
if (typeof result.completion === 'number') {
result.completion = result.completion / 1000000;
}
if (typeof result.request === 'number') {
result.request = result.request / 1000000;
}
if (typeof result.image === 'number') {
result.image = result.image / 1000000;
}
// Only prompt and completion are per-1M in UI and need scaling down to per-token
const convertField = (field: string) => {
const val = result[field];
if (val !== undefined && val !== null) {
const num = typeof val === 'string' ? parseFloat(val) : (val as number);
if (!isNaN(num)) {
result[field] = num / 1000000;
}
}
};
convertField('prompt');
convertField('completion');
// Other fields stay as flat fees
return result;
}
@@ -291,7 +304,19 @@ export class AdminService {
const data = await apiClient.get<ProviderModels>(
`/admin/api/upstream-providers/${providerId}/models`
);
return data;
// Convert pricing for all models in the list so the UI receives "per 1M tokens" values
return {
...data,
db_models: data.db_models.map((m) => ({
...m,
pricing: this.convertPricingToPerMillionTokens(m.pricing),
})),
remote_models: data.remote_models.map((m) => ({
...m,
pricing: this.convertPricingToPerMillionTokens(m.pricing),
})),
};
}
static async createProviderModel(
@@ -498,8 +523,8 @@ export class AdminService {
: null;
const pricing = {
prompt: (data.input_cost as number) / 1000000,
completion: (data.output_cost as number) / 1000000,
prompt: data.input_cost as number,
completion: data.output_cost as number,
request: (data.min_cost_per_request as number) || 0,
image: 0,
web_search: 0,
@@ -553,8 +578,8 @@ export class AdminService {
const existingModel = await this.getModel(modelId, providerId);
const pricing = {
prompt: (data.input_cost as number) / 1000000,
completion: (data.output_cost as number) / 1000000,
prompt: data.input_cost as number,
completion: data.output_cost as number,
request: (data.min_cost_per_request as number) || 0,
image: 0,
web_search: 0,