pricing/README.md
2026-05-14 11:52:58 +08:00

373 lines
13 KiB
Markdown
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.

# pricing 模块开发文档
## 模块概述
pricing 是 Hermes Agent 平台的**通用定价引擎模块**,提供基于 YAML 规则表的灵活定价计算能力。支持多维度定价条件匹配between、in、=、>、<、>=、<= 等)、公式计算、数据映射、分时段定价和批量导入导出。
在 Sage 系统中的作用:为 llmage大模型管理提供计费定价服务。每个 LLM 模型通过 `llm.ppid` 字段关联到 pricing 模块的定价项目pricing_program调用结束后由 llmage 的 `llm_charging()` 通过 `buffered_charging(ppid, usages)` 计算费用。
核心职责:
- 定价项目管理创建定价项目定义定价规格pricing_spec
- 定价项目时序支持按时间段定义不同定价策略enabled_date / expired_date
- 定价数据管理:通过 Excel 批量导入/导出定价表,自动生成下拉菜单
- 定价计算引擎:根据输入数据匹配定价规则,通过公式计算费用
- 定价测试:提供测试界面验证定价规则是否正确
---
## 目录结构
```
pricing/
├── pyproject.toml # 构建配置
├── setup.cfg # 包元数据name=pricing
├── README.md
├── pricing/ # Python 源码包
│ ├── __init__.py # 空
│ ├── init.py # 模块初始化,注册函数到 ServerEnv
│ ├── pricing.py # 定价引擎核心PricingProgram 类 + 匹配逻辑
│ └── write_pattern.py # Excel 定价模版生成与解析
├── json/ # CRUD 定义bricks-framework
│ ├── pricing_program.json # 定价项目管理
│ └── pricing_program_timing.json # 定价项目时序
├── models/ # 表定义xlsx 格式)
│ ├── pricing_program.xlsx
│ └── pricing_program_timing.xlsx
├── wwwroot/ # Web 前端资源
│ ├── menu.ui # 菜单
│ ├── test_pricing_program.ui # 定价测试窗口
│ ├── test_pricing_program.dspy # 定价测试逻辑
│ ├── pricing_test.ui # 定价验证窗口
│ ├── get_all_pricing_programs.dspy # 获取所有定价项目
│ ├── pi_get_all_specs.dspy # 获取所有规格
│ ├── upload_pricing_data.dspy # 上传定价数据
│ ├── download_pricing_data.dspy # 下载定价数据
│ ├── download_pricing_pattern.dspy # 下载定价模版
│ ├── load_pricing_data.ui # 加载定价数据界面
│ ├── get_platform_providers.dspy # 获取平台供应商
│ ├── imgs/ # 图标
│ └── pricing_item/ # 定价项管理
│ ├── index.ui
│ ├── add_pricing_item.dspy
│ ├── update_pricing_item.dspy
│ ├── delete_pricing_item.dspy
│ ├── get_pricing_item.dspy
│ └── get_spec_fields_by_psid.dspy
└── script/
└── perms.json # RBAC 权限配置
```
---
## 数据库表结构
### 表关系
```
pricing_program (定价项目) ──1:N──> pricing_program_timing (定价项目时序)
pricing_program_timing ──1:N──> pricing_item (定价项)
```
### 核心表说明
| 表名 | 说明 | 关键字段 |
|------|------|----------|
| **pricing_program** | 定价项目定义 | id, name, ownerid, providerid, pricing_spec(YAML格式), pricing_belong, discount |
| **pricing_program_timing** | 定价项目时序(分时段定价) | id, ppid, name, enabled_date, expired_date, pricing_data(YAML格式) |
| **pricing_item** | 定价项 | id, pptid, name, formula, spec_values |
### pricing_spec定价规格定义格式
`pricing_program.pricing_spec` 字段是 YAML 格式,定义定价的维度和字段:
```yaml
model:
type: str
label: "模型"
options:
- "viduq3-pro"
- "viduq3-turbo"
resolution:
type: str
label: "分辨率"
options:
- "1024p"
- "720p"
- "540p"
duration:
type: int
label: "时长"
off_peak:
type: int
label: "错峰执行"
options:
- 0 # 正常时段
- 1 # 错峰
price:
type: float
label: 单价
```
**字段属性说明:**
- `type`: 字段类型str、int、float、bool、factor
- `label`: 显示名称
- `options`: 可选值列表(用于前端下拉菜单和定价匹配)
- `value_mode`: 匹配模式(可选,默认 `=`
- `=` 或省略:精确匹配
- `between`: 区间匹配(格式:`小值 ~ 大值`,可加 `=` 表示包含端点)
- `in`: 枚举匹配(格式:`值1 值2 ...`
- `>``>=``<``<=`: 比较运算
**必须有 `price` 字段**,其 `type` 必须为 `float`
### pricing_data定价数据格式
`pricing_program_timing.pricing_data` 是 YAML 格式,包含 `fields``pricings` 两部分:
```yaml
fields:
model:
type: str
label: "模型"
resolution:
type: str
label: "分辨率"
formula:
type: str
label: "计算公式"
price:
type: float
label: "单价"
pricings:
- model: viduq3-pro
resolution: 1024p
price: 0.5
formula: price * 1
- model: viduq3-pro
resolution: 720p
price: 0.3
formula: price * 1
- model: viduq3-turbo
resolution: 1024p
price: 0.2
formula: price * 1
```
### 定价匹配规则
调用 `get_pricing_from_ymalstr(config_data, yamlstr)` 时:
1. 遍历 `pricings` 中的每条定价规则
2. 对每条规则的每个字段,用 `check_value()` 判断输入数据是否匹配:
- `value_mode == '='`: 精确匹配 `data_value == spec_value`
- `value_mode == 'between'`: 区间匹配(如 `0 ~ 10000` 表示 0 <= value < 10000
- `value_mode == 'in'`: 枚举匹配 `gpt-4 gpt-3.5` 表示其中之一
- `value_mode == '>'/'<'>='/<='`: 比较运算
3. **所有字段都匹配**的定价规则才会被选中
4. 执行 `formula` 字段中定义的计算公式得到 `amount`
5. 返回所有匹配的定价项列表
**数据映射mappings**
```yaml
model_mappings: # 模型名称映射
"doubao-seed-2-0-pro-260215": "doubao-seed-2-0-pro"
"doubao-seed-2-0-lite-260215": "doubao-seed-2-0-lite"
```
输入数据中的值会先通过 mappings 转换再与定价规则匹配
---
## Python API
### 模块初始化
```python
def load_pricing():
env = ServerEnv()
env.get_pricing_program = get_pricing_program
env.write_pricing_patten = PricingProgram.write_pricing_patten
env.pricing_program_charging = PricingProgram.charging
env.buffered_charging = PricingProgram.buffered_charging
env.load_pricing_data = PricingProgram.load_pricing_data
env.get_pricing_program = PricingProgram.get_pricing_program
env.test_pricing = test_pricing
```
其他模块的 `.dspy` 文件可通过 `globals()` 直接使用这些函数
### PricingProgram 类pricing.py
#### 定价计算
```python
# 带缓存的计费推荐llmage 使用此方法)
pp = PricingProgram()
prices = await pp.buffered_charging(ppid, data)
# data: {'prompt_tokens': 52, 'completion_tokens': 1416, 'model': 'xxx', ...}
# 返回: [DictObject(amount=0.001, cost=0.001*discount, data={...}, formula=...)]
# 不带缓存的计费(传入 sor
prices = await PricingProgram.charging(sor, ppid, data)
```
#### 定价数据管理
```python
# 获取定价项目(带缓存,按日期)
d = await PricingProgram.get_ppid_pricing(ppid)
# 返回: DictObject(name, ownerid, providerid, pricing_belong, discount, pricing_data)
# 获取定价项目定义
pp = await PricingProgram.get_pricing_program(ppid)
# 从 Excel 文件加载定价数据
await PricingProgram.load_pricing_data(pptid, webpath_xlsx)
# 生成定价 Excel 模版
fpath = await PricingProgram.write_pricing_patten(request, ppid)
```
#### YAML/DB 转换
```python
# DB 到应用YAML 字符串 → dict
PricingProgram.pp_db2app(pp) # pricing_program
PricingProgram.ppt_db2app(ppt) # pricing_program_timing
# 应用到 DBdict → YAML 字符串)
PricingProgram.pp_app2db(pp)
PricingProgram.ppt_app2db(ppt)
```
### 独立函数
```python
# 测试定价(给定 pptid 和输入数据,返回总金额)
amount = await test_pricing(pptid, data)
# 获取定价项目定义
pp = await get_pricing_program(ppid)
```
### 核心匹配引擎
```python
# 静态方法,直接在内存 YAML 上匹配
prices = PricingProgram.get_pricing_from_ymalstr(config_data, yamlstr)
# config_data: 输入数据字典
# yamlstr: pricing_data 的 YAML 字符串
# 返回: 匹配的定价项列表,每项包含 amount通过 formula 计算)
```
---
## 前端页面
### pricing_program.json — 定价项目管理
- `ownerid`登录用户所在组织过滤
- `pricing_spec` 字段不在浏览器中显示YAML 格式复杂
- 子表`pricing_program_timing`定价项目时序
- 工具栏`测试` 按钮弹出测试窗口
### pricing_program_timing.json — 定价项目时序
- `ownerid` 过滤
- 子表`pricing_item`定价细项
- 工具栏
- `定价模版`下载 Excel 模版
- `上传定价数据`上传 Excel 定价文件
- `下载定价数据`下载当前定价数据
- `验证定价`测试定价规则
---
## Excel 模版与数据导入
### 生成模版
通过 `write_pricing_patten(request, ppid)` 生成 Excel 模版
- 根据 `pricing_spec` 中的字段生成列头
- `options` 字段自动生成 Excel 下拉菜单DataValidation
- `bool` 类型生成 TRUE/FALSE 下拉
- `factor` 类型字段不参与定价仅作为因子
### 导入定价数据
通过 `load_pricing_data(pptid, webpath_xlsx)` Excel 导入
1. 解析 Excel 第一行作为字段名
2. label 映射回字段名Excel 列头是 label需映射回 spec 中的 key
3. 转换为 YAML 格式存入 `pricing_program_timing.pricing_data`
---
## 在 Sage 系统中的协同关系
```
llmage (模型管理) ──→ llm.ppid ──→ pricing_program.id
├── pricing_spec (定价维度定义)
└── pricing_program_timing ──→ pricing_data (定价表)
└── pricings[] ──→ formula 计算金额
```
**调用流程**
1. llmage 调用外部 LLM API获取 token 用量prompt_tokens, completion_tokens
2. 记录到 `llmusage` accounting_status='created'
3. 后台记账任务 `backend_accounting()` 10 秒轮询
4. 调用 `llm_charging(ppid, llmusage)` `buffered_charging(ppid, usages)`
5. pricing 模块匹配定价规则计算 `amount` `cost`
6. 更新 `llmusage.amount` `llmusage.cost`
7. 调用 `llm_accounting()` 记账到 accounting 模块
---
## 关键设计要点
1. **YAML 驱动定价规则**所有定价逻辑用 YAML 描述支持字段定义选项匹配模式计算公式
2. **多维度条件匹配**支持 betweenin、=、><、>=、<= 等多种匹配模式
3. **公式计算**:每条定价规则可定义独立的计算公式(如 `(3.2 * prompt_tokens + 16 * completion_tokens) / 1000000`
4. **数据映射mappings**:输入值可预先映射(如 `doubao-seed-2-0-pro-260215``doubao-seed-2-0-pro`
5. **分时段定价**`pricing_program_timing` 支持按 enabled_date/expired_date 定义不同时期的定价
6. **折扣支持**`pricing_program.discount` 字段控制折扣,最终 cost = amount * discount
7. **Excel 批量操作**:通过 Excel 导入导出定价数据,自动生成下拉菜单,降低维护门槛
8. **内存缓存**`PricingProgram.pricing_data``{ppid}.{date}` 键缓存,避免重复查询
9. **定价项pricing_item**:可单独管理定价细项,用于更复杂的定价结构
---
## 依赖关系
```
pricing
├── sqlor # 数据库 ORM
├── apppublic # 工具库日志、唯一ID、时间工具等
├── ahserver # Web 服务器框架
├── openpyxl # Excel 文件读写(依赖安装)
└── pyyaml # YAML 解析(依赖安装)
```
---
## 开发注意事项
1. **pricing_spec 必须包含 price 字段**:且 type 为 float否则定价匹配会失败
2. **formula 字段**:每条定价规则的 formula 是 Python 表达式,通过 `eval()` 执行,可引用 config_data 中的任意字段
3. **value_mode 默认值为 `=`**:不指定时做精确匹配
4. **between 区间格式**`0 ~ 100` 表示 `0 <= value < 100``0 =~ 100` 表示 `0 <= value <= 100`
5. **factor 类型字段**:在 Excel 模版中被跳过(不生成列),仅作为计算因子使用
6. **pricing_data 结构**:必须是 `{fields: {...}, pricings: [...]}` 格式,缺少任一部分都会报错
7. **缓存过期策略**:缓存按日期键管理,每个 ppid 只保留最近 2 天的缓存
8. **discount 应用**discount 在 `buffered_charging()``charging()` 中乘以 amount 得到 cost
9. **Excel 列头是 label**:导入时通过 label 映射回 spec 中的 key 字段名label 必须完全匹配
10. **多匹配规则**:如果多条定价规则都匹配输入数据,**全部返回**(不是只返回第一条),调用方需自行处理