28 KiB
| name | description | author | tags | ||||||
|---|---|---|---|---|---|---|---|---|---|
| auto-model-config | Given a webpage URL and model name, automatically generate SQL INSERT statements to configure the model's llmage and uapi entries in Sage's database. | Hermes Agent |
|
Auto Model Configuration Skill
Overview
Given a vendor pricing/API documentation webpage URL and a model name, automatically generate the complete SQL INSERT statements needed to configure the model in Sage's llmage (model management) and uapi (API gateway) tables.
Trigger Conditions
- User provides a webpage URL and a model name for automatic configuration
- User asks to add a new LLM model to Sage's configuration
- User wants to configure a new API endpoint for an existing or new provider
Required Context (ask user if missing)
- Webpage URL: The vendor's API documentation or pricing page
- Model name: The API model identifier (e.g.,
qwen-plus,gpt-4o) - Display name: Human-readable name in Chinese (e.g.,
千问Plus) - Provider: Which provider this belongs to (e.g.,
阿里百炼,OpenAI,智谱AI) - API Key: The user's API key for this provider (will be stored encrypted)
- Model category: text2text, text2image, text2speech, image2text, text2video, etc.
Architecture: The Tables
Configuration requires inserting into (or referencing existing entries in) these tables:
| Table | Purpose | Key ID Reference |
|---|---|---|
| llmcatelog | Model categories (文生文, 文生图, etc.) | llmcatelogid in llm_api_map |
| upapp | External system (base URL, app ID, auth_apiname) | upappid in llm |
| upappkey | API credentials (encrypted API key) | upappid foreign key |
| uapiio | Input/output field definitions | ioid in uapi |
| uapi | API endpoint (path, method, headers, data template) | upappid joins upapp directly |
| llm | Model definition (name, model, provider, owner) | — |
| llm_api_map | Per-ability config (apiname, query_apiname, ppid) | llmid → llm, llmcatelogid → llmcatelog |
Current uapi → upapp JOIN Pattern
upapp (id) ──1:N──> uapi (upappid)
│
└── a.upappid = b.id (direct join, NO apisetid intermediary)
uapi table columns: id, name, title, upappid, description, need_auth, stream, path, httpmethod, chunk_match, headers, params, data, response, ioid, callbackurl
upapp table columns: id, name, description, ownerid, apisetid, secretkey, baseurl, myappid, dynamic_func, auth_apiname
Note: uapiset table is 废弃 — empty, no longer used. uapi now links to upapp via upappid field directly. auth_apiname is on upapp table.
Step-by-Step Configuration
Step 1: Determine Model Category (llmcatelog)
Map the model type to an existing catalog ID:
| Catalog | ID | Description |
|---|---|---|
| 文生文 | t2t |
Text-to-text (chat, completion) |
| 文生图 | t2i |
Text-to-image |
| 语音识别 | asr |
Audio-to-text (ASR) |
| 文生视频 | t2v |
Text-to-video |
| 图生视频 | i2v |
Image-to-video |
| 参考生视频 | r2v |
Reference-to-video |
| 音乐生成 | music_gen |
Text-to-music |
| 3D生成 | 3d_gen |
Image-to-3D |
| 数字人 | digital_human |
Avatar/digital human |
| 视频工具 | video_tool |
Video tools/misc |
| 语言翻译 | translate |
Translation |
| AI搜索 | ai_search |
AI search |
| 文本分类 | text_cls |
Text classification |
| 图像理解 | vision |
Image-to-text (vision) |
CRITICAL: These are the ACTUAL IDs from the llmcatelog table in production. Do NOT invent IDs like text2text or text2image — they do not exist and will cause JOIN failures in get_llm().
If the user needs a new category, generate:
INSERT INTO llmcatelog (id, name, description, hfid, ioid)
VALUES ('<21-char-ID>', '<name>', '<description>', NULL, '<ioid_or_NULL>');
Step 2: Determine or Create External App (upapp)
Check if the provider already has an upapp entry. Common ones:
| upapp name | 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 |
If the provider is new, generate:
INSERT INTO upapp (id, name, description, ownerid, apisetid, auth_apiname, secretkey, baseurl, myappid, dynamic_func)
VALUES ('<21-char-ID>', '<app_name>', '<description>', '0', NULL, NULL, '', '<base_url>', '', NULL);
Note: auth_apiname is a column on upapp. Most providers don't need auth — set to NULL.
Step 3: Create API Key (upappkey)
INSERT INTO upappkey (id, upappid, ownerid, apikey, apiuser, apipasswd, orgid, is_first)
VALUES ('<21-char-ID>', '<upappid>', '<ownerid>', '<encrypted_apikey>', '', '', '0', '1');
Critical: The API key must be encrypted using ServerEnv.password_encode() before inserting. The SQL should note this:
-- NOTE: apikey must be encrypted with ServerEnv.password_encode() before inserting
-- Example: encrypted_key = password_encode('sk-your-api-key-here')
Step 4: Determine Input/Output Definition (uapiio)
Match the catalog type to the appropriate uapiio:
| uapiio name | ioid | Use case |
|---|---|---|
| 文本会话 | Is8l4TGkcZcqFSjbbeIK2 |
text2text (OpenAI-compatible) |
| 文本图像转文本 | PONIk8Br7ADTbzWQijJl- |
image2text (vision models) |
| t2i | p4K0-HTPKG3Ap--BZYqm5 |
text2image |
| tts | UJm-sp08Q31QgOJWk_2E2 |
text2speech |
| t2m | ZuIZvoKP996JJv2kccjl0 |
text2music |
| 万相t2v | QU8F6f6yfRCAGpToq1B9I |
text2video (Tongyi Wanxiang) |
| 文本媒体转文本 | t-ujII59ku45tIPcdXu4O |
multimodal input |
Step 5: Create API Endpoint (uapi)
This is the most complex part. The uapi record defines the actual HTTP request.
uapi table columns: id, name, title, upappid, description, need_auth, stream, path, httpmethod, chunk_match, headers, params, data, response, ioid, callbackurl
Key change: uapi now uses upappid (VARCHAR 32) to link to upapp.id directly.
For OpenAI-compatible text2text (most common):
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', -- apiname, referenced by llm_api_map.apiname
'模型对话',
'<upappid>', -- direct FK to upapp.id (NOT apisetid)
'<model_description>',
'0',
'stream', -- 'stream' for SSE, 'sync' for one-shot, 'async' for task submission
'/chat/completions',
'POST',
NULL,
'{"Authorization": "Bearer {{apikey}}", "Content-Type": "application/json"}',
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 %} {% if usage %}"usage":{"prompt_tokens":{{usage.prompt_tokens}},"completion_tokens":{{usage.completion_tokens}},"total_tokens":{{usage.total_tokens}}}{% endif %}}',
'<ioid>',
NULL
);
For async video generation models:
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>',
'<unique_apiname>', -- e.g., 't2v', 'i2v', 'ti2v'
'<api_title>',
'<upappid>',
'<description>',
'0',
'async',
'/video/generations',
'POST',
NULL,
'{"Authorization": "Bearer {{apikey}}", "Content-Type": "application/json"}',
NULL,
'{"model": "{{model}}", "input": {"prompt": {{json.dumps(prompt)}}{% if image_file %}, "image_url": "{{b64media2url(request, image_file)}}"{% endif %}}}',
'{"taskid":"{{task_id}}"}',
'<ioid>',
NULL
);
For async query API (separate uapi for polling):
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>', -- e.g., 't2vstatus', 'taskStatus'
'查询任务状态',
'<upappid>',
'',
'0',
'sync',
'/video/generations/{{taskid}}',
'GET',
NULL,
'{"Authorization": "Bearer {{apikey}}"}',
NULL,
NULL,
'{% if status == "SUCCEEDED" %}"status": "SUCCEEDED", "result_url": "{{output_video_url}}"{% elif status == "FAILED" %}"status": "FAILED"{% else %}"status": "PENDING"{% endif %}',
NULL,
NULL
);
Step 6: Create Model Entry (llm) + Ability Mapping (llm_api_map)
-- llm table: base model metadata
INSERT INTO llm (id, name, model, description, iconid, upappid, providerid, ownerid, enabled_date, expired_date)
VALUES (
'<21-char-ID>',
'<display_name>', -- e.g., '千问Plus'
'<api_model_name>', -- e.g., 'qwen-plus'
'<description>',
'<iconid>', -- e.g., 'qwen', 'openai', 'zhipu'
'<upappid>', -- e.g., 'ali-qwen'
'<providerid>', -- org ID of provider
'0',
'<today_date>',
'9999-12-31'
);
-- llm_api_map: per-ability config (one row per catalog + apiname combination)
INSERT INTO llm_api_map (id, llmid, llmcatelogid, apiname, query_apiname, query_period, ppid, isdefaultcatelog)
VALUES (
'<21-char-ID>',
'<llm_id_from_above>', -- must match llm.id
'<llmcatelogid>', -- e.g., 'text2text'
'<apiname>', -- e.g., 't2t', 't2i', 't2v'
'<query_apiname_or_empty>', -- async: status API name; sync/stream: NULL
<polling_period>, -- NULL for sync, 10-30 for async
NULL, -- ppid (pricing program), NULL if not priced yet
'1' -- isdefaultcatelog flag
);
ID Generation
IMPORTANT: Different tables have different ID length limits!
| Table | Field | Max Length |
|---|---|---|
llm |
id |
varchar(32) |
llm_api_map |
id, llmid, ppid |
varchar(21) |
pricing_program |
id |
varchar(32) |
pricing_program_timing |
id, ppid |
varchar(32) |
upapp |
id |
varchar(32) |
uapi |
id |
varchar(32) |
- For llm_api_map fields (id, llmid, ppid): Use
uuid.uuid4().hex[:21]— exactly 21 chars. IDs longer than 21 chars will be truncated or rejected, breakingget_llm()JOINs. - For other tables: Use
appPublic.uniqueID.getID()which returns 32-char nanoid. - .dspy files: Use
uuid()function (SQL-side generation). - Import path for standalone scripts:
from appPublic.jsonConfig import getConfig(NOTappPublic.getConfig). - Do NOT use
uuid.uuid4()full 32-char hex for llm_api_map — it won't fit in varchar(21).
Correct standalone script pattern:
from appPublic.jsonConfig import getConfig
from appPublic.uniqueID import getID
import asyncio
from sqlor.dbpools import DBPools
config = getConfig('.')
db = DBPools(config.databases)
dbname = list(config.databases.keys())[0]
async def main():
async with db.sqlorContext(dbname) as sor:
new_id = getID() # 32-char ID
# ... use new_id in SQL ...
asyncio.run(main())
Icon IDs (common values)
| Provider | iconid |
|---|---|
| 阿里百炼/千问 | qwen |
| OpenAI | openai |
| 智谱AI | zhipu |
| 火山方舟/豆包 | doubao |
| MiniMax | minimax |
| 百度 | baiducloud |
| 腾讯混元 | tengxinyuanbao |
| 阶跃星辰 | jieyuexingchen |
| 月之暗面/Kimi | moonshot |
Gemini |
|
| Anthropic | claude |
| 本地部署 | opencomputing |
Stream Modes
| Mode | Description | Use case |
|---|---|---|
stream |
SSE streaming response | Text generation, chat |
sync |
One-shot synchronous response | Translation, classification |
async |
Submit task + poll for results | Video generation, 3D, image gen |
False |
Synchronous non-streaming | Some older APIs |
llmage BufferedLLMs SQL Pattern (get_llm)
The core query in llmage/llmage/utils.py get_llm():
select a.id, a.name, a.model, a.providerid, a.description, a.iconid, a.upappid, a.ownerid, a.min_balance,
m.llmcatelogid, m.apiname, m.query_apiname, m.query_period, m.ppid,
e.ioid, e.stream, e.callbackurl, f.input_fields,
lc.name as catelogname
from llm a, llm_api_map m, llmcatelog lc, upapp c, uapi e, uapiio f
where a.id = m.llmid
and a.upappid = c.id
and c.id = e.upappid
and m.apiname = e.name
and e.ioid = f.id
and a.id = ${llmid}$
and a.expired_date > ${today}$
and a.enabled_date <= ${today}$
Key JOIN: c.id = e.upappid (upapp.id = uapi.upappid) — direct FK, no apisetid intermediary.
媒体文件模板函数(uapi data/response 中使用)
- 上传媒体:
{{b64media2url(request, media_file)}}— 将前台上传的 base64 媒体文件转为可访问的 URL,供供应商 API 使用。用于 image2text、image2video 等需要输入图片/文件的场景。 - 下传文件:
{{downloadfile2url(request, provider_url)}}— 将供应商返回的临时 URL(如 OSS 签名链接)下载并转为 Sage 平台自身的 URL。用于文生图、文生视频等返回文件 URL 的场景。图像/视频生成模型的 response 模板中必须用此函数包装供应商返回的 URL。
示例(文生图 response 模板):
{% for item in choice.message.content %}
{% if item.image %}
{"url": "{{downloadfile2url(request, item.image)}}"}
{% endif %}
{% endfor %}
示例(图生视频 data 模板):
"image_url": "{{b64media2url(request, image_file)}}"
Pitfalls
-
CRITICAL: llm_api_map ID fields are varchar(21), NOT varchar(32): The
llm_api_maptable has strict varchar(21) limits onid,llmid, andppidfields. If you generate 23-char or 32-char IDs (likegetID()which returns 32 chars), MySQL strict mode will reject the INSERT, and non-strict mode will silently truncate, breaking the JOIN inget_llm(). Always use exactly 21-char IDs for llm_api_map fields. Check schema first:-- llm_api_map schema (varchar(21) fields) `id` varchar(21) NOT NULL COMMENT '主键', `llmid` varchar(21) NOT NULL COMMENT '模型ID', `ppid` varchar(21) DEFAULT NULL COMMENT '定价项目ID',Correct pattern:
uuid.uuid4().hex[:21]to generate exactly 21 chars. -
CRITICAL: Use actual llmcatelog IDs, not invented names: The
llmcatelogtable has specific short IDs liket2t,t2i,t2v, NOT long names liketext2textortext2image. Using wrong IDs causesget_llm()JOIN to fail with'NoneType' object has no attribute 'ownerid'. Always verify against the actual table:SELECT id, name FROM llmcatelog;or parse the DB dump. -
NEVER use the legacy
httpapitable for new model endpoints: Thehttpapitable is deprecated/legacy. All new API endpoint configurations MUST go through theuapimodule (uapi + uapiio tables). Thellm_api_map.apinamereferencesuapi.name, and the full URL is constructed fromupapp.baseurl + uapi.path. Usinghttpapifor new models will not work with the currentget_llm()JOIN logic. -
Shared ioid pattern across providers: Multiple providers can share the same
uapiiorecord viaioid. Each provider needs its ownuapirow (with provider-specificupappid,data/responsetemplates), but theioidis shared:
Is8l4TGkcZcqFSjbbeIK2(文本会话) — all OpenAI-compatible t2t providerst-ujII59ku45tIPcdXu4O(文本媒体转文本) — multimodal (image+video+audio) inputPONIk8Br7ADTbzWQijJl-(文本图像转文本) — vision models
When adding a new provider that uses OpenAI-compatible API, just create a new uapi row with the same ioid and the provider's upappid. Do NOT duplicate uapiio entries.
- Use bugfix/execute_sql.dspy for live data queries: Instead of parsing database dumps, query live production data via
https://token.opencomputing.cn/bugfix/api/execute_sql.dspyusing thesworduser's Bearer token. This gives accurate, real-time data for existing model configs, pricing, and uapi entries:
import urllib.request, json
def execute_sql(sql, rows=200):
body = json.dumps({"sql": sql, "rows": rows}).encode()
req = urllib.request.Request("https://token.opencomputing.cn/bugfix/api/execute_sql.dspy",
data=body, headers={"Authorization": "Bearer <sword_key>", "Content-Type": "application/json"})
return json.loads(urllib.request.urlopen(req, timeout=30).read())
Use LIKE CONCAT('%%', 'keyword', '%%') instead of LIKE '%keyword%' to avoid Python format string conflicts.
-
Exclude already-existing models to avoid duplicates: Before generating SQL, check which models already exist in the system (parse DB dump or query API). For overlapping models, only delete records added on the current date (e.g.,
enabled_date = '2026-06-11') to avoid destroying working configurations. Example rollback pattern:-- For existing models, only delete today's additions DELETE FROM `llm` WHERE model = 'qwen3.5-plus' AND upappid = 'ali-qwen' AND enabled_date = '2026-06-11'; -- For new models, delete all DELETE FROM `llm` WHERE model = 'qwen3.7-plus' AND upappid = 'ali-qwen'; -
CRITICAL: Always check for PARTIAL existence across ALL tables: When a model was partially added in a prior session (e.g., llm + pricing_program exist but llm_api_map + pricing_program_timing are missing), you MUST query every related table before generating SQL. Generating INSERT for records that already exist causes
Duplicate entryerrors; generating SQL that assumes nothing exists creates broken cross-references. The correct query sequence (use bugfix/execute_sql.dspy):-- 1. llm (model definition) SELECT id, name, model, upappid, providerid FROM llm WHERE model LIKE CONCAT('%%', 'wan2.7-t2v', '%%') -- 2. pricing_program (pricing project) SELECT id, name, discount, pricing_spec FROM pricing_program WHERE name LIKE CONCAT('%%', 'wan2.7-t2v', '%%') -- 3. uapi (API endpoints - check if reusable) SELECT id, name, title, path, upappid FROM uapi WHERE upappid = '<upappid>' AND name = 't2v' -- 4. llm_api_map (ability mapping) SELECT * FROM llm_api_map WHERE llmid = '<llm_id_from_step1>' -- 5. pricing_program_timing (actual pricing data) SELECT * FROM pricing_program_timing WHERE ppid = '<pricing_program_id_from_step2>'Only generate INSERT for tables that returned zero rows. Existing records should be referenced by their actual IDs (e.g., existing llm.id for llm_api_map.llmid). This pattern is especially common when an earlier session's write_file was truncated or the user partially executed a multi-statement SQL.
-
Reuse existing upapp when possible: If the provider already exists (e.g.,
ali-qwenfor DashScope models), reuse the existingupappid. Only create new upapp entries for truly new providers. -
API access to token.opencomputing.cn returns 401: When querying existing models/config via the API (
/llm/list,/llm_api_map/list,/pricing_program/list), all endpoints return HTTP 401 even with valid Bearer tokens from~/test/app/token.yaml. Fallback: Parse the latest database dump at~/db/sage-YYYY-MM-DD.sqlusing Python regex to extract existing records. Example pattern:import re with open('/home/hermesai/db/sage-2026-06-01.sql', 'r', errors='ignore') as f: for line in f: if line.startswith('INSERT INTO `llm` VALUES'): matches = re.findall(r"\('([^']*)','([^']*)','([^']*)',...\)", line) # Parse and filter -
Verify ALL models exist on vendor pricing page before generating SQL: Some models listed on the model catalog page may NOT appear on the pricing page (e.g., qwen3-4b, qwen3-1.7b, qwen3-0.6b are free/open-source and have no billing info). Use
browser_consoleto search for each model ID on the pricing page. If a model returns "NOT FOUND", exclude it from the SQL generation — do not fabricate prices. -
Thinking vs non-thinking mode pricing for open-source models: Some Qwen open-source models (qwen3-235b-a22b, qwen3-32b, etc.) have different output prices: thinking mode output is charged at the higher "completion" rate, while non-thinking mode output is cheaper. The pricing system cannot distinguish modes at billing time. Decision: Use the thinking mode price (higher) as the default, since it covers the worst case. Document this choice in the model description.
-
Cached token price is typically 20% of input price: When a model supports context caching (e.g., qwen3.7-plus, qwen3.6-flash), the cached token price is approximately
input_price * 0.2. Verify this against the vendor pricing page — some models may have different cache discounts. The formula pattern:cache_price * cached / 1M + input_price * (prompt - cached) / 1M + output_price * completion / 1M -
OpenAI-compatible providers share uapi templates: Providers using the same OpenAI-compatible API format (DashScope, DeepSeek, OpenAI, Grok) can share the same uapi
t2ttemplate — just reference the existing upappid. -
API key encryption is mandatory: The
upappkey.apikeyfield stores encrypted values usingServerEnv.password_encode(). Never store plain text API keys in SQL. -
apiname must be unique within an upapp: The
uapitable hasnameunique within eachupappid. Ift2talready exists for the upapp, don't create a duplicate — just reference it in llm. -
Async models need TWO uapi entries: One for submitting the task (stream='async'), one for polling status (stream='sync'). The llm_api_map's
query_apinamefield references the status API name. BUT: status endpoints are shared per provider — DashScope'sGET /tasks/{task_id}works for ALL DashScope models regardless of which creation endpoint was used. Do NOT create duplicate status uapi records when one already exists for the upappid. Always check for existing status uapi before generating new ones. -
query_period is in seconds: Default 30 for async, 0 for sync/stream.
-
Prefer sync over async when provider supports both: DashScope official docs explicitly recommend sync ("一次请求即可获得结果,流程简单,推荐大多数场景使用"). Only use async for genuinely long-running tasks. If a provider offers both sync and async for the same operation, default to sync — it eliminates the need for status polling entirely and simplifies the uapi configuration (1 record instead of 2).
-
DashScope图像模型有两种API模式: 新模型(wan2.7-image-pro, qwen-image-2.0系列)使用SYNC模式的multimodal-generation端点; 旧模型(qwen-image-plus, qwen-image)使用ASYNC模式的text2image端点。判断方法: 查看官方文档,若注明"仅支持同步接口"则用sync模式(
wan2.7-image-sync或新建qwen-image-syncuapi),若注明"仅支持异步接口"则用async模式(复用现有t2i+t2istatusuapi)。注意: wan2.7-image-sync模板包含thinking_mode参数,qwen-image-sync不需要此参数。 -
ppid is optional: Leave as NULL if no pricing is configured yet.
-
uapiset table 废弃:
uapinow links toupappviaupappiddirectly. No uapiset INSERT needed.auth_apinameis onupapptable. -
Provider ID vs UpApp ID:
llm.provideridis an org ID (who owns/operates the model), whilellm.upappidis the external system ID (how to call the API). They are different fields. -
Multi-ability models: A model with multiple capabilities (e.g., t2v + i2v) has ONE llm row + multiple llm_api_map rows (one per catalog+apiname combo). Each llm_api_map gets its own unique ID.
-
New model requires uapi setup first: upapp → uapi → upappkey → llm + llm_api_map (in that order). NO uapiset.
-
Verify before executing: Always present the complete SQL to the user for review before suggesting execution. Confirm all IDs, URLs, and template strings are correct.
-
Pricing SQL is mandatory, not optional: Every model configuration MUST include
pricing_programandpricing_program_timingINSERTs. The model won't bill withoutllm_api_map.ppidpointing to a validpricing_program.id. Use Python script to guarantee ID consistency. -
ppid ID consistency:
pricing_program.id,pricing_program_timing.ppid, andllm_api_map.ppidMUST all be the same ID. Raw SQL cannot express this — always generate via Python script usinggetID()assigned to a variable, then substitute in all related records. -
Orchestrator delegation: When configuring multiple models, delegate to a sub-agent rather than doing it yourself. The user expects delegation, not direct execution.
-
uapi 通过 upappid 直接连接 upapp: uapi 表有
upappid字段(VARCHAR 32),直接指向upapp.id。不再使用apisetid作为中间连接。SQL JOIN 模式:uapi.upappid = upapp.id。
Script Template
Use templates/model-config-sql-generator.py as a starter. Copy it, fill in model-specific fields, and run with python3 to generate SQL. The template demonstrates the UUID consistency pattern (ppid = uuid.uuid4() reused across all records).
Working example: /home/hermesai/scripts/add_qwen_wan_models.py — qwen-image-2.0-pro + wan2.7-image-pro 完整配置脚本,包含定价+uapi+llm共7条SQL。
Output Format
- Model summary table: name, model, category, provider, stream mode
- Configuration checklist: What's reused vs. what's new
- Generate via Python script: ALWAYS use a Python script to generate the SQL (not raw SQL). The script uses
getID()to guaranteepricing_program.id = llm_api_map.ppid = pricing_program_timing.ppidconsistency. Script must be runnable withpython3 script.pyand output SQL to stdout.
DashScope/阿里百炼参考
references/dashscope-image-api.md— qwen-image-2.0-pro 和 wan2.7-image-pro 的定价、API端点、请求/响应格式、Sage配置复用关系references/qwen-text-model-pricing-2026-06.md— Qwen文生文全系列模型定价(2026-06-11提取,含商业API+开源模型+Coder+翻译)references/dashscope-wan27-image-api.md— wan2.7完整API参数(sync推荐/async路径不同/响应格式)、Sage配置要点references/existing-sage-config-lookup.md— 已有upapp/upappkey/llmcatelog/uapiio/iconid速查表(从生产dump提取,配置时优先复用已有条目)references/minimax-vendor-config.md— MiniMax供应商uapi/定价ppid/模型速查(含M3分段定价配置)references/qwen-image-api.md— qwen-image全系列API模式对照(sync vs async)、uapi模板差异、Sage配置模式
技能重叠说明
llm-api-config-from-url 与本技能高度重叠。差异在于:
auto-model-config: 侧重网页URL分析+完整SQL生成工作流(含定价Python脚本模板)llm-api-config-from-url: 侧重表结构速查+从API URL到配置的快速映射 两者未来应考虑合并。
Why Python Script (not raw SQL):
pricing_program.idMUST equalllm_api_map.ppidfor billing to work- IDs generated inline (e.g.,
getID()per line orUUID()) cannot be referenced across multiple INSERT statements - Python script generates one
getID()upfront, reuses it in all related records - Script also includes
pricing_programandpricing_program_timingINSERTs (raw SQL approach often misses these)
Script Template Pattern:
from appPublic.jsonConfig import getConfig
from appPublic.uniqueID import getID
config = getConfig('.')
db = DBPools(config.databases)
dbname = list(config.databases.keys())[0]
ppid = getID() # Same ID for pricing_program + all llm_api_map records (21 chars)
# Output SQL with {ppid} substituted everywhere
Working scripts:
/home/hermesai/scripts/add_qwen_wan_models.py— qwen-image-2.0-pro + wan2.7-image-pro (定价+uapi+llm共7条SQL)/home/hermesai/scripts/add_text_models.py— 8个text2text模型 (定价+llm共10条SQL)scripts/extract_existing_models.py— 从数据库dump提取现有模型配置(当API返回401时使用)