TencentDB Agent Memory 的 llm_binding 表:schema 与建表 SQL 两套
在一个 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.json 是 2.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_id | text | primary key |
mode | text | not null,默认 proxy |
proxy_base_url | text | 可空 |
api_key | text | 可空 |
base_url | text | 可空 |
enabled | integer | not null,默认 1 |
updated_at | text | not null |
七个字段,没有 model。
位置二,MemoryKnowledge/src/db/client.ts:143-152,migrate() 函数体里那一大段原生 SQL 的最后一块,CREATE TABLE IF NOT EXISTS llm_binding。列的顺序是 service_id、mode、proxy_base_url、api_key、model、base_url、enabled、updated_at —— model TEXT 落在第 148 行,夹在 api_key 和 base_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 的默认值就是 proxy,enabled 默认 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_key 与 base_url 服务于 byo 那条路。唯独模型名这一列,Drizzle 侧压根没有。
两套定义分别服务于哪条路径
关键在于,这个模块的运行时建表不走 drizzle-kit。
MemoryKnowledge/drizzle.config.ts 全文只有 10 行:dialect: "sqlite",schema: "./src/db/schema.ts",out: "./src/db/migrations",dbCredentials.url 取 process.env.KNOWLEDGE_DB_PATH || "./data/knowledge.db"。package.json:18-19 里也确实有两条脚本,db:generate → drizzle-kit generate,db:migrate → drizzle-kit migrate。
但真正在进程启动时建表的是 MemoryKnowledge/src/db/client.ts:50-153 的 migrate(),函数体是手写的一串 CREATE TABLE IF NOT EXISTS 原生 SQL,五张表加索引一次性 raw.exec() 掉。而 drizzle.config.ts 里 out 指向的 ./src/db/migrations 这个目录,我们在快照里 ls MemoryKnowledge/src/db/ 只看到 client.ts 与 schema.ts 两个文件,没有 migrations/。
也就是说,schema.ts 与 client.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_MODELenv
于是这张表上并列着三处表述:Drizzle schema 没有该列、运行时 SQL 有该列、store 的注释说明模型名不按实例存、只来自全局 LLM_MODEL 环境变量。三处位置都给出来了,说完这一句就停。
顺带把全局那一侧也标一下:LLM_MODEL 的默认值在 MemoryKnowledge/src/config.ts:163 是 Memory-Model。
同一份建表 SQL 里的另一个相邻细节
client.ts 这段 SQL 还有个值得注意的地方:不是所有 Drizzle 侧存在的列都写在 CREATE TABLE 正文里。
knowledge_code_graph 与 knowledge_wiki 两张表的 service_url、summary 是靠 addColumnIfMissing() 补的(MemoryKnowledge/src/db/client.ts:157-163)——先读 PRAGMA table_info,发现列不存在再 ALTER TABLE ADD COLUMN,注释里说明这样做是因为 SQLite 的 ALTER TABLE ADD COLUMN 不是幂等的。补列名单一共六项:knowledge_code_graph 的 service_url 与 summary、knowledge_wiki 的 service_url 与 summary、以及两张 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。同样只陈述差异。
你自己怎么核这一条
不需要跑任何服务,四步都是纯静读:
- 切到
feat/server_team分支(该仓库的默认分支即为此),确认自己手上的快照; - 打开
MemoryKnowledge/src/db/schema.ts:131-139,数llmBinding的字段; - 打开
MemoryKnowledge/src/db/client.ts:143-152,数CREATE TABLE IF NOT EXISTS llm_binding的列; - 在
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_MODE是custom就原样返回全局配置;若是默认的proxy,则返回一份把baseUrl与apiKey清空的配置,让createLlmClient直接抛错,注释原文是 no silent direct fallback。ingest-v2/llm.ts:51-53、90-101那边同样是缺apiKey或baseUrl就抛,注释明说不再兜底读process.env、避免「偷偷掉回直连」。 - 如果你在纠结到底该填哪个模型名,那是另一处口径差异,不是这张表的列。
src/config.ts:163与src/engines/wiki/ingest-v2/llm.ts:44都是Memory-Model,README.md:71的示例也是Memory-Model;而docker-compose.yml:41与docker/entrypoint.sh:228的默认值是gpt-4o-mini,docker/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:45的DEFAULT_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 是什么:团队级 Agent 记忆中枢怎么读
- 本专题共 40 篇,完整分组目录见专题页
- TencentDB Agent Memory 三处都写 4 张表,实际建了 5 张
- TencentDB Agent Memory 的构建产物名:三处写法不一致
本文依据 TencentDB Agent Memory 官方仓库(github.com/TencentCloud/TencentDB-Agent-Memory)
feat/server_team 分支上的 README、INSTALL、CHANGELOG、ROADMAP 与四个模块的源码整理,
核对日 2026-08-16,对应仓库快照 97f9465。该仓库的默认分支即为 feat/server_team。
本文内容为仓库源码与文档口径,我们没有部署、也没有运行过该项目的任何一个模块,
因此不涉及运行效果、检索质量与性能的任何描述。
该项目主模块处于 beta 阶段、其余模块版本号仍为 0.1.0,参数与接口随版本变动,请以仓库最新内容为准。
该项目会采集并存储团队的对话、文档与代码,属于敏感数据,是否使用请结合自身合规要求评估。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。