25 KiB
Raw Blame History

name description author tags
llm-api-config-from-url 根据大模型API的URL,快速生成Sage平台所需的完整数据库配置数据(upapp/upappkey/uapiio/uapi/llm/llm_api_map/pricing_program/pricing_program_timing),支持OpenAI兼容接口及异步任务型API。 Hermes Agent
llmage
uapi
model-config
sql
sage
llm
api-config

LLM API配置技能(从URL到完整数据库配置)

触发条件

  • 用户提供一个API URL(如 https://dashscope.aliyuncs.com/compatible-mode/v1),要求配置大模型
  • 用户需要为新的LLM供应商或模型在Sage平台中注册
  • 用户询问如何将某个外部LLM API接入Sage

概述

Sage平台通过 llmage(模型业务层) + uapi(API网关层) 协作来接入外部LLM API。配置一条API需要向8张表写入数据,遵循严格的顺序和ID关联规则。

表依赖关系(配置顺序)

1. llmcatelog      ← 模型分类(通常已存在,直接引用)
2. upapp           ← 外部应用注册(base URL、认证方式)
3. upappkey        ← API密钥(加密存储)
4. uapiio          ← 输入输出字段定义(UI表单schema)
5. uapi            ← API端点定义(请求路径、方法、headers、data/response模板)
6. llm             ← 模型元数据(名称、model名、关联upapp)
7. llm_api_map     ← 模型能力映射(关联catalog + apiname + 定价)
8. pricing_program / pricing_program_timing ← 定价规则

核心原则: pricing_program.id = llm_api_map.ppid = pricing_program_timing.ppid 必须是同一个ID。

各表完整结构

1. llmcatelog(模型分类)

CREATE TABLE `llmcatelog` (
  `id` varchar(32) NOT NULL,
  `name` varchar(100) DEFAULT NULL COMMENT '分类名',
  `description` longtext DEFAULT NULL,
  `hfid` varchar(32) DEFAULT NULL,
  `ioid` varchar(32) DEFAULT NULL COMMENT '关联的uapiio',
  PRIMARY KEY (`id`)
);

已有分类ID(直接复用,无需新建):

分类 ID 对应uapiio
文生文 text2text Is8l4TGkcZcqFSjbbeIK2(文本会话)
文生图 text2image p4K0-HTPKG3Ap--BZYqm5(t2i)
文生语音 text2speech UJm-sp08Q31QgOJWk_2E2(tts)
图像理解 image2text PONIk8Br7ADTbzWQijJl-(文本图像转文本)
文生视频 -i2ET0YkhfVQdHONfk9pX QU8F6f6yfRCAGpToq1B9I(万相t2v)
图生视频 RdsO6pXgXcUTvUj819-7X 21QAi9ZUgs8iEzJ1czGEX(hailuo-ti2v)
参考生视频 fHrfsOnAFCz53DAILMO7G —
音乐生成 HaRXiNCaAACurZsmEqpsU ZuIZvoKP996JJv2kccjl0(t2m)
语音识别 audio2text 0kO4bWFZ_D0gkc9zj237L(index-tts)
3D生成 s6-nhQtEvDKxG_qDPWwT7 —
数字人 czKvk-clQTRLS2KVddSWo 5cpgFUC-zxXg79b_rlkh8(万相数字人)
视频工具 sRmpG8draTM-tsbO5nMJO —
语言翻译 t7sUuj8BCnsD762PwMUKM —
AI搜索 9_P5y-qiQzQASacTVk2Lq —
文本分类 Rqj-QBj1v4560l-FPCrIU —
文本媒体转文本 — t-ujII59ku45tIPcdXu4O

2. upapp(外部应用注册)

CREATE TABLE `upapp` (
  `id` varchar(32) NOT NULL,
  `name` varchar(200) DEFAULT NULL COMMENT '上位应用名',
  `description` longtext DEFAULT '0',
  `ownerid` varchar(32) DEFAULT NULL,
  `apisetid` varchar(32) DEFAULT NULL,
  `secretkey` varchar(255) DEFAULT NULL,
  `baseurl` varchar(500) DEFAULT NULL COMMENT '基础URL',
  `myappid` varchar(100) DEFAULT NULL,
  `dynamic_func` longtext DEFAULT NULL,
  `auth_apiname` varchar(100) DEFAULT NULL COMMENT '认证API名',
  PRIMARY KEY (`id`)
);

已有upapp(优先复用):

供应商 upappid baseurl
阿里百炼 ali-qwen https://dashscope.aliyuncs.com/compatible-mode/v1
OpenAI 4f4VUCUb4qThwdRroATF7 https://api.openai.com/v1
智谱AI ESX0csV3pd9P_U2cLODwA https://open.bigmodel.cn/api/paas/v4
火山方舟 huoshanfangzhou https://ark.cn-beijing.volces.com/api/v3
千帆大模型 qianfan https://qianfan.baidubce.com/v2
深度求索 deepseek https://api.deepseek.com
MiniMax minimax https://api.minimax.chat/v1
通义万象 tongyi-wan https://dashscope.aliyuncs.com/api/v1
Grok Fc8ElDTJKGG9I0gCTL8eI https://api.x.ai/v1
海螺 hailuo https://api.minimaxi.com/v1
vidu vidu https://api.vidu.cn
stepfun stepfun https://api.stepfun.com/v1
wetokenai wetokenai https://api.wetokenai.com
百度 a6su6IbB0yHDW4gc2ncfZ 百度API

新建upapp SQL模板:

INSERT INTO upapp (id, name, description, ownerid, apisetid, secretkey, baseurl, myappid, dynamic_func, auth_apiname)
VALUES ('<21-char-ID>', '<app_name>', '<description>', '0', NULL, '', '<base_url>', '', NULL, NULL);

3. upappkey(API密钥)

CREATE TABLE `upappkey` (
  `id` varchar(32) NOT NULL,
  `upappid` varchar(32) DEFAULT NULL,
  `ownerid` varchar(32) DEFAULT '0',
  `apikey` varchar(4000) DEFAULT '0' COMMENT '加密后的API密钥',
  `apiuser` varchar(100) DEFAULT NULL,
  `apipasswd` varchar(100) DEFAULT NULL,
  `orgid` varchar(32) DEFAULT NULL,
  `is_first` varchar(1) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

关键: apikey 必须用 ServerEnv.password_encode() 加密后存储,绝不可明文。

SQL模板:

-- NOTE: apikey必须先用 ServerEnv.password_encode('sk-xxx') 加密
INSERT INTO upappkey (id, upappid, ownerid, apikey, apiuser, apipasswd, orgid, is_first)
VALUES ('<21-char-ID>', '<upappid>', '<ownerid>', '<encrypted_apikey>', '', '', '<orgid_or_0>', '1');

4. uapiio(输入输出字段定义)

CREATE TABLE `uapiio` (
  `id` varchar(32) NOT NULL,
  `name` varchar(100) DEFAULT NULL COMMENT '类型名',
  `description` longtext DEFAULT NULL,
  `input_fields` longtext DEFAULT NULL COMMENT 'JSON数组,定义UI表单字段',
  PRIMARY KEY (`id`)
);

input_fields格式:与bricks Form字段完全一致的字典数组。每个字段必须包含完整的表单描述属性:

[
  {
    "name": "字段名(与uapi.data模板变量名一致)",
    "label": "前端显示标签",
    "uitype": "text|textarea|image|audio|video|code|number|date|str",
    "required": true,
    "defaultvalue": "默认值(可选)",
    "data": [{"value": "v1", "text": "显示文本"}],
    "placeholder": "输入提示(可选)"
  }
]

各uitype必填属性:

uitype 必填属性 说明
text/str/textarea name, label, uitype 文本输入,textarea多行
image/audio/video name, label, uitype 文件上传控件
code name, label, uitype, data 下拉选择,data提供选项数组
number name, label, uitype 数字输入

完整示例(图生视频):

[
  {"name": "prompt", "label": "提示词", "uitype": "textarea", "required": true, "placeholder": "描述你想生成的视频内容"},
  {"name": "image_file", "label": "首帧图片", "uitype": "image", "required": true},
  {"name": "image_file1", "label": "尾帧图片", "uitype": "image"},
  {"name": "duration", "label": "时长(秒)", "uitype": "code", "data": [{"value": "5", "text": "5秒"}, {"value": "10", "text": "10秒"}], "defaultvalue": "5"},
  {"name": "resolution", "label": "分辨率", "uitype": "code", "data": [{"value": "720p", "text": "720P"}, {"value": "1080p", "text": "1080P"}], "defaultvalue": "720p"}
]

常用uapiio:

名称 ioid 适用场景
文本会话 Is8l4TGkcZcqFSjbbeIK2 text2text
t2i p4K0-HTPKG3Ap--BZYqm5 text2image
tts UJm-sp08Q31QgOJWk_2E2 text2speech
文本图像转文本 PONIk8Br7ADTbzWQijJl- image2text (vision)
t2m ZuIZvoKP996JJv2kccjl0 text2music
万相t2v QU8F6f6yfRCAGpToq1B9I text2video
hailuo-ti2v 21QAi9ZUgs8iEzJ1czGEX image2video
万相数字人 5cpgFUC-zxXg79b_rlkh8 avatar
index-tts 0kO4bWFZ_D0gkc9zj237L index-tts

新建uapiio SQL模板:

INSERT INTO uapiio (id, name, description, input_fields)
VALUES ('<21-char-ID>', '<name>', '<description>', '<JSON_escaped_input_fields>');

5. uapi(API端点定义)

CREATE TABLE `uapi` (
  `id` varchar(32) NOT NULL,
  `name` varchar(100) DEFAULT NULL COMMENT 'API名(代码中引用)',
  `title` varchar(200) DEFAULT NULL COMMENT '显示名',
  `upappid` varchar(32) DEFAULT NULL COMMENT '关联upapp.id',
  `description` longtext DEFAULT NULL,
  `need_auth` varchar(1) DEFAULT NULL,
  `stream` varchar(10) DEFAULT NULL COMMENT 'stream/sync/async/False',
  `path` varchar(500) DEFAULT NULL COMMENT '请求路径',
  `httpmethod` varchar(10) DEFAULT NULL COMMENT 'GET/POST/PUT',
  `chunk_match` longtext DEFAULT NULL,
  `headers` longtext DEFAULT NULL COMMENT 'JSON: 请求头模板',
  `params` longtext DEFAULT NULL COMMENT 'JSON: URL参数模板',
  `data` longtext DEFAULT NULL COMMENT 'Jinja2: 请求体模板',
  `response` longtext DEFAULT NULL COMMENT 'Jinja2: 响应解析模板',
  `ioid` varchar(32) DEFAULT NULL COMMENT '关联uapiio.id',
  `callbackurl` varchar(500) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

stream模式:

值 含义 适用场景
stream SSE流式响应 文本对话/生成
sync 同步一次性响应 翻译/分类/图片生成(同步)
async 异步任务提交+轮询 视频生成/3D生成等耗时任务
False 同步非流式 某些老式API

OpenAI兼容 text2text uapi模板(最常见):

INSERT INTO uapi (id, name, title, upappid, description, need_auth, stream, path, httpmethod, chunk_match, headers, params, data, response, ioid, callbackurl)
VALUES (
  '<21-char-ID>',
  't2t',
  '模型对话',
  '<upappid>',
  'OpenAI兼容对话接口',
  '0',
  'stream',
  '/chat/completions',
  'POST',
  NULL,
  '{"Content-Type": "application/json", "Authorization": "Bearer {{apikey}}"}',
  NULL,
  '{"model": "{{model}}", "stream_options": {"include_usage": true}, "messages": [{% if sys_prompt %}{"role": "system", "content": {{json.dumps(sys_prompt, ensure_ascii=False)}}},{% endif %}{"role": "user", "content": {{json.dumps(prompt, ensure_ascii=False)}}}]}',
  '{"model": "{{model}}", {% if object == "chat.completion" %}"content":{{json.dumps(choices[0].message.content, ensure_ascii=False)}},{% else %}"content":{{json.dumps(choices[0].delta.content, ensure_ascii=False)}},{% endif %} "usage":{"prompt_tokens":{{usage.prompt_tokens}},"completion_tokens":{{usage.completion_tokens}},"total_tokens":{{usage.total_tokens}}}}',
  '<ioid>',
  NULL
);

异步任务型uapi(提交+查询两个端点):

-- 任务提交
INSERT INTO uapi (id, name, title, upappid, description, need_auth, stream, path, httpmethod, chunk_match, headers, params, data, response, ioid, callbackurl)
VALUES ('<21-char-ID>', '<apiname>', '<title>', '<upappid>', '<desc>', '0', 'async', '<path>', 'POST', NULL, '{"Authorization": "Bearer {{apikey}}", "Content-Type": "application/json"}', NULL, '<data_template>', '{"taskid":"{{task_id}}"}', '<ioid>', NULL);

-- 状态查询(response必须返回usage + 文件URL用downloadfile2url转本地 + status统一映射)
INSERT INTO uapi (id, name, title, upappid, description, need_auth, stream, path, httpmethod, chunk_match, headers, params, data, response, ioid, callbackurl)
VALUES ('<21-char-ID>', '<status_apiname>', '查询任务状态', '<upappid>', '', '0', 'sync', '<status_path>', 'GET', NULL, '{"Authorization": "Bearer {{apikey}}"}', NULL, NULL, '{% if status in ["SUCCEEDED","succeeded","success","COMPLETE","completed"] %}"status": "SUCCEEDED", "result_url": "{{downloadfile2url(request, output_url)}}", "usage":{"prompt_tokens":{{usage.input_tokens|default(0)}},"completion_tokens":{{usage.output_tokens|default(0)}},"total_tokens":{{usage.total_tokens|default(0)}}}{% elif status in ["FAILED","failed","error","ERROR"] %}"status": "FAILED"{% elif status in ["RUNNING","running","processing","IN_PROGRESS","in_progress"] %}"status": "RUNNING"{% elif status in ["CREATED","created","queued","QUEUED"] %}"status": "CREATED"{% else %}"status": "PENDING"{% endif %}', NULL, NULL);

6. llm(模型定义)

CREATE TABLE `llm` (
  `id` varchar(32) NOT NULL,
  `name` varchar(100) DEFAULT NULL COMMENT '显示名',
  `model` varchar(100) DEFAULT NULL COMMENT 'API model名',
  `description` longtext DEFAULT NULL,
  `llmcatelogid` varchar(32) DEFAULT NULL COMMENT '关联llmcatelog.id',
  `iconid` varchar(32) DEFAULT NULL COMMENT '图标ID',
  `upappid` varchar(32) DEFAULT NULL COMMENT '关联upapp.id',
  `apiname` varchar(100) DEFAULT NULL COMMENT '关联uapi.name',
  `providerid` varchar(32) DEFAULT NULL COMMENT '供应商org ID',
  `ownerid` varchar(32) DEFAULT NULL,
  `enabled_date` date DEFAULT NULL,
  `expired_date` date DEFAULT NULL,
  `query_apiname` varchar(100) DEFAULT NULL COMMENT '异步结果查询API名',
  `query_period` int(11) DEFAULT NULL COMMENT '查询间隔(秒)',
  `min_balance` double(18,2) DEFAULT 10.00,
  PRIMARY KEY (`id`)
);

注意: llm表没有stream字段,流式/异步控制由uapi.stream和llm_api_map.query_apiname决定。

7. llm_api_map(模型能力映射)

CREATE TABLE `llm_api_map` (
  `id` varchar(32) NOT NULL,
  `llmid` varchar(32) NOT NULL COMMENT '关联llm.id',
  `llmcatelogid` varchar(32) NOT NULL COMMENT '关联llmcatelog.id',
  `apiname` varchar(100) NOT NULL COMMENT '关联uapi.name',
  `query_apiname` varchar(100) DEFAULT NULL COMMENT '异步轮询API名',
  `query_period` int(11) DEFAULT NULL COMMENT '轮询间隔(秒)',
  `ppid` varchar(32) DEFAULT NULL COMMENT '关联pricing_program.id',
  `isdefaultcatelog` varchar(1) DEFAULT NULL COMMENT '是否默认分类',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_llm_api_catelog` (`llmid`,`llmcatelogid`)
);

注意: 所有ID字段均为varchar(32),不是21。使用 getID() 生成的nanoid为21字符,varchar(32)足够容纳。

8. pricing_program + pricing_program_timing(定价)

CREATE TABLE `pricing_program` (
  `id` varchar(32) NOT NULL,
  `name` varchar(256) DEFAULT NULL COMMENT '定价项目名',
  `ownerid` varchar(32) DEFAULT NULL,
  `providerid` varchar(32) DEFAULT NULL,
  `pricing_belong` varchar(32) DEFAULT NULL COMMENT 'provider/output',
  `discount` double(18,3) DEFAULT NULL,
  `description` longtext DEFAULT NULL,
  `pricing_spec` longtext DEFAULT NULL COMMENT 'YAML: 规格定义',
  PRIMARY KEY (`id`)
);

CREATE TABLE `pricing_program_timing` (
  `id` varchar(32) NOT NULL,
  `ppid` varchar(32) DEFAULT NULL COMMENT 'pricing_program.id',
  `name` varchar(256) DEFAULT NULL,
  `pricing_data` longtext DEFAULT NULL COMMENT 'YAML: 定价规则',
  `enabled_date` date DEFAULT NULL,
  `expired_date` date DEFAULT '9999-12-31',
  PRIMARY KEY (`id`)
);

执行步骤(给定API URL后)

Step 1: 分析URL,识别供应商

  • 从URL提取baseurl(如 https://dashscope.aliyuncs.com/compatible-mode/v1 → 阿里百炼)
  • 查已有upapp表,如果供应商已存在 → 复用upappid
  • 如果新供应商 → 新建upapp

Step 2: 确定模型分类

  • 根据API功能(文本/图片/视频/语音)匹配llmcatelog
  • 多数情况直接用已有分类

Step 3: 确定uapiio

  • 根据分类选择对应uapiio
  • OpenAI兼容text2text → Is8l4TGkcZcqFSjbbeIK2(文本会话)

Step 4: 确定uapi

  • OpenAI兼容API → 复用已有 t2t uapi(同一个upapp下name唯一)
  • 自定义API → 新建uapi记录
  • 异步API → 需要两个uapi(提交+查询)

Step 5: 生成ID

  • 使用 appPublic.uniqueID.getID() 生成21-char nanoid
  • 关键: ppid 必须在Python中生成一次,然后在 pricing_program、pricing_program_timing、llm_api_map 三处复用

Step 6: 生成Python脚本

生成可运行的Python脚本(参考 templates/config-generator.py),输出完整SQL。

ID生成规则

  • 所有ID: 21-char nanoid(通过 getID() 生成),VARCHAR(32)字段足够容纳
  • ppid: 在Python中 ppid = getID() 一次,然后三处复用
  • llm id: 每个模型独立ID
  • llm_api_map id: 每个能力映射独立ID(一个模型多个能力=多行)
  • uapi id: 每个API端点独立ID

参考文件

  • references/uapiio-patterns.md — 从数据库提取的uapiio定义、uitype类型、ioinfo存储格式
  • references/ali-qwen-config.md — 阿里百炼已有配置(upapp/upappkey/uapi/providerid复用值)

输出格式

  1. 模型摘要表: 名称、model、分类、供应商、stream模式
  2. 复用vs新建清单: 哪些表记录复用已有、哪些需要新建
  3. Python配置脚本: 可运行的脚本,输出完整SQL
  4. 执行说明: 用户需手动执行加密API密钥和SQL

Pitfalls

  1. upappid复用优先: 同一供应商的多个模型共享一个upapp,不要重复创建
  2. uapi name唯一性: 同一upapp下uapi.name必须唯一。OpenAI兼容接口通常复用已有 t2t
  3. API密钥必须加密: 使用 ServerEnv.password_encode(),绝不明文
  4. ppid三处一致: pricing_program.id = pricing_program_timing.ppid = llm_api_map.ppid
  5. 异步模型需要两个uapi: 提交(stream='async') + 查询(stream='sync'),llm_api_map.query_apiname指向查询API
  6. llm.stream vs uapi.stream: llm.stream控制推理模式('async'/True/False),uapi.stream控制HTTP调用方式('stream'/'sync'/'async')。两者不同,不要混淆
  7. uapi通过upappid直连upapp: 不再使用apisetid中间表
  8. 多能力模型: 一个llm + 多个llm_api_map(每行对应一个能力+分类组合)
  9. ownerid默认为'0': 除非明确指定属主
  10. providerid是org ID: 指向供应商的机构ID,不是upappid
  11. 必须生成Python脚本: 不可直接输出原始SQL,因为ID一致性无法在纯SQL中保证
  12. pricing是必选项: 每个模型必须配定价,否则无法计费

文件处理机制

文件上传流程

  1. 前端上传:用户通过uapiio定义的字段(uitype: "image"/"audio"/"video"/"file")上传文件,或直接输入URL
  2. 服务器存储:文件自动保存到服务器指定目录
  3. API请求转换:无论用户输入URL还是上传文件,统一转换为服务器URL格式传给大模型。不使用base64编码

b64media2url函数(已弃用)

⚠️ 旧机制:早期使用 b64media2url(request, filename) 将文件转为base64 data URL。现已改为统一使用URL方式。输入文件和用户输入的URL都转换为服务器URL直接传给大模型,不做base64编码。

uapiio中定义文件上传字段

[
  {"name": "prompt", "label": "提示词", "uitype": "text", "required": true},
  {"name": "image_file", "label": "首帧图片", "uitype": "image"},
  {"name": "image_file1", "label": "尾帧图片", "uitype": "image"},
  {"name": "audio_file", "label": "配音文件", "uitype": "audio"},
  {"name": "video_file", "label": "输入视频", "uitype": "video"}
]

生成文件处理(模型输出)

本地化机制:大模型API返回的生成文件(图片/视频/音频)通常是远端URL。使用 downloadfile2url(request, output.video_url) 将远端文件下载到本服务器,转换为本服务器URL。用户直接从本地下载文件,不暴露远端URL。

用法:

"result_url": "{{downloadfile2url(request, output.video_url)}}"

存储方式:

  • 文本输出:直接存储在 llmusage.ioinfo 字段(JSON格式)
  • 文件输出(图片/视频/音频):存储为JSON文件,llmusage.ioinfo 保存webpath(如 /llmio/182/138/79/46/xxx.json)

ioinfo JSON结构示例:

{
  "input": {"model": "wan2.1", "prompt": "...", "image_url": "https://server.example.com/llmio/uploads/xxx.png"},
  "output": [
    {"model": "wan2.1", "content": "", "finish": "0", "llmusageid": "xxx"},
    {"model": "wan2.1", "content": "https://cdn.example.com/generated-video.mp4", "finish": "1", "usage": {...}, "llmusageid": "xxx"}
  ]
}

异步模型response模板提取生成文件URL(必须包含usage + downloadfile2url + status标准化):

{# 从API响应中提取状态(标准化)、结果URL(转本地)和usage #}
{
  "taskid": "{{task_id}}",
{% if state in ['SUCCEEDED','succeeded','success','COMPLETE','completed'] %}
  "status": "SUCCEEDED",
  "result_url": "{{downloadfile2url(request, output_video_url)}}",
  "usage":{"prompt_tokens":{{usage.input_tokens|default(0)}},"completion_tokens":{{usage.output_tokens|default(0)}},"total_tokens":{{usage.total_tokens|default(0)}}}
{% elif state in ['FAILED','failed','error','ERROR'] %}
  "status": "FAILED"
{% elif state in ['RUNNING','running','processing','IN_PROGRESS','in_progress'] %}
  "status": "RUNNING"
{% elif state in ['CREATED','created','queued','QUEUED'] %}
  "status": "CREATED"
{% else %}
  "status": "PENDING"
{% endif %}
}

同步模型response模板(必须包含usage):

{# 同步API响应:内容 + usage(必须) + 文件URL用downloadfile2url转本地 #}
{
  "model": "{{model}}",
  "content": {{json.dumps(output_text, ensure_ascii=False)}},
  "usage":{"prompt_tokens":{{usage.prompt_tokens}},"completion_tokens":{{usage.completion_tokens}},"total_tokens":{{usage.total_tokens}}}
}

uapi.data模板中的文件处理示例

图生视频(异步):

{
  "model": "{{model or 'viduq2'}}",
  "payload": "i2v",
  "images": ["{{image_file1_url}}", "{{image_file2_url}}"],
  "duration": {{duration or 10}},
  "prompt": {{json.dumps(prompt, ensure_ascii=False)}},
  "resolution": "{{resolution or '1080p'}}"
}

图像理解(vision):

{
  "model": "{{model}}",
  "messages": [
    {"role": "user", "content": [
      {"type": "text", "text": {{json.dumps(prompt, ensure_ascii=False)}}},
      {"type": "image_url", "image_url": {"url": "{{image_file_url}}"}}
    ]}
  ]
}

文件处理Pitfalls

  1. 文件变量传递request:uapi.data模板中的文件处理函数必须传request作为第一参数
  2. 文件字段名必须匹配:uapi.data模板中的变量名必须与uapiio.input_fields中的name一致
  3. 文件未上传时的错误:如果uapiio定义了image_file但用户未上传,会抛出变量未定义错误
  4. URL文件大小限制:不同API provider对URL方式引用文件有不同大小限制,需查阅文档
  5. 生成文件本地化(强制):大模型生成的远端文件URL必须用 downloadfile2url(request, output.file_url) 下载并转为本服务器URL。response模板中直接调用:"result_url": "{{downloadfile2url(request, output.video_url)}}"。绝不可直接暴露远端URL给客户
  6. ioinfo存储:大输入输出内容存储为JSON文件,llmusage.ioinfo保存webpath而非完整JSON
  7. llm.model严格按API文档:llm表的model字段必须严格填写API文档中要求的模型名称/ID,不可自行编造、缩写或改写。不同供应商的模型命名规则不同(如OpenAI的gpt-4o、阿里的qwen-max),必须以官方API文档为准
  8. 输入文件URL化:用户输入的文件(上传或URL)统一转换为服务器URL传给大模型,不使用base64编码
  9. llm表字段顺序:id, name, model, description, llmcatelogid, iconid, upappid, apiname, providerid, ownerid, enabled_date, expired_date, query_apiname, query_period, min_balance。注意没有stream字段
  10. ali-qwen providerid约定:阿里百炼的providerid固定使用upappkey的id 6fadgewjraOyvxC_EkHou,不是upappid也不是org id。所有已有ali-qwen模型都使用此值,新建必须一致
  11. response必须返回usage(强制):无论同步还是异步任务查询,uapi.response模板必须包含usage字段(prompt_tokens/completion_tokens/total_tokens)。同步响应从API的usage对象直接取;异步任务查询从任务结果的usage/metrics对象取。usage是计费的必要数据,缺失会导致无法正确扣费
  12. uapiio input_fields必须完整描述:input_fields中每个字段必须使用与bricks Form字段一致的完整字典格式(name/label/uitype为必填,code类型还需data选项数组,可选required/defaultvalue/placeholder)。不可只写name和uitype
  13. status必须标准化为5种值(强制):不同供应商返回的任务状态五花八门(如SUCCEEDED/succeeded/success/COMPLETE、RUNNING/running/processing/IN_PROGRESS、CREATED/created/queued、FAILED/failed/error等),uapi.response模板中必须用Jinja2 in 判断将供应商原始状态映射为以下5种标准值之一:
    标准值 含义 供应商常见原始值
    CREATED 任务已创建,尚未开始 CREATED, created, queued, QUEUED
    PENDING 排队中/等待中(默认兜底) PENDING, pending, waiting, 以及所有未识别的状态
    RUNNING 正在处理中 RUNNING, running, processing, IN_PROGRESS, in_progress
    SUCCEEDED 成功完成 SUCCEEDED, succeeded, success, COMPLETE, completed
    FAILED 失败 FAILED, failed, error, ERROR

兜底规则:{% else %}"status": "PENDING"{% endif %},任何未识别的状态都归为PENDING