Skip to content

feat(ai): 完成 token 统计与预算计费闭环#1078

Closed
itkdm wants to merge 700 commits intoYunaiV:master-jdk17from
itkdm:feat/ai-billing
Closed

feat(ai): 完成 token 统计与预算计费闭环#1078
itkdm wants to merge 700 commits intoYunaiV:master-jdk17from
itkdm:feat/ai-billing

Conversation

@itkdm
Copy link
Copy Markdown
Contributor

@itkdm itkdm commented Feb 26, 2026

背景

本 PR 完成 AI 模块 token 统计与预算计费主链路建设,目标是保证“可计量、可限额、可追溯、可告警”。

变更范围

  • 模块:yudao-module-ai
  • 主要涉及:Chat / Write / MindMap 的调用日志、预算预扣与结算、预算周期、阈值告警、计费策略、SSE 异常处理
  • 分支:feat/ai-billing

主要改动

  • 新增并完善 AI token 统计与计费相关表结构
  • 完成调用日志能力:
  • Chat 非流式与流式日志埋点
  • Write/MindMap 流式日志埋点
  • 调用日志统计接口与 Excel 导出
  • 完成预算能力:
  • 预算配置 CRUD
  • 预算使用情况查询
  • Redis Lua 原子预扣(用户+租户多维)
  • 预算事件日志、阈值告警、超限拦截记录
  • 预算用量 SQL 原子累加(修复热点并发场景)
  • 计费策略重构:
  • 引入策略模式(默认策略 + 管理器)
  • 支持按模型计费配置快照计算费用
  • 流式稳定性与错误处理增强:
  • SSE 入口对业务异常返回统一事件
  • 取消/异常路径保证预算释放或结算闭环
  • 预算结算安全修复:
  • 修复结算正向补扣穿透限额风险(补扣封顶)
  • 强化 settle 兜底,避免异常时错误反向冲销
  • 预扣估算改进:
  • Chat 侧改为基于真实 Prompt 估算(覆盖附件内容)
  • 对“缺失 usage 时回落预估”增加明确注释说明

兼容性与影响

  • 主要为 AI 模块内部计费/预算链路增强,对外接口语义保持兼容
  • 数据库需要应用本分支新增/调整的 AI 计费相关 SQL 变更

测试与验证

  • yudao-module-ai 全量测试通过:
  • Tests run: 195
  • Failures: 0
  • Errors: 0
  • Skipped: 86
  • 针对关键修复补充了单测

备注

  • 成功请求通常依赖厂商返回 usage 进行精确计费;极少数缺失 usage 的场景按预估费用(预估较高)回落处理(代码中已明确注释)。

YunaiV and others added 30 commits April 30, 2025 00:11
# Conflicts:
#	yudao-dependencies/pom.xml
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareCreateReqVO.java
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareUpdateReqVO.java
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/record/IotOtaUpgradeRecordPageReqVO.java
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/task/IotOtaUpgradeTaskPageReqVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java
#	yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java
#	yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImplTest.java
#	yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java
Signed-off-by: dhb52 <dhb52@126.com>
…vue-pro

# Conflicts:
#	yudao-dependencies/pom.xml
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/captcha/CaptchaController.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
#	yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java
…vue-pro

# Conflicts:
#	yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java
#	yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java
#	yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/template/AppCouponTemplateRespVO.java
#	yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImplTest.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java
#	yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java
…vue-pro

# Conflicts:
#	README.md
#	pom.xml
#	yudao-dependencies/pom.xml
Merge pull request !1333 from dhb52/N/A
…vue-pro

# Conflicts:
#	yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java
…vue-pro

# Conflicts:
#	yudao-dependencies/pom.xml
#	yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java
#	yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java
…vue-pro

# Conflicts:
#	yudao-dependencies/pom.xml
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawBaseVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageWithdrawController.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
#	yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java
#	yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java
#	yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoTransferController.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/transfer/PayDemoTransferCreateReqVO.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferCreateReqVO.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferService.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java
#	yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java
itkdm added 26 commits February 26, 2026 18:17
… price_in_per_1m 等列名 - 流式回调 executeIgnore 改为 execute(tenantId) 修复 tenant_id 丢失 - OpenAiChatOptions 加 streamUsage(true) 修复豆包等模型流式token为0 - 同步修复 H2 测试 DDL 列名
…接口 + DefaultPricingStrategy + AiPricingStrategyManager - AiModelPricingDO新增strategy_type/strategy_config字段 - 修复AiBudgetChecker/AiBudgetUsageController写死MONTHLY导致DAILY配置不生效 - 修复流式接口预算超限时HttpMediaTypeNotAcceptableException - AiCostCalculator标记@deprecated,指向DefaultPricingStrategy  - Redis key格式yyyyMM改为yyyyMMdd,支持DAILY周期  - 新增DefaultPricingStrategyTest + AiPricingStrategyManagerTest
…加方法 - AiBudgetChecker 适配原子累加逻辑 - AiBudgetUsageServiceImpl settle 改为原子累加
…lTest / AiBudgetUsageServiceImplTest 补充原子累加场景 - 新增 AiModelCallLogServiceImplTest / AiBudgetLogServiceImplTest / AiModelPricingServiceImplTest - create_tables.sql 同步更新测试 DDL
…插件,手动加 AND tenant_id 条件 - addUsage 通过 TenantContextHolder.getRequiredTenantId() 传入 tenantId - 单元测试 @beforeeach 设置 TenantContextHolder 匹配 H2 默认值
…ct 移到 insert 之前 - 预算超限抛异常时不会产生未执行的空消息/空任务记录
…务记录 insert 之前,超限时不留脏数据 - createCallLog 中 settle 移到 finally 块,日志写入失败也能结算预扣费 - settle 自身异常时 fallback 到 release,避免 Redis 预扣金额永久占用
- createBudgetConfig/updateBudgetConfig 新增唯一性校验(同租户同用户同周期类型)
- H2 测试 DDL 同步加唯一索引
- 新增 BUDGET_CONFIG_DUPLICATE 错误码
- 原逻辑 MONTHLY 存在但禁用时直接返回 null,跳过 DAILY 检查
- 改为逐个检查 MONTHLY/DAILY 的启用状态,优先返回启用的配置
@itkdm itkdm changed the base branch from master to master-jdk17 February 26, 2026 11:57
@itkdm itkdm marked this pull request as draft February 26, 2026 14:02
@itkdm
Copy link
Copy Markdown
Contributor Author

itkdm commented Feb 26, 2026

已基于 master-jdk17 重新开 PR:#1079
原 PR 因 base 切换导致 diff 过大(显示 700 commits),为方便 review 此 PR 保持 draft 并将关闭。

@itkdm itkdm closed this Feb 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.