Extend the builtin_models.yaml loader so the YAML file becomes a complete
source of truth for the rows it owns. Builds on the previous commit's
managed_by column.
Lifecycle contract:
- Every UPSERTed entry is tagged managed_by="yaml".
- The DoUpdates list now includes deleted_at, so an entry that was
soft-deleted (e.g. via UI/API) is automatically resurrected when it
reappears in the file. Closes the "ghost row that exists but is
invisible" failure mode.
- After all UPSERTs, the loader soft-deletes rows where
managed_by='yaml' AND id NOT IN (current YAML id set). Removing an
entry from YAML is now the supported way to retire a built-in model —
no manual SQL needed.
- Rows tagged managed_by='' (UI/API/SQL-seeded built-ins) are invisible
to the reconcile path and never touched.
- When a YAML entry sets is_default=true, the loader first clears
is_default on any other rows in the same (tenant_id, type) bucket,
mirroring the invariant enforced by the API path
(repository.UnsetDefaultModel).
Failure handling stays defensive:
- File missing / not a regular file / parse error: warn and skip; the
drift sweep is NOT executed so a malformed file cannot wipe rows.
- Per-entry UPSERT error: warn, drop the id from the keep-set so the
sweep also leaves the existing row alone ("leave alone on failure").
Tests cover: file-missing, parse-error, basic upsert + defaults,
idempotency, ${ENV} interpolation (set vs unset), drift sweep removing
YAML rows, drift sweep ignoring manual rows, soft-delete resurrection,
is_default cleanup across tenant+type, explicit empty list sweeping all
yaml-managed rows, and a regression guard ensuring BeforeCreate does not
overwrite YAML-supplied stable ids.
Docs are rewritten so operators see "delete from YAML and restart" as
the supported removal path; SQL is retained only for the legacy
managed_by='' slice.
9.8 KiB
内置模型管理指南
概述
内置模型是系统级别的模型配置,对所有租户可见,但敏感信息会被隐藏,且不可编辑或删除。内置模型通常用于提供系统默认的模型配置,确保所有租户都能使用统一的模型服务。
内置模型特性
- 所有租户可见:内置模型对所有租户都可见,无需单独配置
- 安全保护:内置模型的敏感信息(API Key、Base URL)会被隐藏,无法查看详情
- 只读保护:内置模型不能被编辑或删除,只能设置为默认模型
- 统一管理:由系统管理员统一维护,确保配置一致性和安全性
如何添加内置模型
WeKnora 支持两种方式添加内置模型:推荐使用 YAML 声明式配置(自动幂等下发),保留 SQL 直插作为兼容路径。
方式一(推荐):YAML 配置文件
文件位置
默认路径是 config/builtin_models.yaml(与 config.yaml、builtin_agents.yaml 同目录)。如需挂载到其他位置,设置环境变量 BUILTIN_MODELS_CONFIG=/absolute/path/builtin_models.yaml 覆盖。
文件不存在时启动期会跳过、不报错;解析失败仅记录 Warning、不影响主流程。每次应用启动会重新读取并按 id 字段 UPSERT 到 models 表(保留 created_at,刷新其他字段)。
Schema
builtin_models:
- id: <required, stable, UPSERT key>
tenant_id: <int, default 10000> # 与 tenants_id_seq 起点对齐
name: <string>
type: KnowledgeQA | Embedding | Rerank | VLLM | ASR
source: <string, default "remote"> # local | remote | aliyun | ...
description: <string, optional>
is_default: <bool, default false>
status: <string, default "active">
parameters:
base_url: <string>
api_key: <string, supports ${ENV_VAR}>
provider: <string> # openai | generic | moonshot | ...
embedding_parameters: # 仅 Embedding 类型
dimension: <int>
truncate_prompt_tokens: <int>
完整示例
builtin_models:
- id: builtin-openai-chat
name: gpt-4o-mini
type: KnowledgeQA
source: remote
description: OpenAI 默认对话模型
is_default: false
parameters:
base_url: https://api.openai.com/v1
api_key: ${OPENAI_API_KEY}
provider: openai
- id: builtin-openai-embeddings
name: text-embedding-3-small
type: Embedding
source: remote
parameters:
base_url: https://api.openai.com/v1
api_key: ${OPENAI_API_KEY}
provider: openai
embedding_parameters:
dimension: 1536
truncate_prompt_tokens: 0
- id: builtin-rerank
name: bge-reranker-v2-m3
type: Rerank
source: remote
parameters:
base_url: ${RERANK_BASE_URL}
api_key: ${RERANK_API_KEY}
provider: generic
${ENV} 插值
api_key / base_url / name 等任何字符串字段都可以引用环境变量:${OPENAI_API_KEY} 会在启动时被对应的 os.Getenv("OPENAI_API_KEY") 替换。
- 环境变量存在 → 替换为实际值
- 环境变量不存在 → 保留字面
${OPENAI_API_KEY}字符串(让 401 错误能直接看出来 env 没设,便于排查) - 不支持
${VAR:-default}这种 shell 扩展,行为与现有config.yaml的插值实现一致 - 非字符串字段不能 env 化(如
type、dimension、is_default),因为它们必须按 YAML 的目标类型解析
env 变量怎么进入容器
docker-compose.yml 的 app 服务已经预置了:
env_file:
- path: .env
required: false
意味着把变量值写到项目根目录的 .env 文件里,启动时自动透传到容器。无需在 environment: 块里逐个透传。required: false 保证 .env 不存在时容器仍可启动(适配上游 fresh clone 场景)。
仓库的 .env.example 顶部预留了 Built-in Models 注释段,列出 LLM / Embedding / Rerank 的参考变量名作为起点;复制 .env.example 为 .env 后取消注释并填值即可。变量名由 YAML 自行决定,参考段只是常见样板,不是保留字。
完整端到端示例:
.env
LLM_MODEL_NAME=gpt-4o-mini
LLM_BASE_URL=https://api.openai.com/v1
LLM_API_KEY=sk-...
LLM_PROVIDER=openai
config/builtin_models.yaml
builtin_models:
- id: builtin-llm-default
type: KnowledgeQA
is_default: true
name: ${LLM_MODEL_NAME}
parameters:
base_url: ${LLM_BASE_URL}
api_key: ${LLM_API_KEY}
provider: ${LLM_PROVIDER}
启动:
docker compose up -d
启动后验证
docker compose logs app | grep -E 'Built-in models? config'
会看到类似:
Built-in model upserted: id=builtin-openai-chat name=gpt-4o-mini type=KnowledgeQA
Built-in model upserted: id=builtin-openai-embeddings name=text-embedding-3-small type=Embedding
Built-in models config applied: 2 entries from /app/config/builtin_models.yaml.
Docker 部署
在 docker-compose.yml 的 app 服务 volumes 块挂载文件:
services:
app:
volumes:
- ./config/builtin_models.yaml:/app/config/builtin_models.yaml:ro
仓库提供了 config/builtin_models.yaml.example 作为起点,复制为 config/builtin_models.yaml 后按需修改。
方式二:直接 SQL 插入
支持的 provider:generic(自定义)、openai、aliyun、zhipu、volcengine、hunyuan、deepseek、minimax、mimo、siliconflow、jina、openrouter、gemini、modelscope、moonshot、qianfan、qiniu、longcat、gpustack
-- 示例:LLM 内置模型
INSERT INTO models (
id, tenant_id, name, type, source, description,
parameters, is_default, status, is_builtin
) VALUES (
'builtin-llm-001',
10000,
'gpt-4o-mini',
'KnowledgeQA',
'remote',
'系统内置 LLM 模型',
'{"base_url": "https://api.openai.com/v1", "api_key": "sk-xxx", "provider": "openai"}'::jsonb,
false,
'active',
true
) ON CONFLICT (id) DO NOTHING;
-- Embedding
INSERT INTO models (
id, tenant_id, name, type, source, description,
parameters, is_default, status, is_builtin
) VALUES (
'builtin-embedding-001',
10000,
'text-embedding-3-small',
'Embedding',
'remote',
'系统内置 Embedding 模型',
'{"base_url": "https://api.openai.com/v1", "api_key": "sk-xxx", "provider": "openai", "embedding_parameters": {"dimension": 1536, "truncate_prompt_tokens": 0}}'::jsonb,
false,
'active',
true
) ON CONFLICT (id) DO NOTHING;
-- Rerank
INSERT INTO models (
id, tenant_id, name, type, source, description,
parameters, is_default, status, is_builtin
) VALUES (
'builtin-rerank-001',
10000,
'bge-reranker-v2-m3',
'Rerank',
'remote',
'系统内置 Rerank 模型',
'{"base_url": "https://api.jina.ai/v1", "api_key": "jina-xxx", "provider": "jina"}'::jsonb,
false,
'active',
true
) ON CONFLICT (id) DO NOTHING;
验证插入结果
SELECT id, name, type, is_builtin, status
FROM models
WHERE is_builtin = true
ORDER BY type, created_at;
将现有模型设置为内置模型
如果你已经手工创建了一个普通模型,想把它升级为内置模型:
UPDATE models
SET is_builtin = true
WHERE id = '模型ID';
移除内置模型
从 YAML 删除条目即可。 应用启动时会自动软删除 models 表中 YAML 不再声明的 YAML 托管行 —— 你不再需要手工跑 SQL。
工作原理:每条由 YAML 写入的行会被打上 managed_by = 'yaml' 标记。重启时 loader 走两步:
- UPSERT 当前 YAML 中的所有条目(按
id幂等,包含把之前软删过的deleted_at重置为 NULL —— 也就是说从 YAML 拿掉再加回来等于"复活") - 软删除
is_builtin = true AND managed_by = 'yaml' AND id NOT IN (当前 YAML 中的 id 集合)的行
手工通过 SQL 插入的 builtin 行(managed_by = '')永远不会被 loader 触碰,与 YAML 完全隔离。
手工路径补充
如果你是走 SQL 路径管理的(managed_by = ''),删除仍然走老方法:
-- 取消 builtin 标记,恢复为普通模型
UPDATE models SET is_builtin = false WHERE id = '模型ID';
-- 或直接删除
DELETE FROM models WHERE id = '模型ID';
紧急关闭 YAML 接管
如果误改了 YAML 想立刻停用接管又不想清空文件,最快的方法是:把环境变量 BUILTIN_MODELS_CONFIG 指向一个不存在的路径并重启 —— loader 看到文件缺失会直接 no-op,包括跳过 drift sweep,已经写入的 YAML 托管行保留原状。
注意事项
- ID 命名规范:建议使用
builtin-{type}-{slug}的格式,例如builtin-openai-chat、builtin-rerank - 租户ID:内置模型可以属于任意租户,默认
10000(与tenants_id_seq起点一致) - YAML 与 SQL 并存:两种方式可以同时使用,loader 只动
managed_by='yaml'的行;通过 SQL 插入的 builtin 行对 loader 完全不可见 is_default单一保证:YAML 中将某条 entry 标记is_default: true时,loader 会先把同(tenant_id, type)下的其它默认模型置为false,避免 API 路径维护的"每类型一个默认模型"语义被破坏- 重启即生效:修改 YAML 后
docker compose restart app即可让新配置生效 - 加密:API Key 在
parametersJSONB 中以加密形式存储(若SYSTEM_AES_KEY已配置),未配置时降级为明文兼容路径 - 安全性:前端会自动隐藏内置模型的 API Key 和 Base URL,但数据库中的原始数据仍然存在,请妥善保管数据库访问权限
- 解析错误自我保护:YAML 解析失败时 loader 仅打 warning 并跳过 reconcile,不会执行 drift sweep,确保一个手抖的 YAML 改动不会大规模软删既有内置模型