--- name: llm-api-config-from-url description: 根据大模型API的URL,快速生成Sage平台所需的完整数据库配置数据(upapp/upappkey/uapiio/uapi/llm/llm_api_map/pricing_program/pricing_program_timing),支持OpenAI兼容接口及异步任务型API。 author: Hermes Agent tags: [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(模型分类) ```sql 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(外部应用注册) ```sql 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模板**: ```sql INSERT INTO upapp (id, name, description, ownerid, apisetid, secretkey, baseurl, myappid, dynamic_func, auth_apiname) VALUES ('<21-char-ID>', '', '', '0', NULL, '', '', '', NULL, NULL); ``` ### 3. upappkey(API密钥) ```sql 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模板**: ```sql -- NOTE: apikey必须先用 ServerEnv.password_encode('sk-xxx') 加密 INSERT INTO upappkey (id, upappid, ownerid, apikey, apiuser, apipasswd, orgid, is_first) VALUES ('<21-char-ID>', '', '', '', '', '', '', '1'); ``` ### 4. uapiio(输入输出字段定义) ```sql 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字段完全一致的字典数组。每个字段必须包含完整的表单描述属性: ```json [ { "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 | 数字输入 | **完整示例**(图生视频): ```json [ {"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模板**: ```sql INSERT INTO uapiio (id, name, description, input_fields) VALUES ('<21-char-ID>', '', '', ''); ``` ### 5. uapi(API端点定义) ```sql 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模板**(最常见): ```sql 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', '模型对话', '', '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}}}}', '', NULL ); ``` **异步任务型uapi(提交+查询两个端点)**: ```sql -- 任务提交 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>', '', '', '<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(模型定义) ```sql 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(模型能力映射) ```sql 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(定价) ```sql 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中定义文件上传字段 ```json [ {"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。 **用法**: ```jinja2 "result_url": "{{downloadfile2url(request, output.video_url)}}" ``` **存储方式**: - 文本输出:直接存储在 `llmusage.ioinfo` 字段(JSON格式) - 文件输出(图片/视频/音频):存储为JSON文件,`llmusage.ioinfo` 保存webpath(如 `/llmio/182/138/79/46/xxx.json`) **ioinfo JSON结构示例**: ```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标准化)**: ```jinja2 {# 从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)**: ```jinja2 {# 同步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模板中的文件处理示例 **图生视频(异步)**: ```json { "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)**: ```json { "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 13. **文件变量传递request**:uapi.data模板中的文件处理函数必须传request作为第一参数 14. **文件字段名必须匹配**:uapi.data模板中的变量名必须与uapiio.input_fields中的name一致 15. **文件未上传时的错误**:如果uapiio定义了image_file但用户未上传,会抛出变量未定义错误 16. **URL文件大小限制**:不同API provider对URL方式引用文件有不同大小限制,需查阅文档 17. **生成文件本地化(强制)**:大模型生成的远端文件URL**必须**用 `downloadfile2url(request, output.file_url)` 下载并转为本服务器URL。response模板中直接调用:`"result_url": "{{downloadfile2url(request, output.video_url)}}"`。绝不可直接暴露远端URL给客户 18. **ioinfo存储**:大输入输出内容存储为JSON文件,llmusage.ioinfo保存webpath而非完整JSON 19. **llm.model严格按API文档**:llm表的model字段必须严格填写API文档中要求的模型名称/ID,不可自行编造、缩写或改写。不同供应商的模型命名规则不同(如OpenAI的`gpt-4o`、阿里的`qwen-max`),必须以官方API文档为准 20. **输入文件URL化**:用户输入的文件(上传或URL)统一转换为服务器URL传给大模型,不使用base64编码 21. **llm表字段顺序**:`id, name, model, description, llmcatelogid, iconid, upappid, apiname, providerid, ownerid, enabled_date, expired_date, query_apiname, query_period, min_balance`。注意没有stream字段 22. **ali-qwen providerid约定**:阿里百炼的providerid固定使用upappkey的id `6fadgewjraOyvxC_EkHou`,不是upappid也不是org id。所有已有ali-qwen模型都使用此值,新建必须一致 23. **response必须返回usage(强制)**:无论同步还是异步任务查询,uapi.response模板**必须**包含usage字段(prompt_tokens/completion_tokens/total_tokens)。同步响应从API的usage对象直接取;异步任务查询从任务结果的usage/metrics对象取。usage是计费的必要数据,缺失会导致无法正确扣费 24. **uapiio input_fields必须完整描述**:input_fields中每个字段必须使用与bricks Form字段一致的完整字典格式(name/label/uitype为必填,code类型还需data选项数组,可选required/defaultvalue/placeholder)。不可只写name和uitype 25. **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