Skip to content

糖豆(tangdou)加载本体规则 ​

1. 本体规则的定义与界面编辑 ​

什么是本体规则 ​

本体规则(Ontology)是糖豆 Text2SQL 系统的"领域知识库",用于告诉 AI 助手数据库的结构、表关系和业务查询规则。本体规则定义在 backend/ontology/ 目录下,包含两个 Markdown 文件:

文件用途内容概要
schema.md数据库结构概述9 张表的名称、主键、外键、用途说明,表关系 ER 图,查询策略提示
rules.md业务查询规则区划/部门的 TYPE 和 LEVEL 取值含义、报表/任务表的分组字段说明等

通过界面修改 ​

糖豆提供了可视化的本体管理界面(前端 Ontology 系列组件),用户无需手动编辑文件:

  • 表结构面板(OntologyTableListPanel):查看和编辑表的中文名、主键、外键、用途
  • 字段详情面板(OntologyDetailPanel):管理每张表的字段(名称、类型、备注)
  • 表关系图(OntologyGraphPanel / OntologyRelationPanel):可视化展示表之间的外键关系
  • 规则编辑器(OntologyRulesEditor):在线编辑业务查询规则

当用户在界面中修改规则时,后端 API(PUT /api/ontology/rules)会同步做两件事:

  1. 写入数据库 ontology_rules 表(持久化存储)
  2. 写入 backend/ontology/rules.md 文件(供 Agent 加载)
用户编辑规则 → PUT /api/ontology/rules
                 ├→ 写入 ontology_rules 表
                 └→ 写入 ontology/rules.md  ← Agent 热重载入口

这一设计使得规则修改后,Agent 能在下次请求时自动加载最新内容,无需重启服务。


2. 早期方案:create_deep_agent + HotReloadMemoryMiddleware ​

架构概述 ​

糖豆早期使用 deepagents 库的 create_deep_agent 创建 Agent 实例。该框架内置了 MemoryMiddleware 机制,用于在 Agent 运行时动态加载本体文件。

工作流程 ​

用户请求
    ↓
