pipeline-llm/README.md
yumoqing 8e32cb85f3 feat(llm): 会话agent可选模型收窄为对话能力t2t/i2t/m2t+能力字典加m2t(2026-09-06用户定夺)
- selection.CHAT_CAPS=('t2t','i2t','m2t') 作为会话形态能力白名单唯一事实源
- model_options/resolve_model_name 加 capabilities 参数('chat'哨兵),
  IN 列表展开占位符(sqlor传list会崩),capability 空串按 t2t 归一(COALESCE+NULLIF)
- chat_inference 同步门禁从「只认 t2t」放宽到 CHAT_CAPS——
  实测根因:测试库 6 个机构策略主模型全是 qwen3.8-max(i2t),
  16:28 已产生 FAILED「capability mismatch: i2t != t2t」,会话agent选它必挂
- m2t 入字典四处同步:种子 init/data.json + 端点注释 models.dspy
  + design-spec §6 能力表(+ 提取提示词在 pipeline-platform)
2026-09-06 19:32:59 +08:00

116 lines
8.0 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.

# 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` | 适配模板(协议×能力形态去重;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_llm()`(try/except ImportError 兜底)
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)。
- **会话 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(模板生成按输入媒体处理)。
## 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`。