145 lines
11 KiB
Markdown
145 lines
11 KiB
Markdown
# pipeline-llm — 产线平台模型治理模块
|
||
|
||
产线平台的模型层:供应商/账号/模型治理 + 组织容错策略 + 限额限流 + 双维度记账。
|
||
所有表 `llm_` 前缀,建在宿主应用库(pipeline)。
|
||
|
||
## 模块定位
|
||
|
||
解决五类问题:
|
||
1. **供给侧治理**:供应商协议+全球端点目录、账号(钱包)、模型注册、适配模板
|
||
2. **容量治理**:供应商限流 → 多账号轮转;多供应商 → 主备模型容错
|
||
3. **需求侧治理**:组织级模型容错策略、组织限额限流、个人限额限流
|
||
4. **计费**:双维度记账(成本侧记账号、消费侧记组织/个人),模型挂定价 `pricing_program.ppid`
|
||
5. **端点选择**:端点目录配在供应商下(全球配置一次),账号选用端点,客户按区域偏好(prefer/must)
|
||
|
||
## 数据表(8 张)
|
||
|
||
| 表 | 说明 |
|
||
|---|---|
|
||
| `llm_vendor` | 供应商(端点目录 JSON;2026-09-05 起不存协议字段,请求形态归模型挂的适配模板) |
|
||
| `llm_account` | 供应商账号(钱包:余额/充值/选端点,api_key AES 加密;模型可绑定账号 account_id 收窄候选池) |
|
||
| `llm_model` | 模型注册(能力分类/挂定价 ppid/sync_mode/query_profile_ids,name 唯一;2026-09 起不存单价,定价走 ppid) |
|
||
| `llm_api_profile` | 适配模板(提交模板按协议×能力×形态指纹去重复用,见 _tpl_fingerprint;data/response 模板全 Jinja2,含媒体转换函数;method 独立列,2026-09-06 起不塞 request_template) |
|
||
| `llm_org_policy` | 组织容错策略(主备模型 + 辅助模型 utility_model_id + 端点偏好) |
|
||
| `llm_org_quota` | 组织限额限流(池余额 + QPM) |
|
||
| `llm_user_quota` | 个人限额限流(额度上限 + QPM) |
|
||
| `llm_usage` | 用量流水(双维度记账事实表;status 统一枚举 SUCCEEDED/FAILED/PENDING/RUNNING/recharge;accounting_status 仅 SUCCEEDED 行有意义否则 NULL;usages 字段存非 token 计价因子 JSON) |
|
||
|
||
## 调用门禁链
|
||
|
||
```
|
||
① 个人限流 → ② 组织限流 → ③ 个人额度 → ④ 组织池(预授权)
|
||
→ ⑤ 组织策略选模型(主→备) → ⑥ 账号×端点候选池(偏好过滤+余额加权+冷却避让)
|
||
→ ⑦ 调用 → ⑧ 结算(实际用量,多退少补,双维度记账)
|
||
```
|
||
任何一级不过:快速失败 + 明确中文原因(不挂起、不静默)。
|
||
|
||
## 集成方式(复用不改建)
|
||
|
||
模块只提供治理引擎与配置管理;实际 LLM 转发仍由宿主已有的
|
||
`pipeline_service.llm_bridge` / `llm_proxy` 承担,在两处挂钩子:
|
||
- `llm_bridge._get_model_config`:治理启用时前置解析(主备容错+端点选择)
|
||
- `llm_proxy.proxy_chat_completion`:调用前后接门禁与结算(运行环境 token 路径)
|
||
|
||
机构未配置治理(无策略、无新模型)→ 返回 `__LEGACY__`,调用方回退旧 `llm` 表,向后兼容。
|
||
|
||
## 宿主接入清单
|
||
|
||
1. `app/pipeline_app.py` init() 加 `load_pipeline_llm()`(try/except ImportError 兜底;规范名 load_<模块名>,旧名 `load_llm` 保留为别名)
|
||
2. `build.sh`:clone 枚举 + pip install 枚举 + CRUD 生成枚举 + wwwroot 符号链接枚举
|
||
3. `scripts/create_tables.py`:IDE 幂等建表枚举加 `pipeline-llm`(或执行本模块 `mysql.ddl.sql`)
|
||
4. `scripts/import_init.py`:INIT_MODULES 加本模块 `init/data.json`(appcodes 字典)
|
||
5. `conf/rp.json`:`/pipeline-llm/**` 注册到 `logined`;运行 `scripts/load_path.py`
|
||
6. `wwwroot/index.ui` 主菜单加「模型治理」项(URL `/pipeline-llm`)
|
||
7. `restart-pipeline.sh` 重启(.py 变更必须重启;`api_key` 有缓存,改 key 也须重启)
|
||
|
||
## 关键约定
|
||
|
||
- **api_key 用 AES 单层加密**(`appPublic.aes.aes_encode_b64`,password_key 做密钥)。
|
||
⚠️ 不用 RC4:`password()/unpassword()` 盐不对称,加密后永久不可解。
|
||
- **限流用 Redis 分钟窗口原子计数**(db4,与会话 db3 隔离);Redis 故障时限流放行(fail-open),
|
||
余额仍由 DB 条件 UPDATE 守住。
|
||
- **429 → (账号,端点)对冷却 60s**;账号级冷却用 `*` 通配键。
|
||
- **status 统一枚举 + 记账三态(2026-09-05 定夺;2026-09-06 补 NULL 语义,m0012)**:
|
||
`llm_usage.status` 各家模型归一为 SUCCEEDED/FAILED/PENDING/RUNNING(其他=recharge 充值对账行)。
|
||
`accounting_status` 只有三个值——`created`(待记账)→ `accounted`(记账成功)/ `failed`(记账失败);
|
||
**只有 status='SUCCEEDED' 才有记账意义**:仅成功行写 created,非 SUCCEEDED 行保持 **NULL**
|
||
(不冒充待记账)。出账循环扫 `accounting_status='created' AND status='SUCCEEDED'`。
|
||
自用 vs 跨机构分流由产品层 `product_accounting_generic` 的 is_self_use 决定(自用只记 PAY* 采购成本,
|
||
跨机构另记 PAY 客户应付)。流水 `usages` 存非 token 计价因子(视频时长/
|
||
分辨率等),异步出账循环(独立进程,需 load_appbase 提供 get_business_date)
|
||
读入定价引擎。未挂 ppid 的模型出账置 failed 并写明原因(不静默)。
|
||
- **method 独立列(2026-09-06 用户定夺,m0012)**:`llm_api_profile.method`(GET/POST,
|
||
默认 POST)不塞 request_template JSON;运行时读该列决定 HTTP 动词,存量行由迁移
|
||
从 request_template 的 $.method 回填,查询步骤兼容回退解析。
|
||
- **媒体转换铁律(2026-09-06 三数组契约,对齐 sage/llmage)**:生成类模型适配模板
|
||
data 用 `b64media2url(request, _f)` 逐元素转公网 URL、response 用
|
||
`downloadfile2url(request, url)`(生成物落地本地,上游 URL 仅 24 小时);
|
||
上行媒体统一三数组参数 `image_files`/`audio_files`/`video_files`(字符串或数组
|
||
均可,Jinja `is string` 动态判断),变长输入用 `{% for %}` 按文档示例组装。
|
||
- **异步模型执行器**:sync_mode=async 时走提交→按 query_profile_ids 轮询→
|
||
取结果;等待预算默认 600 秒,`_timeout` 可覆盖(上限 900)。
|
||
- **记账 derived 计费双保险(2026-09-07 根治 qwen3.8-max 记账 failed)**:
|
||
① `inference.py` 同步路径把模型返回的 usage 原文落 `llm_usage.usages`(对齐异步路径与
|
||
sage/llmage 范式)——定价 YAML 的 derived 变量引用(`prompt_tokens_details.cached_tokens`)
|
||
依赖原始结构,此前同步路径丢弃 usages 导致 uncache_tokens 兜底成 0、输入 token 永不计费;
|
||
② `accounting.py` `_settle_one` 出账时对缺失的 `prompt_tokens_details` 补 `{'cached_tokens': 0}`,
|
||
并按 `created_at` 注入 `hour` 维度(忙闲时分档计价用,如 deepseek-v4-pro-0813)。
|
||
定价 YAML 铁律:定价项顶层只放运行时必带的维度,Batch/显式缓存/限时折扣等备选模式绝不入
|
||
YAML(缺失键会炸整个方案)——详见 pricing-data-format 技能 §14-16。
|
||
- **会话 agent 模型选择收窄为对话能力(2026-09-06 用户定夺)**:
|
||
`selection.CHAT_CAPS = ('t2t','i2t','m2t')` 是会话形态能力白名单的**唯一事实源**——
|
||
所有会话 agent 模型下拉(agent_model_options / cockpit_model_options /
|
||
get_model_options 角色配置)传 `capabilities='chat'` 过滤;持久化入口
|
||
(set_agent_model 个人默认 / cockpit_agent 角色模型 / pipeline_service
|
||
gateway._resolve_and_persist_model 项目模型)同样只接受对话能力,防绕过下拉。
|
||
`chat_inference` 同步门禁从「只认 t2t」放宽到 CHAT_CAPS(实测根因:测试库
|
||
机构策略主模型 qwen3.8-max 能力是 i2t,旧门禁直接 FAILED
|
||
「capability mismatch: i2t != t2t」,会话 agent 选它必挂)。
|
||
存量模型 `capability` 空串按 t2t 语义归一(COALESCE+NULLIF)。
|
||
embedding/rerank/图视频生成等非对话模型各有专属入口(list_models 按能力分栏)。
|
||
- **m2t(多媒体生文)入能力字典四处同步(m0013)**:种子 `init/data.json` +
|
||
端点注释 `api/v1/models.dspy` + `docs/design-spec.md §6` + 提取提示词
|
||
(`pipeline_platform.platform_ability._EXTRACT_PROMPT`,判定表补
|
||
「多媒体输入→文本 ⇒ m2t」「仅图像→文本 ⇒ i2t」「仅文本→文本 ⇒ t2t」,
|
||
且 r2v 判定加「输出为视频」限定防误吸文本输出模型);
|
||
`_MEDIA_INPUT_CAPS` 加 m2t(模板生成按输入媒体处理)。
|
||
|
||
## 治理页面(弹窗化,2026-09-07 用户定夺)
|
||
|
||
`wwwroot/index.ui` 顶部统计条(llm_dashboard_widget)+ 7 张功能卡片。
|
||
**每张卡片点击后弹窗处理(PopupWindow 85%×85%),不再在页面下方 llm_content 容器展示**:
|
||
- `api/card_popup.dspy`:target 白名单(llm_vendor/llm_model/llm_api_profile/llm_org_policy/
|
||
llm_org_quota/llm_user_quota/llm_usage)→ 弹窗内嵌对应 CRUD 页 urlwidget;
|
||
非白名单 target 返回「无效的功能入口」提示(不拼 URL)。
|
||
- 模型注册弹窗(target=llm_model)顶部额外两个按钮:
|
||
- **🤖 自动配置** → `api/auto_config_popup.dspy`:输入 API 文档与定价信息的链接或正文,
|
||
「开始自动配置」按钮 script 组装指令后驱动弹窗内嵌 AgentIO
|
||
(pipeline_id=platform_general,弹窗级独立 session_id,spec 锚定会话隔离天然成立),
|
||
内部助手全链完成(抓取/提取/落库/定价/测试/记账检查)后汇报模型状态。
|
||
- **✅ 上线检查** → `api/online_check_popup.dspy`:模型下拉
|
||
(`api/get_llm_online_check_models.dspy`,value=模型名,test_model_call 按名调用)+
|
||
「上线检查」按钮 → 内部助手按模型配置后的测试过程处理(test_model_call 真实调用 →
|
||
check_model_accounting 记账检查 → platform_llm_status 状态复核),最后明确告知通过/不通过。
|
||
- 驱动 AgentIO 的机制:`io.inputw.textw.setValue(指令)` + `io.inputw.input_finished()`
|
||
(AgentInput 原生方法 dispatch('inputed') → AgentIO POST 流),零自定义 JS 文件。
|
||
- 权限:新 dspy 都在 `/pipeline-llm/**` logined 通配内,无需新增 RBAC;
|
||
内部助手工具本身有 owner 角色代码层硬门禁(pipeline-platform)。
|
||
|
||
## API 文档端点(2026-09-05)
|
||
|
||
`/pipeline-llm/api/v1/docs/`(RBAC 授权 any,公开文档照 sage /bricks/docs 先例):
|
||
- `index.ui` 按 `_lang` 参数分发 ApiDoc 组件(zh/en/ja/ko 四语言,带白名单回落 zh)
|
||
- `api_{zh,en,ja,ko}.md` 四语言文档(chat/completions + models 端点、异步模型参数、
|
||
媒体转换约定、token 认证、计费说明)
|
||
- 依赖宿主 conf/config.json 的 processors 注册 `[".md","md"]`(MarkdownProcessor)
|
||
|
||
## 文档
|
||
|
||
- `docs/design-spec.md` — 完整设计规范(架构/表/机制/功能点/分期)
|
||
- `docs/test-cases.md` — 测试用例(30 例,部署后全部执行)
|
||
|
||
## 开发日志
|
||
|
||
见 `docs/work-log-2026-09-01.md`。
|