diff --git a/skills_library/pipelines/platform_general/common/auto-api-pricing-config/SKILL.md b/skills_library/pipelines/platform_general/common/auto-api-pricing-config/SKILL.md new file mode 100644 index 0000000..60a1361 --- /dev/null +++ b/skills_library/pipelines/platform_general/common/auto-api-pricing-config/SKILL.md @@ -0,0 +1,65 @@ +--- +name: auto-api-pricing-config +description: 从API文档/报价文档自动生成uapi接口配置(headers/data/params/response)和定价(pricing YAML)。内部agent配置接口或报价时必读。 +capability: auto_api_pricing +tools: [fetch_model_doc, extract_llm_api_spec, apply_llm_config, platform_llm_status] +--- + +# 接口与报价自动配置(文档 → uapi + pricing) + +两条自动配置链路:**API 文档 → 接口配置**(upapp + uapi + uapiio), +**报价文档 → 定价配置**(pricing_program + pricing_program_timing YAML)。 +定价只允许来自文档原文,禁止编造。 + +## 目标表(pipeline 库,均已存在) + +| 表 | 用途 | 关键字段 | +|---|---|---| +| upapp | 供应商系统 | name, baseurl, secretkey(AES), dynamic_func | +| uapi | 接口定义 | upappid, name, title, path, httpmethod, stream(sync/async/stream/false), chunk_match, headers/data/params/response(Jinja2模板), ioid | +| uapiio | 输入字段定义 | name, input_fields(JSON数组或类型schema) | +| pricing_program | 定价项目 | name(模型名), providerid, pricing_belong(provider/llm), currency | +| pricing_program_timing | 定价数据 | ppid, pricing_data(YAML), enabled_date, expired_date | +| upappkey | 供应商密钥 | upappid, apikey(AES加密)——**永远由人工补录,不自动生成** | + +## 工作流 + +1. `fetch_model_doc` 抓取 API 文档 + 报价页面 +2. `extract_llm_api_spec` 提取规格(端点/鉴权/请求体/响应体/定价) +3. **接口配置**:按「协议模板库」生成 uapi 记录(见 references/uapi-templates.md) + - 先查 upapp 是否已有该供应商(按 baseurl 匹配),没有才新建 + - stream 接口:chunk_match 填流式分块匹配串(OpenAI 系为 `data: `) + - async 接口:chunk_match 填任务 id 字段名(如 `task_id`),response 模板必须输出 `taskid`+`status` + - 异步接口通常需要配对的**状态查询接口**(`{name}status`,GET,响应输出最终产物 + usage) +4. **报价配置**:按「定价生成规则」生成 pricing_program_timing YAML(见 references/pricing-patterns.md) +5. 把生成结果摘要(端点/鉴权方式/每模型定价)给用户确认后再落库 +6. 落库后用 `platform_llm_status` / CRUD 查询复核,回报结果 + +## uapi 模板语法约定(Jinja2) + +- 占位变量:`{{apikey}}`(供应商密钥,运行时注入)、`{{model}}`、`{{prompt}}`、`{{taskid}}` +- 字符串字段必须 `{{json.dumps(prompt, ensure_ascii=False)}}`,防引号注入 +- 多媒体输入用 `{{b64media2url(request, image_file)}}`(base64→临时URL),产物输出用 + `{{downloadfile2url(request, output.video_url)}}`(下载到平台存储再给URL) +- 流式响应模板区分 `object == "chat.completion"`(message)与 chunk(delta), + 用 `{% if usage %}` 判断结束并输出 `"finish": "1"` +- 条件参数用 `{% if xxx %}...{% endif %}`(sys_prompt/image_file/tools 等可选字段) + +## 定价生成规则(硬规则) + +- 单位:YAML 里 `unit_prices` 用人类可读值 + `unit: 百万` + `unit_values: {百万: 1000000}`; + 文档「元/百万tokens」直接落数值,「元/千tokens」×1000 换算 +- **区间下界含 0 必须用 `=~`**(`0 =~ 128000`);`~=` 会排除 0 值导致计费为 0 +- 键名是 `pricings`(复数);`unit_values` 只放实际用到的单位 +- 缓存价:阿里云系缓存命中 = 输入价 × 0.2(以文档为准);拆 `uncache_tokens`/`cached_tokens` 两个因子 +- 模型名映射:API 返回带点版本号(`doubao-seedance-2.0`)而定价用连字符(`doubao-seedance-2-0`)时, + 必须在 `model_mappings` 加映射,否则静默匹配失败 +- YAML 布尔陷阱:过滤值 `no/yes/true/false` 必须加引号(`"no"`) +- 文档没写的价格 → 不生成该定价项并明确标注「文档未列价,需人工补录」,禁止编造 +- 阶梯价:同一档位的所有因子项(输入/缓存/输出)都要带相同的区间过滤条件 + +## 参考 + +- `references/uapi-templates.md` — 协议模板库(从生产真实配置提炼):openai_compat 流式/多模态、 + dashscope 异步、任务轮询型、音乐/3D 特殊响应、upapp/uapiio 样例 +- `references/pricing-patterns.md` — 定价 YAML 五类模板(token/缓存/视频秒/按次/积分)+ 单位换算表 diff --git a/skills_library/pipelines/platform_general/common/auto-api-pricing-config/references/pricing-patterns.md b/skills_library/pipelines/platform_general/common/auto-api-pricing-config/references/pricing-patterns.md new file mode 100644 index 0000000..e32a34c --- /dev/null +++ b/skills_library/pipelines/platform_general/common/auto-api-pricing-config/references/pricing-patterns.md @@ -0,0 +1,179 @@ +# 定价 YAML 模板库(生产真实配置提炼) + +定价落 `pricing_program_timing.pricing_data`(YAML 字符串),引擎按 `pricings` 数组匹配。 +先看报价文档是哪种计费形态,选对应模板。 + +## 数据模型(落库三层) + +``` +pricing_program 一个模型/产品一个定价项目 + ├─ name 模型名(如 "通义千问 qwen3.7-max") + ├─ providerid 供应商 id(关联 upapp) + ├─ pricing_belong provider | llm + └─ currency CNY | USD +pricing_program_timing 生效时段 + 定价数据 + ├─ ppid 关联 pricing_program.id + ├─ pricing_data YAML(见下) + ├─ enabled_date / expired_date +pricing_item(旧表,参考) spec_value / pricing_unit / pricing_amount / cost_amount +``` + +新配置统一走 `pricing_data` YAML;`pricing_item` 是历史遗留,了解即可。 + +## 单位换算表(元 → YAML unit_prices) + +| 文档单位 | 换算 | YAML 落法 | +|---|---|---| +| 元/百万tokens | 直接 | `unit_prices: 6.0` + `unit: 百万` + `unit_values:{百万:1000000}` | +| 元/千tokens | ×1000 | `unit_prices: 6.0` + `unit: 百万`(先×1000 再落百万) | +| 元/秒(视频/音频) | 直接 | `unit_prices: 1.6` + `unit: 秒` | +| 元/次 | 直接 | `unit_prices: 0.2` + `unit: 次` | +| 元/张(图片) | 直接 | `unit_prices: 0.2` + `unit: 次`(按张=按次) | + +**禁止** `6e-06` 这种科学计数法——引擎会二次除法算错。 + +## 模板 1:文本模型 token 计价(含缓存拆分) + +阿里云系缓存命中 = 输入价 × 0.2(以文档为准)。真实样例(qwen3.7-max,限时5折后): + +```yaml +unit_values: + 百万: 1000000 + +fields: + price_factors: {type: string, role: factor, label: 计价因子} + unit_prices: {type: float, role: factor, label: 单位定价} + unit: {type: string, role: factor, label: 计价单位} + uncache_tokens: + type: int, role: factor, label: 非缓存tokens + derived: prompt_tokens - prompt_tokens_details.cached_tokens + cached_tokens: + type: int, role: factor, label: 缓存tokens + derived: prompt_tokens_details.cached_tokens + completion_tokens: {type: int, role: factor, label: 输出tokens} + model: {type: string, role: filter, label: 模型} + +pricings: + - price_factors: uncache_tokens + unit_prices: 6.0 # 输入 6元/百万(限时5折后) + unit: 百万 + filters: [{model: qwen3.7-max}] + - price_factors: cached_tokens + unit_prices: 1.2 # 缓存 = 6 × 0.2 + unit: 百万 + filters: [{model: qwen3.7-max}] + - price_factors: completion_tokens + unit_prices: 18.0 # 输出 18元/百万 + unit: 百万 + filters: [{model: qwen3.7-max}] +``` + +派生字段 `uncache_tokens`/`cached_tokens` 引擎自动算,**不要**手动预填到 usage。 + +## 模板 2:阶梯价(区间过滤) + +同一档位的所有因子项都要带相同区间条件。下界含 0 必须 `=~`。 + +```yaml +pricings: + - price_factors: uncache_tokens + unit_prices: 2.0 + unit: 百万 + filters: + - model: qwen3.7-plus + prompt_tokens: 0 =~ 256000 # ← 下界0用 =~ + value_mode: between + - price_factors: cached_tokens + unit_prices: 0.4 + unit: 百万 + filters: + - model: qwen3.7-plus + prompt_tokens: 0 =~ 256000 # ← 同区间 + value_mode: between + - price_factors: completion_tokens + unit_prices: 8.0 + unit: 百万 + filters: + - model: qwen3.7-plus + prompt_tokens: 0 =~ 256000 # ← 同区间(输出也随档位变) + value_mode: between +``` + +**新增区间过滤键**必须同步加进 `fields`(type: int, role: filter, value_mode: between), +否则区间过滤静默失效 → 计费为 0。 + +## 模板 3:视频/音频按秒(分辨率多档) + +真实样例(万相视频,720P/1080P 分档): + +```yaml +unit_values: + 秒: 1 + +pricings: + - price_factors: duration + unit_prices: 0.6 # 720P 0.6元/秒 + unit: 秒 + filters: [{model: wan2.6-t2v, resolution: "720P"}] + - price_factors: duration + unit_prices: 1.0 # 1080P 1.0元/秒 + unit: 秒 + filters: [{model: wan2.6-t2v, resolution: "1080P"}] +``` + +过滤值(resolution)必须与 uapi 查询响应里 `usage.update` 塞的值**逐字符一致**。 + +## 模板 4:按次计费(图片/音频/单任务) + +```yaml +unit_values: + 次: 1 + +pricings: + - price_factors: flat + unit_prices: 0.2 # 0.2元/张 + unit: 次 + filters: [{model: qwen-image-2.0}] +``` + +`price_factors: flat` + 固定 `unit_prices` = 按次;`usage` 里给 `{"count": 1}`。 + +## 模板 5:积分制 / total_tokens + +```yaml +pricings: + - price_factors: total_tokens # 按总 token + unit_prices: 24.0 + unit: 百万 + filters: [{model: doubao-seedance-2-0}] +# 或积分制 + - price_factors: credits + unit_prices: 0.05 + unit: 次 +``` + +## 模型名映射(必查) + +API 返回带点版本号、定价用连字符时必须加映射,否则静默不匹配: + +```yaml +model_mappings: + doubao-seedance-2.0: doubao-seedance-2-0 + doubao-seedance-2.0-fast: doubao-seedance-2-0-fast +``` + +## YAML 布尔陷阱 + +过滤值 `no/yes/true/false` 必须加引号(`"no"`),否则被解析成布尔导致匹配失败。 + +## 生成后自检清单 + +- [ ] `unit_prices` 是人类可读值(6.0 不是 6e-06) +- [ ] `unit_values` 只含用到的单位 +- [ ] 下界含 0 的区间用 `=~`(不是 `~=`) +- [ ] 键名 `pricings`(复数) +- [ ] 区间过滤键已加进 `fields` 且带 `value_mode: between` +- [ ] 模型名与 API 返回逐字符一致(或加 model_mappings) +- [ ] 布尔过滤值已加引号 +- [ ] 文档没写的价格 → 不生成 + 标注「需人工补录」,未编造 +- [ ] 用 factor=0 测一遍确认仍匹配 diff --git a/skills_library/pipelines/platform_general/common/auto-api-pricing-config/references/uapi-templates.md b/skills_library/pipelines/platform_general/common/auto-api-pricing-config/references/uapi-templates.md new file mode 100644 index 0000000..9f1a9b2 --- /dev/null +++ b/skills_library/pipelines/platform_general/common/auto-api-pricing-config/references/uapi-templates.md @@ -0,0 +1,232 @@ +# uapi 协议模板库(生产真实配置提炼) + +生成 uapi 记录时按协议族选模板。以下为真实生产配置的规范化版本,占位符保持 `{{xxx}}` Jinja2 语法。 + +## 1. OpenAI 兼容 · 流式文本(stream=stream) + +适用:deepseek / moonshot / stepfun / 智谱 / Grok / 通义兼容端点 等 `/chat/completions` 流式接口。 + +```json +upapp.baseurl = "https://api.deepseek.com" // 供应商根地址,不带 path +uapi: + name = "t2t", title = "文生文" + path = "/chat/completions", httpmethod = "POST" + stream = "stream", chunk_match = "data: " + headers = {"Authorization": "Bearer {{apikey}}", "Content-Type": "application/json"} +``` + +data 模板(含 tools/多模态可选字段): + +``` +{ +{% if stream %} + "stream_options":{"include_usage": true}, +{% endif %} +{% if tools %} + "tools": {{json.dumps(tools, ensure_ascii=False)}}, +{% endif %} +{% if tool_choice %} + "tool_choice": "{{tool_choice}}", +{% endif %} +{% if messages %} + "messages": {{json.dumps(messages, ensure_ascii=False)}}, +{% else %} + "messages": [ +{% if sys_prompt %} + {"role": "system", "content": {{json.dumps(sys_prompt, ensure_ascii=False)}}}, +{% endif %} + {"role": "user", "content": {{json.dumps(prompt, ensure_ascii=False)}}} + ], +{% endif %} +{% if stream %} + "stream": true, +{% endif %} + "model": "{{model}}" +} +``` + +response 模板(流式通用): + +``` +{ + "model": "{{model}}", +{% if object == "chat.completion" %} + "reasoning_content": {{json.dumps(choices[0].message.reasoning_content, ensure_ascii=False)}}, + "content": {{json.dumps(choices[0].message.content, ensure_ascii=False)}}, +{% elif len(choices)>0 %} + "reasoning_content": {{json.dumps(choices[0].delta.reasoning_content, ensure_ascii=False)}}, + "content": {{json.dumps(choices[0].delta.content, ensure_ascii=False)}}, +{% endif %} +{% if usage %} + "finish": "1", + "usage": {{json.dumps(usage)}} +{% else %} + "finish": "0" +{% endif %} +} +``` + +多模态变体(tm2t/ti2t):user.content 改数组,按 `{% if image_file %}` 插入 +`{"type": "image_url", "image_url": "{{b64media2url(request, image_file)}}"}`, +video_file/audio_file 同理(type 为 video_url/audio_url),最后附 `{"type": "text", "text": {{json.dumps(prompt, ensure_ascii=False)}}}`。 + +## 2. 阿里云 DashScope · 原生异步(stream=async) + +适用:通义万相视频生成(`/api/v1`),头需 `X-DashScope-Async: enable`。 + +```json +uapi: + name = "t2v"/"2i2v", path = "/services/aigc/video-generation/video-synthesis" + httpmethod = "POST", stream = "async", chunk_match = "" + headers = {"X-DashScope-Async": "enable", "Authorization": "Bearer {{apikey}}", "Content-Type": "application/json"} +``` + +data 模板(首尾帧示例): + +``` +{ + "model": "{{model}}", + "input": { + "prompt": {{json.dumps(prompt)}}, +{% if negative_prompt %} + "negative_prompt": {{json.dumps(negative_prompt)}}, +{% endif %} +{% if image2_file %} + "last_frame_url": "{{b64media2url(request, image2_file)}}", +{% endif %} + "first_frame_url": "{{b64media2url(request, image1_file)}}" + }, + "parameters": { + "resolution": "{{resolution or '1080P'}}", + "prompt_extend": true + } +} +``` + +提交响应模板(异步必须输出 taskid + status): + +``` +{ +{% if output %} + "taskid": "{{output.task_id}}", + "status": "{{output.task_status}}" +{% else %} + "status": "FAILED", + "error": "{{code}}.{{message}}" +{% endif %} +} +``` + +**配对状态查询接口**(同供应商必配): + +``` +name = "{t2v}status", path = "/tasks/{{taskid}}", httpmethod = "GET", stream = "sync" +headers = {"Authorization": "Bearer {{apikey}}"} +response: +{ +{% if output.task_status == 'SUCCEEDED' %} + "video": "{{downloadfile2url(request, output.video_url)}}", + "usage": {{json.dumps(usage)}}, +{% elif output.task_status == 'FAILED' %} + "error": "{{output.code}}:{{output.message}}", +{% endif %} + "status": "{{output.task_status}}" +} +``` + +文生图同理:`"image": "{{downloadfile2url(request, output.results[0].url)}}"`。 + +## 3. 任务型 API(POST 建任务 + GET 查结果) + +适用:seedance / 即梦 / Vidu 等(`/contents/generations/tasks`)。 + +``` +提交: path = "/contents/generations/tasks", POST, stream = "async", chunk_match = "task_id" +headers = {"Content-Type": "application/json", "Authorization": "Bearer {{apikey}}"} +response: +{ +{% if id %} + "status": "CREATED", "taskid": "{{id}}" +{% else %} + "status": "FAILED", "error": "{{error.code}}.{{error.message}}" +{% endif %} +} + +查询: path = "/contents/generations/tasks/{{taskid}}", GET, stream = "sync" +response: +{ +{% if status=='succeeded' %} + "taskid": "{{id}}", "status": "SUCCEEDED", +{% set v=usage.update({'model': model, 'has_video':'no', 'resolution': resolution}) %} + "usage": {{json.dumps(usage)}}, + "video": "{{downloadfile2url(request, content.video_url)}}" +{% elif status == "failed" or status == 'expired' %} + "status": "FAILED", "error": "{{error.code}}.{{error.message}}" +{% else %} + "status": "{{status}}" +{% endif %} +} +``` + +注意 usage.update 里塞入计费过滤字段(has_video/resolution)——定价 filters 依赖这些值, +字段名和值必须与 pricing_data 的 filters 完全一致(最常见的计费失败根因)。 + +## 4. 同步功能型接口(stream=sync) + +适用:翻译 / neo4j / milvus 等。 + +``` +翻译: path = "/translate", POST +data = {"text": {{json.dumps(prompt)}}, "src_lang": "{{src_lang}}", "tgt_lang": "{{tgt_lang}}"} +response = {"content": {{json.dumps(data.translation)}}, "status": "SUCCEEDED"} +``` + +## 5. 特殊响应形态 + +音乐(MiniMax `/music_generation`): + +``` +data: {"model": "{{model}}", "prompt": {{json.dumps(prompt)}}, "lyrics": {{json.dumps(lyrics)}}, + "output_format": "url", "audio_setting": {"sample_rate": 44100, "format": "wav", "bitrate": 256000}} +response: +{% if base_resp.status_code == 0 %} + "status": "SUCCEEDED", "usage": {"count": 1}, + "audio": "{{downloadfile2url(request, data.audio)}}" +{% else %} + "status": "FAILED", "error": "{{base_resp.status_code}}:{{base_resp.status_msg}}" +{% endif %} +``` + +混元3D(`/v1/ai3d/query`,轮询 JobId):状态字段在 `Response.Status`(DONE/FAIL), +产物 `Response.ResultFile3Ds[]` 按 Type=='GLB' 取 `downloadfile2url(request, rzt.Url)`。 + +## 6. upapp 样例(供应商注册) + +``` +upapp: + id: 小写可读名(如 "ali-qwen"、"minimax"、"vidu") + name: 展示名(如 "阿里百炼"、"MiniMax") + baseurl: API 根地址(通义万相原生 = https://dashscope.aliyuncs.com/api/v1; + 兼容端点 = https://dashscope.aliyuncs.com/compatible-mode/v1) + ownerid: "0" + dynamic_func: 仅 AK/SK 签名类填(如 "volcengine_ark_sign"),Bearer 类留空 +``` + +## 7. uapiio 样例(输入字段定义) + +`input_fields` 两种格式(历史并存,新配置用数组格式): + +```json +// 数组格式(表单驱动) +[ + {"name": "prompt", "label": "提示词", "uitype": "text", "required": true}, + {"name": "image1_file", "label": "首帧图片", "uitype": "image", "required": true}, + {"name": "resolution", "label": "分辨率", "uitype": "code", + "data": [{"value": "720p"}, {"value": "1080p"}], "defaultvalue": "720p"}, + {"name": "duration", "label": "视频长度", "uitype": "int", "defaultvalue": 10} +] +// uitype 可选: text/textarea/image/video/audio/str/int/number/code(下拉)/switch +``` + +标准 io 可直接复用已有记录(按 ioid 关联):文本会话 `Is8l4TGkcZcqFSjbbeIK2`、 +asr `JaEJTylQmzGmyRiI1VfzO` 等——先查 uapiio 现有记录再决定是否新建。