Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
0ca86060
authored
Sep 18, 2026
by
renyizhao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
模型价格精度调整
parent
0d2f60f7
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
74 additions
and
21 deletions
+74
-21
computility-module-apihub/computility-module-apihub-biz/src/main/java/com/luhu/computility/module/apihub/controller/app/aimodel/AppAiModelController.java
+55
-17
computility-module-apihub/computility-module-apihub-biz/src/main/java/com/luhu/computility/module/apihub/controller/app/aimodel/vo/AppModelPricingVO.java
+19
-4
No files found.
computility-module-apihub/computility-module-apihub-biz/src/main/java/com/luhu/computility/module/apihub/controller/app/aimodel/AppAiModelController.java
View file @
0ca86060
...
@@ -234,9 +234,17 @@ public class AppAiModelController {
...
@@ -234,9 +234,17 @@ public class AppAiModelController {
String
billingExpr
=
(
String
)
pricing
.
get
(
"billing_expr"
);
String
billingExpr
=
(
String
)
pricing
.
get
(
"billing_expr"
);
vo
.
setBillingMode
(
billingMode
);
vo
.
setBillingMode
(
billingMode
);
vo
.
setBillingExpr
(
billingExpr
);
vo
.
setBillingExpr
(
billingExpr
);
// 提前提取 billing_usage_schema,用于 tier 计费字段单位推断(happyhorse 等按秒/次计费模型)
Map
<
String
,
Object
>
billingUsageSchemaForTiers
=
null
;
Object
schemaObjForTiers
=
pricing
.
get
(
"billing_usage_schema"
);
if
(
schemaObjForTiers
instanceof
Map
)
{
@SuppressWarnings
(
"unchecked"
)
Map
<
String
,
Object
>
schemaMap
=
(
Map
<
String
,
Object
>)
schemaObjForTiers
;
billingUsageSchemaForTiers
=
schemaMap
;
}
// 解析分档表达式
// 解析分档表达式
if
(
"tiered_expr"
.
equals
(
billingMode
)
&&
billingExpr
!=
null
)
{
if
(
"tiered_expr"
.
equals
(
billingMode
)
&&
billingExpr
!=
null
)
{
List
<
AppModelPricingVO
.
TierVO
>
tiers
=
parseTieredExpr
(
billingExpr
,
groupRatio
);
List
<
AppModelPricingVO
.
TierVO
>
tiers
=
parseTieredExpr
(
billingExpr
,
groupRatio
,
billingUsageSchemaForTiers
);
vo
.
setTiers
(
tiers
);
vo
.
setTiers
(
tiers
);
// 解析条件乘数
// 解析条件乘数
List
<
AppModelPricingVO
.
ConditionMultiplierVO
>
conditionMultipliers
=
parseConditionMultipliers
(
billingExpr
);
List
<
AppModelPricingVO
.
ConditionMultiplierVO
>
conditionMultipliers
=
parseConditionMultipliers
(
billingExpr
);
...
@@ -253,28 +261,30 @@ public class AppAiModelController {
...
@@ -253,28 +261,30 @@ public class AppAiModelController {
if
(
vo
.
getQuotaType
()
!=
null
&&
vo
.
getQuotaType
()
==
1
)
{
if
(
vo
.
getQuotaType
()
!=
null
&&
vo
.
getQuotaType
()
==
1
)
{
// 按次计费(quota_type=1):使用原始 group_ratio.default,不叠加 puhui 业务系数
// 按次计费(quota_type=1):使用原始 group_ratio.default,不叠加 puhui 业务系数
BigDecimal
perCallPrice
=
modelPrice
.
multiply
(
plainGroupRatio
)
BigDecimal
perCallPrice
=
modelPrice
.
multiply
(
plainGroupRatio
)
.
setScale
(
2
,
RoundingMode
.
HALF_UP
);
.
setScale
(
4
,
RoundingMode
.
HALF_UP
);
vo
.
setInputPrice
(
perCallPrice
);
vo
.
setInputPrice
(
perCallPrice
);
vo
.
setOutputPrice
(
perCallPrice
);
vo
.
setOutputPrice
(
perCallPrice
);
}
else
{
}
else
{
// 按量计费(quota_type=0):应用 groupRatio(含 BUSINESS_RATIO)
// 按量计费(quota_type=0):应用 groupRatio(含 BUSINESS_RATIO)。
BigDecimal
inputPrice
=
modelPrice
.
add
(
modelRatio
).
multiply
(
groupRatio
).
setScale
(
2
,
RoundingMode
.
HALF_UP
);
// 精度保留 4 位小数,避免 modelRatio × groupRatio(如 0.1435 × 2 = 0.287)
// 被四舍五入到 2 位后丢失精度;前端 formatPrice 用 toFixed(4) 去末尾 0 显示。
BigDecimal
inputPrice
=
modelPrice
.
add
(
modelRatio
).
multiply
(
groupRatio
).
setScale
(
4
,
RoundingMode
.
HALF_UP
);
vo
.
setInputPrice
(
inputPrice
);
vo
.
setInputPrice
(
inputPrice
);
vo
.
setOutputPrice
(
modelPrice
.
add
(
modelRatio
.
multiply
(
completionRatio
)).
multiply
(
groupRatio
).
setScale
(
2
,
RoundingMode
.
HALF_UP
));
vo
.
setOutputPrice
(
modelPrice
.
add
(
modelRatio
.
multiply
(
completionRatio
)).
multiply
(
groupRatio
).
setScale
(
4
,
RoundingMode
.
HALF_UP
));
// 缓存价格 = cache_ratio × 输入价格(输入价格已包含 groupRatio)
// 缓存价格 = cache_ratio × 输入价格(输入价格已包含 groupRatio)
vo
.
setCacheReadPrice
(
cacheRatio
.
multiply
(
inputPrice
).
setScale
(
2
,
RoundingMode
.
HALF_UP
));
vo
.
setCacheReadPrice
(
cacheRatio
.
multiply
(
inputPrice
).
setScale
(
4
,
RoundingMode
.
HALF_UP
));
vo
.
setCacheCreatePrice
(
createCacheRatio
.
multiply
(
inputPrice
).
setScale
(
2
,
RoundingMode
.
HALF_UP
));
vo
.
setCacheCreatePrice
(
createCacheRatio
.
multiply
(
inputPrice
).
setScale
(
4
,
RoundingMode
.
HALF_UP
));
// 图片输入价格 = image_ratio × 输入价格
// 图片输入价格 = image_ratio × 输入价格
if
(
imageRatio
!=
null
&&
imageRatio
.
compareTo
(
BigDecimal
.
ZERO
)
>
0
)
{
if
(
imageRatio
!=
null
&&
imageRatio
.
compareTo
(
BigDecimal
.
ZERO
)
>
0
)
{
vo
.
setImageInputPrice
(
imageRatio
.
multiply
(
inputPrice
).
setScale
(
2
,
RoundingMode
.
HALF_UP
));
vo
.
setImageInputPrice
(
imageRatio
.
multiply
(
inputPrice
).
setScale
(
4
,
RoundingMode
.
HALF_UP
));
}
}
// 音频输入价格 = audio_ratio × 输入价格
// 音频输入价格 = audio_ratio × 输入价格
if
(
audioRatio
!=
null
&&
audioRatio
.
compareTo
(
BigDecimal
.
ZERO
)
>
0
)
{
if
(
audioRatio
!=
null
&&
audioRatio
.
compareTo
(
BigDecimal
.
ZERO
)
>
0
)
{
vo
.
setAudioInputPrice
(
audioRatio
.
multiply
(
inputPrice
).
setScale
(
2
,
RoundingMode
.
HALF_UP
));
vo
.
setAudioInputPrice
(
audioRatio
.
multiply
(
inputPrice
).
setScale
(
4
,
RoundingMode
.
HALF_UP
));
}
}
// 音频补全价格 = audio_completion_ratio × 音频输入价格
// 音频补全价格 = audio_completion_ratio × 音频输入价格
if
(
audioCompletionRatio
!=
null
&&
audioCompletionRatio
.
compareTo
(
BigDecimal
.
ZERO
)
>
0
&&
vo
.
getAudioInputPrice
()
!=
null
)
{
if
(
audioCompletionRatio
!=
null
&&
audioCompletionRatio
.
compareTo
(
BigDecimal
.
ZERO
)
>
0
&&
vo
.
getAudioInputPrice
()
!=
null
)
{
vo
.
setAudioCompletionPrice
(
audioCompletionRatio
.
multiply
(
vo
.
getAudioInputPrice
()).
setScale
(
2
,
RoundingMode
.
HALF_UP
));
vo
.
setAudioCompletionPrice
(
audioCompletionRatio
.
multiply
(
vo
.
getAudioInputPrice
()).
setScale
(
4
,
RoundingMode
.
HALF_UP
));
}
}
}
}
}
}
...
@@ -551,7 +561,8 @@ public class AppAiModelController {
...
@@ -551,7 +561,8 @@ public class AppAiModelController {
* 2. 后续档位 conditionDesc 包含前一档 params 的残骸
* 2. 后续档位 conditionDesc 包含前一档 params 的残骸
* 3. 最后一档(无 ? 的兜底档)被错误地给 null
* 3. 最后一档(无 ? 的兜底档)被错误地给 null
*/
*/
private
List
<
AppModelPricingVO
.
TierVO
>
parseTieredExpr
(
String
billingExpr
,
BigDecimal
groupRatio
)
{
private
List
<
AppModelPricingVO
.
TierVO
>
parseTieredExpr
(
String
billingExpr
,
BigDecimal
groupRatio
,
Map
<
String
,
Object
>
billingUsageSchema
)
{
List
<
AppModelPricingVO
.
TierVO
>
tiers
=
new
java
.
util
.
ArrayList
<>();
List
<
AppModelPricingVO
.
TierVO
>
tiers
=
new
java
.
util
.
ArrayList
<>();
if
(
billingExpr
==
null
||
billingExpr
.
isEmpty
())
{
if
(
billingExpr
==
null
||
billingExpr
.
isEmpty
())
{
return
tiers
;
return
tiers
;
...
@@ -595,7 +606,7 @@ public class AppAiModelController {
...
@@ -595,7 +606,7 @@ public class AppAiModelController {
tier
.
setName
(
info
.
label
);
tier
.
setName
(
info
.
label
);
// 解析参数部分,提取各倍率
// 解析参数部分,提取各倍率
parseTierParams
(
tier
,
info
.
params
);
parseTierParams
(
tier
,
info
.
params
,
billingUsageSchema
);
// 提取条件:在 (上一个 tier 结束 + 1) 到当前 tier 开始的段落里找顶层 ?
// 提取条件:在 (上一个 tier 结束 + 1) 到当前 tier 开始的段落里找顶层 ?
int
segStart
=
(
i
==
0
)
?
0
:
tierInfos
.
get
(
i
-
1
).
end
+
1
;
int
segStart
=
(
i
==
0
)
?
0
:
tierInfos
.
get
(
i
-
1
).
end
+
1
;
...
@@ -717,21 +728,29 @@ public class AppAiModelController {
...
@@ -717,21 +728,29 @@ public class AppAiModelController {
/**
/**
* 解析 tier 参数部分,提取各扩展价格倍率
* 解析 tier 参数部分,提取各扩展价格倍率
* 格式: p * x + c * y + cr * a + cc * b + cc1h * c1 + img * d + img_o * e + ai * f + ao * g
* 格式: p * x + c * y + cr * a + cc * b + cc1h * c1 + img * d + img_o * e + ai * f + ao * g
* 或 u("field") * X [/ 1000000] (seedance 风格按 token 计费,X 即每百万 token 价格)
* 或 u("field") * X [/ 1000000] (动态计费:tokens 为每百万 token,其余字段按 schema.unit)
*
* @param billingUsageSchema 计费用法 schema(用于推断 u("field") 的字段单位)
*/
*/
private
void
parseTierParams
(
AppModelPricingVO
.
TierVO
tier
,
String
params
)
{
private
void
parseTierParams
(
AppModelPricingVO
.
TierVO
tier
,
String
params
,
Map
<
String
,
Object
>
billingUsageSchema
)
{
if
(
params
==
null
||
params
.
isEmpty
())
{
if
(
params
==
null
||
params
.
isEmpty
())
{
return
;
return
;
}
}
// 提取 u("field") * X [/ 1000000](seedance 风格按 token 计费)
// 提取 u("field") * X [/ 1000000](动态计费)
// 例如 u("tokens") * 29.14 / 1000000 => tokensMultiplier = 29.14
// 例如 u("tokens") * 29.14 / 1000000 => tokensMultiplier = 29.14(每百万 token)
// u("seconds") * 0.4104 => tokensMultiplier = 0.4104(每 1 秒)
java
.
util
.
regex
.
Pattern
tokensPattern
=
java
.
util
.
regex
.
Pattern
.
compile
(
java
.
util
.
regex
.
Pattern
tokensPattern
=
java
.
util
.
regex
.
Pattern
.
compile
(
"u\\s*\\(\\s*\"(\\w+)\"\\s*\\)\\s*\\*\\s*([\\d.]+)\\s*(?:/\\s*1000000)?"
"u\\s*\\(\\s*\"(\\w+)\"\\s*\\)\\s*\\*\\s*([\\d.]+)\\s*(?:/\\s*1000000)?"
);
);
java
.
util
.
regex
.
Matcher
tokensMatcher
=
tokensPattern
.
matcher
(
params
);
java
.
util
.
regex
.
Matcher
tokensMatcher
=
tokensPattern
.
matcher
(
params
);
if
(
tokensMatcher
.
find
())
{
if
(
tokensMatcher
.
find
())
{
tier
.
setTokensMultiplier
(
new
BigDecimal
(
tokensMatcher
.
group
(
2
)).
setScale
(
4
,
RoundingMode
.
HALF_UP
));
String
field
=
tokensMatcher
.
group
(
1
);
BigDecimal
multiplier
=
new
BigDecimal
(
tokensMatcher
.
group
(
2
)).
setScale
(
4
,
RoundingMode
.
HALF_UP
);
tier
.
setTokensMultiplier
(
multiplier
);
tier
.
setUsageField
(
field
);
tier
.
setUsageUnit
(
resolveUsageUnit
(
billingUsageSchema
,
field
));
}
}
// 提取 p * x
// 提取 p * x
...
@@ -799,6 +818,25 @@ public class AppAiModelController {
...
@@ -799,6 +818,25 @@ public class AppAiModelController {
}
}
/**
/**
* 通过 billing_usage_schema 推断 u("field") 引用字段的单位。
* 例如 field=seconds → unit=second,field=tokens → unit=token,field=input_images → unit=count。
* 推断失败时返回 null,由前端兜底为 "token"。
*/
private
String
resolveUsageUnit
(
Map
<
String
,
Object
>
billingUsageSchema
,
String
field
)
{
if
(
billingUsageSchema
==
null
||
field
==
null
||
field
.
isEmpty
())
{
return
null
;
}
Object
fieldDef
=
billingUsageSchema
.
get
(
field
);
if
(!(
fieldDef
instanceof
Map
))
{
return
null
;
}
@SuppressWarnings
(
"unchecked"
)
Map
<
String
,
Object
>
fieldMap
=
(
Map
<
String
,
Object
>)
fieldDef
;
Object
unit
=
fieldMap
.
get
(
"unit"
);
return
unit
==
null
?
null
:
unit
.
toString
();
}
/**
* 简化条件描述
* 简化条件描述
*/
*/
private
String
simplifyCondition
(
String
condition
)
{
private
String
simplifyCondition
(
String
condition
)
{
...
...
computility-module-apihub/computility-module-apihub-biz/src/main/java/com/luhu/computility/module/apihub/controller/app/aimodel/vo/AppModelPricingVO.java
View file @
0ca86060
...
@@ -152,12 +152,27 @@ public class AppModelPricingVO {
...
@@ -152,12 +152,27 @@ public class AppModelPricingVO {
@Schema
(
description
=
"音频输出倍率"
)
@Schema
(
description
=
"音频输出倍率"
)
private
BigDecimal
audioOutputRatio
;
private
BigDecimal
audioOutputRatio
;
/**
/**
* 按 token 计费的倍率(seedance 风格 u("tokens") * X / 1000000 中的 X)。
* 按 usage 计费的倍率(seedance / happyhorse 风格 u("field") * X 中的 X)。
* 适用于 openai-video 等按 token 数动态计费的档位,
* 适用于 openai-video 等按动态字段(token/second/count)计费的档位。
* 单位 = 元/百万 token。前端展示时优先于 inputRatio/outputRatio。
* 单位 = 元 / 每 usageUnit 单位(tokens 时为每百万 token,需配合 schema 推断)。
* 前端展示时优先于 inputRatio/outputRatio。
*/
*/
@Schema
(
description
=
"按
token 计费的倍率(seedance 风格,每百万 token 价格
)"
)
@Schema
(
description
=
"按
usage 计费的倍率(u(\"field\") * X 中的 X
)"
)
private
BigDecimal
tokensMultiplier
;
private
BigDecimal
tokensMultiplier
;
/**
* tokensMultiplier 对应的计费字段名(如 tokens/seconds)。
* 仅当 tokensMultiplier 非空时有意义,用于前端明确当前档位在计什么。
*/
@Schema
(
description
=
"tokensMultiplier 对应的计费字段名(如 tokens/seconds/input_images)"
)
private
String
usageField
;
/**
* usageField 在 billing_usage_schema 中的 unit(如 token/second/count)。
* 仅当 tokensMultiplier 非空时有意义,用于前端选择展示单位文案(token/秒/次)。
*/
@Schema
(
description
=
"usageField 在 schema 中的 unit(token/second/count),用于前端选择展示单位文案"
)
private
String
usageUnit
;
}
}
/**
/**
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment