TencentDB Agent Memory 的 llm_binding 表:schema 与建表 SQL 两套

2026-08-16

在一个 TypeScript 项目里看到 Drizzle ORM,通常会默认「schema 文件就是表结构的真相」。TencentDB Agent Memory 仓库的 MemoryKnowledge 模块里有一张小表打破了这个默认:同一张 llm_binding,在 Drizzle schema 里是七个字段,在运行时执行的建表 SQL 里是八列。多出来的那一列叫 model

先把口径交代清楚:以下全部来自我们对该仓库 feat/server_team 分支快照 97f9465 的静读,核对日 2026-08-16。这个仓库的默认分支就是 feat/server_team,不是 main 也不是 master,所以下文所有文件路径都要放在这个分支上看。MemoryKnowledge/package.json 里该模块的版本截至 2026-08-16 还是 0.1.0,仓库主模块处于 beta 阶段(MemoryCore/package.json2.0.0-beta.1,而 CHANGELOG.md 最新条目写的是 [2.0.1-beta.1] — 2026-08-13,两处不一致)。beta 阶段的字段、默认值和接口随时可能变动,本文写到的行号只对这个快照有效。

两处定义摆在一起

位置一MemoryKnowledge/src/db/schema.ts:131-139,Drizzle 的 sqliteTable("llm_binding", …)

字段类型约束 / 默认
service_idtextprimary key
modetextnot null,默认 proxy
proxy_base_urltext可空
api_keytext可空
base_urltext可空
enabledintegernot null,默认 1
updated_attextnot null

七个字段,没有 model

位置二MemoryKnowledge/src/db/client.ts:143-152migrate() 函数体里那一大段原生 SQL 的最后一块,CREATE TABLE IF NOT EXISTS llm_binding。列的顺序是 service_idmodeproxy_base_urlapi_keymodelbase_urlenabledupdated_at —— model TEXT 落在第 148 行,夹在 api_keybase_url 中间。

差异就这一条:Drizzle 侧七个字段,运行时建表 SQL 八列,多的是 model。两处的位置都写在上面了,你可以自己翻到那两段对着数。

这张表本身管的是什么

llm_binding 存的是按 service_id 划分的一份 LLM 路由配置。MemoryKnowledge/src/db/schema.ts:126-129 的注释把两种 mode 写得很直接:mode='proxy' 表示走 context_proxy,并带一个专用于 knowledge-service 的 user_key;mode='byo' 表示走用户自带的 OpenAI 兼容端点。表里 mode 的默认值就是 proxyenabled 默认 1,两者都是 not null。

proxy 这条路上 baseUrl 怎么拼,落在同一条链路的下一站:MemoryKnowledge/src/store/llm-binding-store.ts:167 把它拼成 {proxy_base_url}/proxy/{service_id}/v1。所以这张表的列基本都有去处——proxy_base_url 与主键 service_id 直接进 URL,api_keybase_url 服务于 byo 那条路。唯独模型名这一列,Drizzle 侧压根没有。

两套定义分别服务于哪条路径

关键在于,这个模块的运行时建表不走 drizzle-kit

MemoryKnowledge/drizzle.config.ts 全文只有 10 行:dialect: "sqlite"schema: "./src/db/schema.ts"out: "./src/db/migrations"dbCredentials.urlprocess.env.KNOWLEDGE_DB_PATH || "./data/knowledge.db"package.json:18-19 里也确实有两条脚本,db:generatedrizzle-kit generatedb:migratedrizzle-kit migrate

但真正在进程启动时建表的是 MemoryKnowledge/src/db/client.ts:50-153migrate(),函数体是手写的一串 CREATE TABLE IF NOT EXISTS 原生 SQL,五张表加索引一次性 raw.exec() 掉。而 drizzle.config.tsout 指向的 ./src/db/migrations 这个目录,我们在快照里 ls MemoryKnowledge/src/db/ 只看到 client.tsschema.ts 两个文件,没有 migrations/

也就是说,schema.tsclient.ts 这两处定义各自服务于一条不同的路径:前者是 Drizzle 查询构建器在 TypeScript 侧看到的表形状(llmBinding 这个对象还导出了 LlmBinding 类型),后者是磁盘上那个 SQLite 文件实际被建成什么样。哪一处「是对的」我们不做判断,也不推断为什么两边不同步——这里只陈述这两处的位置与差异;你手上那份库文件里这张表实际有哪些列,以库文件本身为准。

第三处口径:源码注释

同一件事还有第三种说法。MemoryKnowledge/src/store/llm-binding-store.ts:16-17 的模块头注释原文是:

Model is NOT stored per-instance — it always comes from the global LLM_MODEL env

于是这张表上并列着三处表述:Drizzle schema 没有该列、运行时 SQL 有该列、store 的注释说明模型名不按实例存、只来自全局 LLM_MODEL 环境变量。三处位置都给出来了,说完这一句就停。

顺带把全局那一侧也标一下:LLM_MODEL 的默认值在 MemoryKnowledge/src/config.ts:163Memory-Model

同一份建表 SQL 里的另一个相邻细节

client.ts 这段 SQL 还有个值得注意的地方:不是所有 Drizzle 侧存在的列都写在 CREATE TABLE 正文里。

knowledge_code_graphknowledge_wiki 两张表的 service_urlsummary 是靠 addColumnIfMissing() 补的(MemoryKnowledge/src/db/client.ts:157-163)——先读 PRAGMA table_info,发现列不存在再 ALTER TABLE ADD COLUMN,注释里说明这样做是因为 SQLite 的 ALTER TABLE ADD COLUMN 不是幂等的。补列名单一共六项:knowledge_code_graphservice_urlsummaryknowledge_wikiservice_urlsummary、以及两张 audit 表的 service_id

llm_binding 不在这份补列名单里。

「4 张表」这个说法出现在三处,实际是 5 张

跟上面那条挨着的还有一处数字差异,值得一起记下来:

  • MemoryKnowledge/src/db/schema.ts:2-8 的文件头注释写「4 SQLite tables」并逐一列出四张,而同一个文件在第 131 行定义了第五张 llm_binding
  • MemoryKnowledge/src/db/client.ts:46 的函数注释写「for all 4 tables + indexes」,函数体里建的是 5 张;
  • MemoryKnowledge/.env.example:13 写「SQLite 数据库路径(4 张表:knowledge_wiki / knowledge_code_graph / *_audit)」。

三处写 4,代码里数出来是 5。同样只陈述差异。

你自己怎么核这一条

不需要跑任何服务,四步都是纯静读:

  1. 切到 feat/server_team 分支(该仓库的默认分支即为此),确认自己手上的快照;
  2. 打开 MemoryKnowledge/src/db/schema.ts:131-139,数 llmBinding 的字段;
  3. 打开 MemoryKnowledge/src/db/client.ts:143-152,数 CREATE TABLE IF NOT EXISTS llm_binding 的列;
  4. MemoryKnowledge/src/store/llm-binding-store.ts 顶部读那段模块注释。

如果你手里已经有一份由该服务建出来的库文件,那张表的实际列以库文件本身为准——项目自己在 client.ts:157-163 就是用 PRAGMA table_info 来判断某列在不在的,仓库里现成有这个手法。至于你那份库是哪个版本的代码建的、后来有没有被别的方式改过,我们无从判断。

什么情况说明你遇到的不是这一条

这类「两套定义」的问题很容易被拿来解释一切,所以反过来划一下边界:

  • 如果现象是 wiki ingest 直接报 LLM 未配置类的错,那更可能落在 resolveLlmConfig() 的语义上,跟表里有没有 model 列是两码事。MemoryKnowledge/src/store/llm-binding-store.ts:151-157 写得很直白:没有可用 binding 时,若全局 LLM_MODEcustom 就原样返回全局配置;若是默认的 proxy,则返回一份把 baseUrlapiKey 清空的配置,让 createLlmClient 直接抛错,注释原文是 no silent direct fallback。ingest-v2/llm.ts:51-53、90-101 那边同样是缺 apiKeybaseUrl 就抛,注释明说不再兜底读 process.env、避免「偷偷掉回直连」。
  • 如果你在纠结到底该填哪个模型名,那是另一处口径差异,不是这张表的列。src/config.ts:163src/engines/wiki/ingest-v2/llm.ts:44 都是 Memory-ModelREADME.md:71 的示例也是 Memory-Model;而 docker-compose.yml:41docker/entrypoint.sh:228 的默认值是 gpt-4o-minidocker/env.example:25 写的是 LLM_MODEL=ep-your-endpoint-id,仓库另一处 deploy/panel-knowledge-combined/Dockerfile:61 又回到 Memory-Model。四处不同,都在上面标了位置。
  • 如果你发现 drizzle-kit generate 生成不出东西或找不到迁移目录,那是 out 指向的 ./src/db/migrations 在快照里不存在这件事,跟 model 列无关。
  • 如果你在对 token 上限LLM_MAX_TOKENS 也有两层:src/config.ts:165 默认 32768,src/engines/wiki/ingest-v2/llm.ts:45DEFAULT_MAX_TOKENS 是 8192,后者只在上层没传时生效。

一句必要的提醒

llm_binding 这张表里存着 api_key 列。而写这张表的那组内部端点(POST /v3/internal/llm-binding/set|status|list,路由在 MemoryKnowledge/src/routes/llm-binding.ts:40,95,103)的文件头注释里写着:KS trusts the internal network like its other routes; no extra auth layer is added here——即这组路由没有额外鉴权层。这是仓库源码自己的表述,我们照实记下来,不做任何「配成什么样就安全了」的引申。这个模块本身也是围绕团队文档与代码仓库做加工的,涉及的数据敏感度需要你按自己的环境判断。

回到本文这一条:一张七字段的 Drizzle 定义、一段八列的建表 SQL、一句「模型名不按实例存」的注释,三处并列,位置都在上面。要基于这个模块做二次开发,这三处最好都翻一遍,别只信 schema.ts

延伸阅读


本文依据 TencentDB Agent Memory 官方仓库(github.com/TencentCloud/TencentDB-Agent-Memoryfeat/server_team 分支上的 README、INSTALL、CHANGELOG、ROADMAP 与四个模块的源码整理, 核对日 2026-08-16,对应仓库快照 97f9465。该仓库的默认分支即为 feat/server_team。 本文内容为仓库源码与文档口径,我们没有部署、也没有运行过该项目的任何一个模块, 因此不涉及运行效果、检索质量与性能的任何描述。 该项目主模块处于 beta 阶段、其余模块版本号仍为 0.1.0,参数与接口随版本变动,请以仓库最新内容为准。 该项目会采集并存储团队的对话、文档与代码,属于敏感数据,是否使用请结合自身合规要求评估。 安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。