- json/llm_call_trace_list.json: noedit只读列表;exclouded隐藏id/req_path/resp_path/org_id;
url/note加宽;kind/method内联枚举(含gen同步生成,此前草稿引用的get_llm_kind_options.dspy不存在);
删color_mapping(bricks全库0消费的死配置);toolbar查看IO+binds urlwidget→PopupWindow(85%)
url不带?id=${id}$——生成期ArgsConvert会eval成<built-in function id>垃圾串,
行数据id经_add_event_data自动并入params(dapi/llmage生产同款)
- api/llm_call_trace_io.dspy: 薄代理(get_user门禁+get_userorgid机构隔离)→env.llm_call_trace_io;
修草稿4处实锤bug:get_user未await/user.get当dict/sor块外使用/返回PopupWindow套娃
- init.py: llm_call_trace_io读表+同批次+llm_usage上下文(块内读全拷dict)→读文件
(trace.trace_root realpath前缀校验防穿越)→TabPanel三tab(摘要Html+pre escape防注入/
请求/响应CodeEditor readonly mode=null);单侧150K截断提示盘上路径;env注册
- api/llm_project_cost_query.dspy: 改{total,rows}契约接Tabular data_url(PageDataLoader);
wwwroot/llm_project_cost/index.ui: InlineForm(date_from/date_to/status)+Tabular;
script内getWidgetById起点bricks.app(弹窗DOM挂body下,app.root搜不到)
- card_popup.dspy: titles加llm_call_trace;新增page_targets白名单挂按项目费用页;
index.ui挂两张卡片(调用原文追踪/按项目费用)
- accounting.py: _maybe_cleanup_traces每日一次接cleanup_expired(trace.py铁律3
文档承诺worker每日调但循环没接,补齐);放抢锁前(清理幂等多进程同日无害)
- scripts/load_path.py: 注释说明/**通配已覆盖新页面(rbac check_roles_path前缀匹配)
- models/mysql.ddl/README/design-spec: 9张表同步(kind补gen枚举;3.9节;F19/F20)
- scripts/test_llm_call_trace_io.py: stub harness 22断言全过(隔离/穿越/转义/截断/每日门控)
- .gitignore: wwwroot/llm_call_trace/生成目录不入库
12 KiB
12 KiB
pipeline-llm — 产线平台模型治理模块
产线平台的模型层:供应商/账号/模型治理 + 组织容错策略 + 限额限流 + 双维度记账。
所有表 llm_ 前缀,建在宿主应用库(pipeline)。
模块定位
解决五类问题:
- 供给侧治理:供应商协议+全球端点目录、账号(钱包)、模型注册、适配模板
- 容量治理:供应商限流 → 多账号轮转;多供应商 → 主备模型容错
- 需求侧治理:组织级模型容错策略、组织限额限流、个人限额限流
- 计费:双维度记账(成本侧记账号、消费侧记组织/个人),模型挂定价
pricing_program.ppid - 端点选择:端点目录配在供应商下(全球配置一次),账号选用端点,客户按区域偏好(prefer/must)
数据表(9 张)
| 表 | 说明 |
|---|---|
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 是计费唯一事实源——上游 usage 原文 JSON 全量不截断,token 计数与按量因子都在其中;req_tokens/resp_tokens 冗余列已删(m0021),统计查询走 JSON_EXTRACT;project_id 关联 sd_projects 按项目统计费用(非项目调用哨兵'0',查询不传=不区分项目,m0025);call_id 关联 llm_call_trace 上行/下行原文) |
llm_call_trace |
调用原文追踪(2026-09-10 用户定夺):每次上游 HTTP 往返一行(chat/gen/submit/query),请求/响应原文脱敏落盘 filesroot/llm_trace/<日>/<call_id>_.req/.resp.json(Authorization 等敏感 header 值替换 [REDACTED],URL 去 query);call_id 与 llm_usage.call_id 同值——按流水反查全部原文;列表页「查看IO」弹窗(llm_call_trace_io.dspy → env.llm_call_trace_io)展示上下文摘要+请求/响应 CodeEditor 三 Tab,realpath 前缀校验防路径穿越,机构隔离(org_id='0' 平台可看全部);保留 90 天(TRACE_RETENTION_DAYS),过期清理由记账 worker 循环每日调 cleanup_expired(accounting._maybe_cleanup_traces);文件不进 dbackup 库备份,需另行同步备份 |
调用门禁链
① 个人限流 → ② 组织限流 → ③ 个人额度 → ④ 组织池(预授权)
→ ⑤ 组织策略选模型(主→备) → ⑥ 账号×端点候选池(偏好过滤+余额加权+冷却避让)
→ ⑦ 调用 → ⑧ 结算(实际用量,多退少补,双维度记账)
任何一级不过:快速失败 + 明确中文原因(不挂起、不静默)。
集成方式(复用不改建)
模块只提供治理引擎与配置管理;实际 LLM 转发仍由宿主已有的
pipeline_service.llm_bridge / llm_proxy 承担,在两处挂钩子:
llm_bridge._get_model_config:治理启用时前置解析(主备容错+端点选择)llm_proxy.proxy_chat_completion:调用前后接门禁与结算(运行环境 token 路径)
机构未配置治理(无策略、无新模型)→ 返回 __LEGACY__,调用方回退旧 llm 表,向后兼容。
宿主接入清单
app/pipeline_app.pyinit() 加load_pipeline_llm()(try/except ImportError 兜底;规范名 load_<模块名>,旧名load_llm保留为别名)build.sh:clone 枚举 + pip install 枚举 + CRUD 生成枚举 + wwwroot 符号链接枚举scripts/create_tables.py:IDE 幂等建表枚举加pipeline-llm(或执行本模块mysql.ddl.sql)scripts/import_init.py:INIT_MODULES 加本模块init/data.json(appcodes 字典)conf/rp.json:/pipeline-llm/**注册到logined;运行scripts/load_path.pywwwroot/index.ui主菜单加「模型治理」项(URL/pipeline-llm)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存上游 usage 原文 JSON(全量不截断, 2026-09-10 用户定夺:usages 是计费唯一事实源,req_tokens/resp_tokens 冗余列删除 m0021;此前 [:2000] 截断会产生非法 JSON → 出账 json.loads 静默失败 → derived 兜底 0 → 永不计费),异步出账循环(独立进程,需 load_appbase 提供 get_business_date)读入定价引擎;usages 非法 JSON 或为空 → 出账置 failed 写明 原因(禁静默 0 元账)。未挂 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(字符串或数组 均可,Jinjais 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。