28 KiB
Raw Blame History

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
llmage
uapi
model-config
sql
sage
llm

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, breaking get_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 (NOT appPublic.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
Google 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

  1. CRITICAL: llm_api_map ID fields are varchar(21), NOT varchar(32): The llm_api_map table has strict varchar(21) limits on id, llmid, and ppid fields. If you generate 23-char or 32-char IDs (like getID() which returns 32 chars), MySQL strict mode will reject the INSERT, and non-strict mode will silently truncate, breaking the JOIN in get_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.

  2. CRITICAL: Use actual llmcatelog IDs, not invented names: The llmcatelog table has specific short IDs like t2t, t2i, t2v, NOT long names like text2text or text2image. Using wrong IDs causes get_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.

  3. NEVER use the legacy httpapi table for new model endpoints: The httpapi table is deprecated/legacy. All new API endpoint configurations MUST go through the uapi module (uapi + uapiio tables). The llm_api_map.apiname references uapi.name, and the full URL is constructed from upapp.baseurl + uapi.path. Using httpapi for new models will not work with the current get_llm() JOIN logic.

  4. Shared ioid pattern across providers: Multiple providers can share the same uapiio record via ioid. Each provider needs its own uapi row (with provider-specific upappid, data/response templates), but the ioid is shared:

  • Is8l4TGkcZcqFSjbbeIK2 (文本会话) — all OpenAI-compatible t2t providers
  • t-ujII59ku45tIPcdXu4O (文本媒体转文本) — multimodal (image+video+audio) input
  • PONIk8Br7ADTbzWQijJl- (文本图像转文本) — 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.

  1. 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.dspy using the sword user'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.

  1. 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';
    
  2. 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 entry errors; 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.

  3. Reuse existing upapp when possible: If the provider already exists (e.g., ali-qwen for DashScope models), reuse the existing upappid. Only create new upapp entries for truly new providers.

  4. 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.sql using 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
    
  5. 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_console to 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.

  6. 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.

  7. 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

  8. OpenAI-compatible providers share uapi templates: Providers using the same OpenAI-compatible API format (DashScope, DeepSeek, OpenAI, Grok) can share the same uapi t2t template — just reference the existing upappid.

  9. API key encryption is mandatory: The upappkey.apikey field stores encrypted values using ServerEnv.password_encode(). Never store plain text API keys in SQL.

  10. apiname must be unique within an upapp: The uapi table has name unique within each upappid. If t2t already exists for the upapp, don't create a duplicate — just reference it in llm.

  11. 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_apiname field references the status API name. BUT: status endpoints are shared per provider — DashScope's GET /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.

  12. query_period is in seconds: Default 30 for async, 0 for sync/stream.

  13. 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).

  14. 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-sync uapi),若注明"仅支持异步接口"则用async模式(复用现有t2i+t2istatus uapi)。注意: wan2.7-image-sync模板包含thinking_mode参数,qwen-image-sync不需要此参数。

  15. ppid is optional: Leave as NULL if no pricing is configured yet.

  16. uapiset table 废弃: uapi now links to upapp via upappid directly. No uapiset INSERT needed. auth_apiname is on upapp table.

  17. Provider ID vs UpApp ID: llm.providerid is an org ID (who owns/operates the model), while llm.upappid is the external system ID (how to call the API). They are different fields.

  18. 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.

  19. New model requires uapi setup first: upapp → uapi → upappkey → llm + llm_api_map (in that order). NO uapiset.

  20. 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.

  21. Pricing SQL is mandatory, not optional: Every model configuration MUST include pricing_program and pricing_program_timing INSERTs. The model won't bill without llm_api_map.ppid pointing to a valid pricing_program.id. Use Python script to guarantee ID consistency.

  22. ppid ID consistency: pricing_program.id, pricing_program_timing.ppid, and llm_api_map.ppid MUST all be the same ID. Raw SQL cannot express this — always generate via Python script using getID() assigned to a variable, then substitute in all related records.

  23. Orchestrator delegation: When configuring multiple models, delegate to a sub-agent rather than doing it yourself. The user expects delegation, not direct execution.

  24. 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

  1. Model summary table: name, model, category, provider, stream mode
  2. Configuration checklist: What's reused vs. what's new
  3. Generate via Python script: ALWAYS use a Python script to generate the SQL (not raw SQL). The script uses getID() to guarantee pricing_program.id = llm_api_map.ppid = pricing_program_timing.ppid consistency. Script must be runnable with python3 script.py and 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.id MUST equal llm_api_map.ppid for billing to work
  • IDs generated inline (e.g., getID() per line or UUID()) cannot be referenced across multiple INSERT statements
  • Python script generates one getID() upfront, reuses it in all related records
  • Script also includes pricing_program and pricing_program_timing INSERTs (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时使用)