diff --git a/README.md b/README.md index 4199b6b..3ec6cef 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,282 @@ # product_management +产品管理模块 —— 统一管理产品目录、类别树、资源绑定、订阅、消费引擎及资源模块对接。 + +## 模块定位 + +product_management 是产品生命周期的中枢,负责: +1. 管理产品类别树和产品注册表 +2. 从资源模块(如 llmage)导入类别和产品 +3. 通过标准接口调度资源模块完成展示、预检、执行、计费 +4. 管理资源绑定、供应商路由、订阅与配额 +5. 记录消费日志并生成统计 + +## 数据模型 + +| 表名 | 说明 | +|------|------| +| `product_category` | 产品类别树(parent_id 层级关系,resource_module 标记来源模块) | +| `product` | 产品注册表(resource_ref_id 关联资源模块内部ID) | +| `product_type_config` | 产品类型配置(运营者级别的 JSON 配置) | +| `product_resource` | 产品-资源绑定(一个产品可绑定多个资源,含配额和溢出产品) | +| `product_resource_supplier` | 资源绑定的供应商路由(优先级+权重) | +| `product_subscription` | 产品订阅(月度/按量,含配额跟踪和溢出计费) | +| `product_usage_log` | 消费日志(成本、售价、供应商、计费模式) | + +### 关键字段 + +**product_category** +- `resource_module` — 资源模块名称(如 `llmage`),标识该类别下的产品来自哪个模块 +- `has_product` — 是否可挂产品(`1`=是,`0`=仅分类节点) +- `product_type` — 产品类型标识(传递给产品的 product_type) + +**product** +- `resource_ref_id` — 资源模块内部 ID(如 llmage 中 `llm.id`),用于直接调用资源模块接口,无需维护 product_code 对照表 +- `product_code` — 产品编码(业务可读标识) +- `product_type` — 产品类型(如 `llm_model`, `llm_monthly`, `compute`) + +## 架构设计 + +### 1. 资源模块解耦导入 + +资源模块(如 llmage)和产品模块(product_management)职责分离: + +``` +资源模块 产品模块 +───────── ───────── +load_product_category_product() import_categories_and_products() + │ 只读自己的数据 │ 调用资源模块获取标准化数据 + │ 返回标准化格式 │ 负责所有写入和去重逻辑 + └──────────────────┐ │ + ▼ │ + {categories, products} + │ │ + └───────────►│ + │ 类别: name+parent_id+org_id 去重 → 已存在则跳过 + │ 产品: resource_ref_id+org_id 去重 → 已存在则更新 +``` + +**资源模块返回格式:** +```python +{ + 'success': True, + 'categories': [ + { + 'source_id': '资源模块内部类别ID', + 'name': '类别名称', + 'description': '描述', + 'product_type': 'llm_model', + 'product_type_title': '大模型API', + 'sort_order': 0, + }, + ], + 'products': [ + { + 'source_category_id': '对应的 source_id', + 'resource_ref_id': '资源模块内部ID(如 llm.id)', + 'product_code': '产品编码', + 'product_name': '产品名称', + 'product_type': 'llm_model', + 'brief_intro': '简介', + 'sort_order': 0, + }, + ], +} +``` + +**去重规则:** +- 类别:按 `name + parent_id + org_id` 判断,已存在则跳过 +- 产品:按 `resource_ref_id + org_id` 判断,已存在则更新(名称、简介等),不存在则创建 + +### 2. 资源模块接口调度 + +产品模块通过 `product_interface` 字典调度资源模块,所有接口参数统一使用 `resource_ref_id`: + +``` +调用方(UI/API) + │ + │ env.get_product_display_info(product_id='xxx') + ▼ +product_management (调度层) + │ 1. 查 product 表 → 获取 resource_ref_id + │ 2. 查 product_category 表 → 获取 resource_module + │ 3. 从 ServerEnv.product_interface 获取接口实现 + │ 4. 调用接口函数,传入 resource_ref_id + ▼ +资源模块(如 llmage/product_interface.py) + │ 直接用 resource_ref_id 操作自己的数据 + └─ 返回结果 +``` + +### 3. ServerEnv 注册 + +模块初始化时将所有方法注册到 ServerEnv: + +```python +def load_product_management(): + env = ServerEnv() + # 基础 CRUD + env.get_product_brief = get_product_brief + env.get_product_detail = get_product_detail + env.get_category_tree = get_category_tree + env.get_products_by_category = get_products_by_category + env.purchase_product = purchase_product + env.use_product = use_product + env.get_operator_config = get_operator_config + env.set_operator_config = set_operator_config + env.import_categories_and_products = import_categories_and_products + # 资源模块接口调度 + env.get_product_display_info = get_product_display_info + env.check_product_availability = check_product_availability + env.check_product_consumable = check_product_consumable + env.execute_product = execute_product + env.execute_product_stream = execute_product_stream + env.calculate_product_cost = calculate_product_cost +``` + +## 资源模块标准接口规范 + +任何资源模块(如 llmage、supplychain)要对接产品模块,需实现以下 7 个接口函数并在 `load` 时注册到 `ServerEnv.product_interface`: + +### 接口函数列表 + +| # | 函数名 | 参数 | 返回值 | 用途 | +|---|--------|------|--------|------| +| 1 | `get_product_display` | `(resource_ref_id)` | `{success, pricing_text, pricing_detail, extra_info}` | 展示定价信息 | +| 2 | `check_product_availability` | `(resource_ref_id, user_org_id)` | `{available, reason}` | 检查可用性 | +| 3 | `check_product_consumable` | `(resource_ref_id, user_id, user_org_id)` | `{consumable, reason, min_balance, pricing_available}` | 消费前综合预检 | +| 4 | `execute_product_service` | `(resource_ref_id, user_id, user_org_id, request_data)` | `{success, result, usage_data, task_id, status}` | 执行服务(非流式) | +| 5 | `execute_product_service_stream` | `(resource_ref_id, user_id, user_org_id, request_data)` | `AsyncGenerator[{chunk, usage_data, done}]` | 执行服务(流式) | +| 6 | `calculate_product_cost` | `(resource_ref_id, usage_data, user_org_id)` | `{success, amount, cost, discount, pricing_program_id}` | 计算费用 | +| 7 | `load_product_category_product` | `(parent_category_id)` | `{success, categories, products}` | 导入数据(标准化格式) | + +### 注册方式 + +资源模块在其 `__init__.py` 或 `init.py` 的 `load_*` 函数中注册: + +```python +def load_llmage(): + env = ServerEnv() + # ... 其他初始化 ... + from .product_interface import ( + get_product_display, check_product_availability, + check_product_consumable, execute_product_service, + execute_product_service_stream, calculate_product_cost, + ) + env.product_interface = { + 'module_name': 'llmage', + 'get_product_display': get_product_display, + 'check_product_availability': check_product_availability, + 'check_product_consumable': check_product_consumable, + 'execute_product_service': execute_product_service, + 'execute_product_service_stream': execute_product_service_stream, + 'calculate_product_cost': calculate_product_cost, + } +``` + +### resource_ref_id 映射 + +- `resource_ref_id` 存储在 `product.resource_ref_id` 字段 +- 对于 llmage:`resource_ref_id = llm.id` +- 产品模块调度时自动从 product 表提取该字段,资源模块直接使用 +- 资源模块无需知道 product_code,产品模块无需知道资源模块内部结构 + +## 核心业务流程 + +### 产品导入 + +``` +UI 操作 → import_category_products.dspy + → env.import_categories_and_products('llmage', org_id, parent_cat_id, user_id) + → importlib.import_module('llmage.init') + → llmage.load_product_category_product(parent_cat_id) # 返回标准化数据 + → 产品模块执行去重写入 + → 返回统计: 新增N类别, 跳过M类别, 新增X产品, 更新Y产品 +``` + +### 消费流程 + +``` +1. 展示阶段 + env.get_product_display_info(product_id) + → 查 product → 查 category → 获取 resource_module + → 调用 iface.get_product_display(resource_ref_id) + → 返回定价文本 + 详情 + +2. 预检阶段 + env.check_product_consumable(product_id, user_id, user_org_id) + → 调用 iface.check_product_consumable(resource_ref_id, user_id, user_org_id) + → 检查: 模型状态 + ppid定价 + 余额 ≥ min_balance + → 返回: consumable + reason + +3. 执行阶段 + env.execute_product(product_id, request_data, user_id, user_org_id) + → 调用 iface.execute_product_service(resource_ref_id, user_id, user_org_id, request_data) + → 根据 llm.stream 选择 sync/async/stream 路径 + → 返回: result + usage_data + task_id + +4. 计费阶段 + env.calculate_product_cost(product_id, usage_data, user_org_id) + → 调用 iface.calculate_product_cost(resource_ref_id, usage_data, user_org_id) + → 调用 pricing 模块计算 + 客户折扣 + → 返回: amount + cost + discount +``` + +### 消费引擎 (product_use) + +核心消费路由逻辑,支持订阅制和按量制: + +``` +product_use(product_id, user_id, user_org_id, used_amount, used_unit) + │ + ├─ 月度产品 (llm_monthly): + │ ├─ 查有效订阅 → 配额内扣减 (billing_mode='1') + │ └─ 超出配额 → 溢出计费 (overflow_rate × 超出量) + │ + ├─ 按量产品: + │ └─ sell_price = used_amount × product.price + │ + ├─ 供应商路由: + │ └─ product_resource → product_resource_supplier (按 priority+weight) + │ + ├─ 成本计算: + │ └─ supplychain.supplier_resource_price → unit_cost × used_amount + │ + └─ 写入 product_usage_log +``` + +## 目录结构 + +``` +product_management/ +├── models/ # 数据模型定义 (JSON) +│ ├── product.json +│ ├── product_category.json +│ ├── product_resource.json +│ ├── product_resource_supplier.json +│ ├── product_subscription.json +│ ├── product_type_config.json +│ └── product_usage_log.json +├── product_management/ +│ ├── __init__.py # ServerEnv 注册 + 入口函数 +│ └── core.py # ProductManager 全部业务逻辑 +├── init/ +│ └── data.yaml # 初始化数据 (appcodes 编码等) +├── json/ # CRUD 定义 (由 xls2crud 生成) +├── wwwroot/ # 前端页面 (由 xls2ui 生成) +├── README.md +└── setup.py +``` + +## 新增资源模块对接指南 + +以新模块 `supplychain` 为例: + +1. **实现接口函数** — 在 `supplychain/product_interface.py` 中实现上述 7 个函数 +2. **注册到 ServerEnv** — 在 `supplychain/init.py` 的 `load_supplychain()` 中注册 `product_interface` 字典 +3. **导入函数** — 实现 `load_product_category_product(parent_category_id)` 返回标准化格式 +4. **配置 appcodes** — 在 `init/data.yaml` 的 `resource_module` 下添加 `supplychain` 键值对 +5. **创建类别** — 在 UI 中创建产品类别,`resource_module` 选择 `supplychain` +6. **触发导入** — 点击"产品导入"按钮,自动调用导入流程 + +所有参数使用 `resource_ref_id`(supplychain 的内部 ID),产品模块自动处理映射。