Files
FastGPT/packages/global/core/ai/pricing.ts
T
Archer 3f4400a500 V4.14.10 dev (#6674)
* feat: model config with brand-new price calculate machanism (#6616)

* fix: image read and json error (Agent) (#6502)

* fix:
1.image read
2.JSON parsing error

* dataset cite and pause

* perf: plancall second parse

* add test

---------

Co-authored-by: archer <545436317@qq.com>

* master message

* remove invalid code

* wip: model config

* feat: model config with brand-new price calculate machanism

* merge main branch

* ajust calculate way

* ajust priceTiers resolve procession

* perf: price config code

* fix: default price

* fix: test

* fix: comment

* fix test

---------

Co-authored-by: YeYuheng <57035043+YYH211@users.noreply.github.com>
Co-authored-by: archer <545436317@qq.com>

* wip: fix modal UI (#6634)

* wip: fix modal UI

* fix: maxInputToken set

* chore: add price unit for non llm models

* chore: replace question mark icon with beta tag (#6672)

* feat:rerank too long; fix:rerank ui(agent),embedding returns 0 (#6663)

* feat:rerank too long; fix:rerank ui(agent),embedding returns 0

* rerank

* fix:rerank function

* perf: rerank code

* fix rerank

* perf: model price ui

---------

Co-authored-by: archer <545436317@qq.com>

* remove llmtype field

* revert model init

* fix: filed

* fix: model select filter

* perf: multiple selector render

* remove invalid checker

* remove invalid i18n

* perf: model selector tip

* perf: model selector tip

* fix cr

* limit pnpm version

* fix: i18n

* fix action

* set default mintoken

* update i18n

* perf: usage push

* fix:rerank model ui (#6677)

* fix: tier match error

* fix: testr

---------

Co-authored-by: Ryo <whoeverimf5@gmail.com>
Co-authored-by: YeYuheng <57035043+YYH211@users.noreply.github.com>
2026-03-30 10:05:42 +08:00

154 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { ModelPriceTierType, PriceType } from './model.schema';
const isValidNumber = (value: unknown): value is number => {
return typeof value === 'number' && Number.isFinite(value);
};
const getSafePrice = (value: unknown) => (isValidNumber(value) ? value : 0);
/*
格式化 tiers:跳过降序梯度、支持末尾开放梯度
1. 只有一个梯度,不管有没有价格,都推送进去
2. 多个梯度,遇到没有 maxToken 就认为是最后的梯度。
2.1 如果有价格,则推送,认为是无限大梯度
2.2 如果没有价格,认为是空行,跳过
*/
export const sanitizeModelPriceTiers = (tiers?: ModelPriceTierType[]): ModelPriceTierType[] => {
if (!Array.isArray(tiers)) return [];
const result: ModelPriceTierType[] = [];
for (const tier of tiers) {
if (result.length === 0) {
result.push({
minInputTokens: 0,
maxInputTokens: isValidNumber(tier?.maxInputTokens)
? Math.max(0, tier.maxInputTokens)
: undefined,
inputPrice: getSafePrice(tier?.inputPrice),
outputPrice: getSafePrice(tier?.outputPrice)
});
continue;
}
const hasMaxInputTokens = isValidNumber(tier?.maxInputTokens);
const last = result[result.length - 1];
const minInputTokens = last.maxInputTokens ?? 0;
if (!hasMaxInputTokens) {
// 无上限梯度(开放末端):有价格才算有效
const hasPrice = isValidNumber(tier?.inputPrice) || isValidNumber(tier?.outputPrice);
if (hasPrice) {
result.push({
minInputTokens,
inputPrice: getSafePrice(tier?.inputPrice),
outputPrice: getSafePrice(tier?.outputPrice)
});
}
break;
}
const maxInputTokens = Math.max(0, tier.maxInputTokens!);
// 跳过降序梯度(maxInputTokens 必须严格递增)
if (last?.maxInputTokens != null && maxInputTokens <= last.maxInputTokens) {
continue;
}
result.push({
minInputTokens,
maxInputTokens,
inputPrice: getSafePrice(tier?.inputPrice),
outputPrice: getSafePrice(tier?.outputPrice)
});
}
return result;
};
// 计算模型价格梯度
export const getRuntimeResolvedPriceTiers = (config?: PriceType): ModelPriceTierType[] => {
// 格式化梯度
if (Array.isArray(config?.priceTiers)) {
return sanitizeModelPriceTiers(config.priceTiers);
}
// 旧版的价格计费字段
const hasLegacyIOPrice = isValidNumber(config?.inputPrice) && config.inputPrice > 0;
if (hasLegacyIOPrice) {
return [
{
minInputTokens: 0,
inputPrice: getSafePrice(config?.inputPrice),
outputPrice: getSafePrice(config?.outputPrice)
}
];
}
if (isValidNumber(config?.charsPointsPrice) || config?.charsPointsPrice === undefined) {
const comprehensivePrice = getSafePrice(config?.charsPointsPrice);
return [
{
minInputTokens: 0,
inputPrice: comprehensivePrice,
outputPrice: comprehensivePrice
}
];
}
return [];
};
export const calculateModelPrice = ({
config,
inputTokens = 0,
outputTokens = 0,
multiple = 1000
}: {
config?: PriceType;
inputTokens?: number;
outputTokens?: number;
multiple?: number;
}) => {
const tiers = getRuntimeResolvedPriceTiers(config);
// 匹配梯度区间,左开右闭 (prevMax, maxInputTokens]
// 第一个梯度特殊处理为左闭右闭 [0, maxInputTokens]
const getMatchingResolvedTier = (
resolvedTiers: ModelPriceTierType[],
currentInputTokens = 0
): ModelPriceTierType | undefined => {
if (resolvedTiers.length === 0) return undefined;
for (let i = 0; i < resolvedTiers.length; i++) {
const tier = resolvedTiers[i];
const maxInputTokens = tier.maxInputTokens;
// 开放末端梯度(无 maxInputTokens
if (!maxInputTokens) {
return tier;
}
// 检查是否在当前梯度范围内
if (currentInputTokens <= maxInputTokens) {
return tier;
}
}
// 如果都不匹配,返回最后一个梯度
return resolvedTiers[resolvedTiers.length - 1];
};
const matchedTier = getMatchingResolvedTier(tiers, inputTokens / multiple);
const totalPoints =
(matchedTier?.inputPrice ?? 0) * (inputTokens / multiple) +
(matchedTier?.outputPrice ?? 0) * (outputTokens / multiple);
return {
totalPoints,
matchedTier,
tiers
};
};