deepagents Graph 循环
    ├─ agent 节点 → [MemoryMiddleware 拦截]
    │                   ├─ 检查 ontology/*.md 的 mtime
    │                   ├─ mtime 变了 → 重新读取文件内容
    │                   └─ 注入到 agent state 的 memory 字段
    ├─ tools 节点 → 执行 SQL 工具
    └─ agent 节点 → [MemoryMiddleware 再次拦截]
                        └─ 每次节点转换都检查 mtime

热重载原理 ​

HotReloadMemoryMiddleware 在每次 agent 节点转换时执行:

  1. 遍历 ontology/ 目录下的所有 .md 文件
  2. 对每个文件调用 os.path.getmtime() 获取修改时间
  3. 与缓存的 mtime 对比
  4. 如果检测到变化,重新读取文件内容并注入到 agent 的 state 中
  5. LLM 在下一次调用时就能看到最新的本体内容

问题 ​

这套机制虽然功能完整,但存在显著的开销:

  • 每步执行:agent 循环中的每一次节点转换都执行 mtime 检查和文件读取
  • 额外注入的中间件:deepagents 还自动注入了 6+ 个中间件(Filesystem、SubAgent、TodoList、Summarization、PatchToolCalls、Skills),每个中间件都有运行时开销
  • 额外注入的工具:自动添加了 8+ 个糖豆不需要的工具(ls、read_file、write_file、edit_file、glob、grep、task、write_todos)
  • 额外的 Token 开销:deepagents 的 BASE_AGENT_PROMPT(约 2000 字符)和 SKILL.md 文件(约 6000 字符)每次 LLM 调用都会被发送,而糖豆的 Text2SQL 场景完全不需要这些

3. 当前方案:create_react_agent + Prompt 直接嵌入 ​

架构概述 ​

切换到 langgraph 官方的 create_react_agent 后,本体内容在 Agent 创建时直接嵌入 prompt 参数,不再依赖运行时中间件。

工作流程 ​

Agent 创建(惰性初始化或热重载时)
    ↓
load_prompt_and_mtimes(base_dir)
    ├─ 读取 AGENTS.md
    ├─ 读取 ontology/schema.md
    ├─ 读取 ontology/rules.md
    └─ 拼接为完整 system_prompt
    ↓
create_react_agent(model, tools, prompt=system_prompt)
    ↓
用户请求
    └─ agent 循环(无中间件开销)
         ├─ agent 节点 → LLM 调用(prompt 中已含本体内容)
         └─ tools 节点 → 执行 SQL 工具

热重载原理 ​

虽然 create_react_agent 不支持自定义中间件,但热重载能力通过 Agent 实例级别的 mtime 检查 实现:

在 ChatService._get_agent() 中,每次请求都会检查本体文件的 mtime:

python
def _get_agent(self, provider: str):
    agent = self._agents.get(provider)
    _, current_mtimes = load_prompt_and_mtimes(self.base_dir)  # 获取当前 mtime
    cached_mtimes = self._agent_mtimes.get(provider)

    # mtime 未变 → 直接复用缓存的 agent
    if agent is not None and cached_mtimes == current_mtimes:
        return agent

    # mtime 变了 → 重建 agent(新 prompt 生效)
    with self._agent_lock:
        agent = create_agent_instance(
            provider, self.base_dir, checkpointer=self._checkpointer,
        )
        self._agents[provider] = agent
        self._agent_mtimes[provider] = current_mtimes
    return agent

load_prompt_and_mtimes 函数内部带有 5 秒 TTL 缓存,避免每次请求都执行 os.path.getmtime 系统调用:

python
_MTIME_CACHE_TTL = 5.0  # 秒

def load_prompt_and_mtimes(base_dir: str) -> tuple[str, dict[str, float]]:
    # TTL 缓存:5 秒内复用上次的 mtime 结果
    now = time.time()
    if now - _mtime_cache_ts > _MTIME_CACHE_TTL:
        _mtime_cache = {}
        for fpath in files:
            _mtime_cache[fpath] = os.path.getmtime(fpath)
        _mtime_cache_ts = now
    # 组装 prompt:AGENTS.md + ontology/*.md
    ...
    return prompt_text, dict(_mtime_cache)

热重载完整链路 ​

用户在界面修改规则
    ↓
PUT /api/ontology/rules
    ├→ 写入 ontology_rules 表
    └→ 写入 ontology/rules.md  ← 文件 mtime 更新
    ↓
用户发送下一条消息
    ↓
ChatService._get_agent()
    ├→ load_prompt_and_mtimes() 读取 rules.md 的新 mtime
    ├→ 对比缓存的 mtime → 发现不一致
    ├→ 重建 agent(新 prompt 包含最新规则)
    └→ LLM 调用时使用最新的本体内容

4. create_react_agent 与 create_deep_agent 的主要区别 ​

本质定位 ​

create_react_agentcreate_deep_agent
来源langgraph.prebuilt(官方内置)deepagents(第三方扩展库)
设计目标极简 ReAct 循环通用自主代理框架

默认注入的工具 ​

工具reactdeep说明
用户自定义 tools有有SQL 工具、图表工具等
文件系统(ls/read/write/edit/glob/grep)无自动注入糖豆不需要
子任务委派(task)无自动注入糖豆不需要
待办列表(write_todos)无自动注入糖豆不需要

默认注入的中间件 ​

中间件reactdeep作用
FilesystemMiddleware无自动注入给 LLM 提供虚拟文件系统
SubAgentMiddleware无自动注入允许 spawn 子 agent 处理子任务
TodoListMiddleware无自动注入自动管理任务清单
MemoryMiddleware无自动注入热重载 ontology/skills
SummarizationMiddleware无自动注入上下文超长时自动摘要
PatchToolCallsMiddleware无自动注入修复 LLM 不规范的 tool_call 格式

API 参数 ​

python
# create_react_agent — 5 个参数
create_react_agent(model, tools, prompt, checkpointer, debug)

# create_deep_agent — 10+ 个参数
create_deep_agent(model, tools, prompt, base_dir, middleware,
                  skills, filesystem, checkpointer, ...)

每次 LLM 调用的 Token 开销对比 ​

react:  AGENTS.md(~1KB) + ontology(~2.6KB) + 用户消息       ≈ 4KB
deep:   BASE_PROMPT(~2KB) + AGENTS.md + SKILL.md(×2 ~6KB)
        + ontology + 中间件注入的 state 描述                 ≈ 12-18KB

热重载对比 ​

维度Middleware 方案Prompt 方案
检查时机每次节点转换(agent→tools→agent 循环中的每一步)每次用户请求(进入 _get_agent 时)
检查频率高(多轮对话中每步都检查)低(每请求检查一次,带 5 秒 TTL 缓存)
生效时机下一步立即生效下次请求生效
运行时开销每步执行中间件逻辑零(prompt 在创建时固定)

为什么选择 create_react_agent ​

糖豆是 Text2SQL 场景,只需要 SQL 工具和图表工具:

  • 不需要文件系统(没有代码要读/写)
  • 不需要子任务委派(单步 SQL 查询够用)
  • 不需要待办列表(查询-执行-图表是线性流程)

deep_agent 自动注入的 8+ 个工具和 6+ 个中间件全是无用开销。切换到 react_agent 后:

  • 移除了无用工具和中间件的运行时开销
  • 每次 LLM 调用减少约 8000+ 输入 Token
  • 保留了热重载能力(通过 Agent 实例级别的 mtime 检查)
  • 移除了对 deepagents 第三方库的依赖

Released under the MIT License.