feat: auto-api-pricing-config技能(文档→uapi接口配置+定价YAML)

从sage生产真实数据提炼: 协议模板库+定价模板库
- SKILL.md: 工作流+模板语法约定+定价硬规则
- references/uapi-templates.md: openai_compat流式/多模态/dashscope异步/任务轮询/特殊响应
- references/pricing-patterns.md: token/缓存/视频秒/按次/积分 五类模板+单位换算
This commit is contained in:
yumoqing 2026-09-02 16:45:21 +08:00
parent 567e4cc0cc
commit d8c6355514
3 changed files with 476 additions and 0 deletions

View File

@ -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与 chunkdelta
`{% 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/缓存/视频秒/按次/积分)+ 单位换算表

View File

@ -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 测一遍确认仍匹配

View File

@ -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/ti2tuser.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. 任务型 APIPOST 建任务 + 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 现有记录再决定是否新建。