☰
Nexent 后端数据库设计、访问与迁移规范全解:从命名、审计字段到 SQL 迁移治理
2026/10/14 16:55:45 网站建设 项目流程
  • 人工智能
  • AI Agent
  • Agent 编排
  • 低代码
  • AI 技能
  • RAG

【免费下载链接】nexent

Nexent 是一个开源智能体SDK和平台,能够将描述流程的自然语言转化为完整的多模态服务 —— 无需编排,无需复杂拖拉拽。基于 MCP 工具生态系统构建,Nexent 提供灵活的模型集成、可扩展的数据处理和强大的知识库管理。我们的目标很简单:将数据、模型和工具整合到一个智能中心中,让任何人都能轻松地将 Nexent 集成到项目中,使日常工作流程更智能、更互联。

项目地址:https://gitcode.com/ModelEngine/nexent
点击查看免费下载

本文基于 Nexent 仓库中nexent-backend技能库下的数据库参考文档 database.md,完整梳理该项目在 PostgreSQL 上的建表设计工作流、命名与类型规范、审计字段标准、事务所有权约定,以及deploy/sql/下的版本化迁移机制。读完后,你可以在 Nexent 中独立完成一次从 schema 设计、backend/database/模型编写到迁移文件落地的完整数据库变更。

一、设计工作流:先实体后表,先查询后字段

原文档定义的数据库设计工作流共四步,适用于backend/database/**/*.py的 schema 工作以及服务层的完整性检查:

  1. 先识别业务实体与关系,再决定表和字段。E-R 图是可选的;即使不画图,也必须在设计文档中清晰描述关系。
  2. 收集查询场景——过滤、关联、排序、分页、更新、版本/历史需求——并据此共同决定列、JSONB、独立表与索引的选择,而不是孤立地设计表结构。
  3. 明确每个字段的规范:名称、业务定义、类型/长度、可空性/默认值、审计行为、引用关系、应用层强制的完整性约束。JSONB 的边界和索引理由要随 DDL 一起记录。
  4. 对照现有模型、调用方与迁移文件,把新设计要求落到新 schema 上;在实施任何 schema 变更之前,先显式解决遗留兼容性问题。

这套流程的落点就是 db_models.py 中约 80 个模型类(ConversationRecord、AgentInfo、AgentVersion等),以及 deploy/sql/migrations/ 下的版本化 SQL 文件。

二、表与字段命名规范

原文档给出了一套完整的 snake_case 命名规则,仓库中的实际表名可以逐条对上:

规则原文要求仓库实例
业务表后缀<module>_<business_noun_phrase>_tconversation_record_t、ag_tenant_agent_t、memory_records_t(见 db_models.py)
纯后端日志表后缀<module>_<business_noun_phrase>_loguser_token_usage_log_t对应的UserTokenUsageLog(db_models.py)
归档表后缀归档时采用<table_name>_<archive_date>_bak—
名词数1~3 个有意义的名词或名词短语,优先模块内一致的缩写,避免孤立动词文档示例:conversation_share_record_t比conversation_share_t更能表达“分享记录”
字段名带单位度量值要带单位,如latency_ms、amount_usd;引用字段名与其引用标识符保持一致AgentRepository.downloads(下载次数)、ConversationShareAsset.size(字节)
宽泛词必须限定data、info、metadata等宽泛名要加前缀,如provider_metadata,并写清用途ConversationRecord.runtime_metadata、ConversationShareAsset.metadata_json(db_models.py)

两个容易被忽视的硬性要求:

  • 每个字段必须有英文数据库列注释,说明业务含义与适用场景;例如某个凭据若只用于语音模型鉴权,注释中必须写明这一限制。
  • 注释必须落到生成的 DDL/数据库元数据里,并检查 ORM 声明是否真的会输出数据库 comment——Python 里的 doc 字符串不构成列已加注释的证据。

TableBase子类在 db_models.py 中普遍使用doc=参数(如doc="Creation time"、doc="Whether it is deleted. Optional values: Y/N"),这正是“注释随 DDL 走”的实现约定;而 v2.6.1_002_agent_protocol_repair_retry.sql 则展示了纯 SQL 侧的做法:COMMENT ON COLUMN nexent.ag_tenant_agent_t.enable_protocol_repair_retry IS '...',在迁移文件里显式补注释。

三、字段类型与长度边界

原文档把长度/类型选择视为规范的一部分,并给出推荐默认值表,要求输入校验与之一致:

语义推荐默认设计要求
名称/标题VARCHAR(100)按显式业务上限调整
枚举/状态码VARCHAR(30)保持词表与长度一致
一般整数INT4确认所需范围
主键/引用标识INT4与被引用标识符类型一致;容量或历史例外需书面记录
字典/可变结构化值JSONB定义用途、允许的键/结构、值类型与校验边界

仓库中的模型与这张表高度吻合:AgentVersion.version_name是String(100),status字段统一为String(30)(RELEASED / DISABLED / ARCHIVED,db_models.py);结构化值则使用 PostgreSQL 方言的JSONB(db_models.py 中from sqlalchemy.dialects.postgresql import ARRAY, JSONB)。

另有两条边界规则:

  • 避免对有业务上限的值使用无界TEXT/VARCHAR;TEXT留给正文、错误堆栈、原始文本这类没有稳定上限的场景。
  • 不要把示例性的长度上限(如“消息正文 1000 字符”)套用到所有字段上;上限要从实际契约推导,并验证边界与拒绝行为。

四、列、JSONB 还是独立表:按访问模式选择

原文档用一张决策表明确了三种表示形式的选择依据:

场景首选表示
对所有行都有业务意义、会被查询/排序/聚合、或频繁更新的属性显式列
行级别的补充数据、可变结构、或主要以整体形式读取的值用途明确的 JSONB,如快照或 provider 原始输出
属性较多的新业务实体/关系独立表
完全从属于另一实体的小实体/小关系少量列或有界 JSONB,按访问模式选择

以 db_models.py 中的ConversationShare为例可以印证这一决策过程:status(active/revoked)、mode(all/selected)这类会被查询过滤的状态值是显式列;selected_message_ids与snapshot_json则是“整体读取”的冻结快照,用 JSONB 承载。

对 JSONB 的所有权与校验,文档要求写明:允许/必填的键(或显式声明为不透明 payload)、null/默认语义、适用的大小上限;稳定的、可查询的业务属性不允许藏进无约束的 metadata 袋里。

五、审计字段:五字段标准与 TableBase

5.1 五字段标准

新设计规范(原文档)要求每张新表都必须带全部五个审计字段,日志表与归档设计同样适用;已有的历史例外必须被识别出来,而不能被无声地复制:

字段类型含义
created_byVARCHAR(100)创建者(用户或系统行为体)
create_timeTIMESTAMP创建时间
updated_byVARCHAR(100)最后更新者(用户或系统行为体)
update_timeTIMESTAMP最后更新时间
delete_flagVARCHAR(1)软删除标记,默认N

5.2 TableBase 的实现与继承要求

文档明确要求:从 backend/database/db_models.py 的TableBase继承这五个审计字段,且不要在子类中重复声明已继承的审计列。仓库实现与标准逐字对应:

class TableBase(DeclarativeBase): create_time = Column(TIMESTAMP(timezone=False), server_default=func.now(), doc="Creation time") update_time = Column(TIMESTAMP(timezone=False), server_default=func.now( ), onupdate=func.now(), doc="Update time") created_by = Column(String(100), doc="Creator") updated_by = Column(String(100), doc="Updater") delete_flag = Column(String(1), default="N", doc="Whether it is deleted. Optional values: Y/N")

同文件中还存在一个SimpleTableBase(db_models.py),它是不带审计字段的基类,用于RolePermission、A2ATask等场景——从源码结构看,这些属于文档所说的“必须被显式识别出来的历史例外”,新增表不应照抄这一做法。

另一个实现细节值得注意:TableBase的update_time依赖 ORM 的onupdate=func.now()。原文档特别警告——要定义所有写路径(包括裸 SQL 和 SQLAlchemy Core 操作)如何维护时间戳;不要假设 ORM 的onupdate配置能证明数据库层面的行为。也就是说,走session.execute(insert(...).values(...))的 Core 写路径不会触发 ORM 级onupdate,需要显式赋值或在数据库端兜底。

六、键、引用与业务唯一性

这一节是原文档中约束最密集的部分,要点如下:

  • 每张业务表只有一个技术性主键。如果业务身份是复合的,另加一个记录标识符,而不是把业务组合作为主键。
  • 版本化资源要区分“稳定实体身份”与“版本行 ID”。原文档举例:agent_no跨版本稳定,agent_id标识某个具体版本——并强调这只是设计示例,不是对既有列重命名的授权。当前仓库中对应的是 AgentVersion:agent_id关联ag_tenant_agent_t,version_no从 1 开始(不含 0 草稿),source_type区分NORMAL/ROLLBACK。
  • 新业务表设计不声明数据库外键约束,也不声明非主键的唯一约束;不要用唯一索引绕过该策略。主键唯一性和NOT NULL这类结构性约束仍然允许。
  • 逻辑引用名保持一致,关系检查与业务唯一性在服务层实现;把这些契约写进字段注释,并指明负责的服务/写路径。
  • 唯一性必须定义范围(租户、版本、活跃/已删除行),并验证缺失引用、重复与并发写入。“先检查再插入”本身不构成竞态安全的唯一性证据;必要时定义事务/序列化或其他应用层协调策略。
  • 已有的外键与唯一约束/索引属于兼容性问题:不要在无关工作中删除它们;任何删除都需要一次显式迁移和替代完整性行为,并覆盖所有写方。

从源码结构看,这一策略在代码中体现为:db_models.py里存在ForeignKey/UniqueConstraint的 import 与个别历史表上的unique=True列(如ConversationShare.share_token),但新增表普遍只用Sequence主键 + 普通列 + 服务层校验,与“存量保留、增量不再新增”的兼容策略一致。

七、索引设计

  • 索引要与表一起设计,依据真实的过滤、关联、排序、分页场景;说明每个索引支撑哪条查询、为什么列顺序/谓词合适。
  • 考虑常见的活跃行过滤(delete_flag = 'N')与查询组合;默认不要给每个字段都建索引;业务索引一律非唯一。
  • 索引 DDL 要包含在迁移里;在有执行环境时验证代表性查询计划,无法验证的性能假设必须显式标注为未验证。

db_models.py 中的部分索引展示了“活跃行过滤 + 查询组合”的具体做法:postgresql_where=text("delete_flag = 'N'")以及delete_flag = 'N' AND status <> 'DELETED'之类的复合谓词,把软删除语义直接编进索引,避免为已删除行维护无效索引条目。

八、模型与 CRUD 约定

文档规定的新表 CRUD 写法与仓库实现一一对应:

  • 模型定义在 backend/database/db_models.py,继承TableBase复用审计字段(全文件有 60+ 个class Xxx(TableBase),从ConversationRecord到ResourceTagAssignment)。
  • 优先使用 SQLAlchemy Core 的insert/update/select配合session.execute()/session.scalars();ORM 的session.add()仍然允许。例如 agent_version_db.py 中大量使用session.execute(insert(AgentInfo).values(...))这类 Core 写法。
  • 创建:设置创建/更新行为者与delete_flag='N',时间戳通过设计好的默认值填充(对应TableBase的server_default=func.now())。
  • 更新:通常覆盖行、刷新更新者/时间、保留创建字段。
  • 业务表使用软删除:置delete_flag='Y'、更新行为者与时间;当关系契约要求时,在同一事务内软删除从属行。
  • 常规读操作过滤delete_flag='N'(包括相关 join);有共享活跃行过滤器时复用之,不要假设当前数据层会自动注入;访问历史/已删除行必须显式。
  • 版本保留类资源:考虑在单个事务内插入新版本并软删除旧版本;明确稳定/版本特定引用以及读操作应返回哪条记录;不要给每次更新都强加版本化。

九、事务所有权与异常传播

backend/database/client.py 的get_db_session是这套约定的实现核心:

@contextmanager def get_db_session(db_session=None): session = db_client.session_maker() if db_session is None else db_session try: yield session if db_session is None: session.commit() except Exception as e: if db_session is None: session.rollback() logger.error(f"Database operation failed: {str(e)}") raise e finally: if db_session is None: session.close()

由此得到四条规则:

  1. 使用with get_db_session() as session:;调用方需要自持事务时,传入已有的 session(共享 session 形式)。
  2. CRUD 函数绝不调用commit()/rollback()/close()——生命周期归 session 的所有者;上下文管理器对显式传入的 session 不做提交/回滚/关闭(上面代码中所有if db_session is None分支保证了这一点)。
  3. 异常从 CRUD 模块直接抛出。中央 session 管理负责回滚、记录日志、重抛,并关闭它自己拥有的 session——注意client.py中raise e重抛的是原始异常。
  4. 如需要,可在 backend/consts/exceptions.py 中定义共享数据库异常并验证其抛出边界。文档特别澄清:当前 client 重抛的是原始异常,不会自动构造DatabaseOperationError——这一点与源码相符,exceptions.py 中目前也没有DatabaseOperationError类,只有DatabaseError = Exception这样的通用别名。

client.py中还有一个可佐证“调用方持有 session”模式的实现:监控模块使用独立隔离的引擎与会话管理(get_monitoring_db_session,独立连接池pool_size=3),其提交/回滚语义与主会话完全同构。

十、SQL 迁移:deploy/sql/ 的治理机制

10.1 不可变文件与新增迁移

  • 任何名为init.sql或文件名含merged_migrations的文件都已并入目标分支,是不可变的:禁止修改、重命名、删除。当前 deploy/sql/ 下实际存在init.sql及v2.2_merged_migrations.sql…v2.7.0_merged_migrations.sql等合并文件。
  • 新增变更在 deploy/sql/migrations/ 下添加版本化迁移文件,遵循当前命名/版本约定(如v2.6.1_002_agent_protocol_repair_retry.sql);应用版本号取自 backend/consts/const.py 的APP_VERSION。

10.2 迁移文件应有的内容

一次完整的迁移交付需要逐项核验(原文档清单):命名/注释、类型上限、JSONB 校验、审计字段维护、引用/唯一性行为、软删除/版本化、查询索引、事务所有权、迁移兼容性。在外部数据库执行需要对该动作的授权。

一个真实迁移文件展示了“幂等 + 显式注释”的标准写法(v2.6.1_002_agent_protocol_repair_retry.sql):

ALTER TABLE nexent.ag_tenant_agent_t ADD COLUMN IF NOT EXISTS enable_protocol_repair_retry BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE nexent.ag_tenant_agent_t ALTER COLUMN enable_protocol_repair_retry SET DEFAULT FALSE; COMMENT ON COLUMN nexent.ag_tenant_agent_t.enable_protocol_repair_retry IS 'Whether this agent uses strict output validation and silent protocol repair';

10.3 执行器如何决定“跳过还是重跑”

执行逻辑集中在 deploy/common/run-sql-migrations.sh,配合 deploy/sql/migrations/README.md 的说明可以还原完整机制:

  • 迁移 ID 即文件名,当前文件 SHA256 校验和存入nexent.schema_migrations表(字段:migration_id、checksum、status、executed_at、app_version、source_file,见 run-sql-migrations.sh)。
  • 文件按*.sql发现并以版本感知排序(sort -V);无记录则执行并记为applied;校验和相同则跳过;校验和不同则再次执行,并更新校验和、执行时间、应用版本与源文件。
  • 级联重放(cascading re-apply):一旦某个文件因校验和变化被重放,本次部署会话内其后的所有文件(包括校验和未变的)都被标记为 dirty 强制重跑。原因是前链中的破坏性语句(如DROP COLUMN)可能把 schema 回滚到“后面的文件原本在补偿”的状态,跳过它们会导致不一致。该标志是单向的:触发后直到本次部署结束才保持,下一次无变更的部署从干净的标志开始。
  • init.sql每次启动无条件执行,不参与级联,因此其语句必须保持幂等(append_init_sql)。
  • 整个迁移计划被pg_advisory_lock(hashtext('nexent_sql_migrations'))包住(run_migrate_mode),避免多实例并发执行;另有--wait模式供其他容器轮询等待迁移达到目标状态。
  • 正因为“改一个文件就会重跑”,迁移 SQL 必须保持幂等,推荐模式为CREATE TABLE IF NOT EXISTS、ALTER TABLE ... ADD COLUMN IF NOT EXISTS与冲突安全的插入(见 migrations README)。
  • 已部署的*_merged_migrations.sql一律不可再改——哪怕是注释级别的编辑,也会触发级联并逐个重放所有后续文件,在大合并上可能耗时极长;合并之后的任何变更都应使用新的版本化文件(如v2.6.0_xxxx_*.sql)。

结语:一张自检清单

原文档结尾的核验清单可以当作每次 schema 变更前的自查表:命名与注释、类型上限、JSONB 校验、审计字段维护、引用/唯一性、软删除/版本化、索引、事务所有权、迁移兼容性。Nexent 的做法是把这些约定同时落在三处——设计规范(本文来源的 reference 文档)、ORM 层(TableBase+ Core CRUD +get_db_session所有权模型)与部署层(校验和驱动、级联重放保护的版本化迁移)——三层互为证据,这也是在贡献 Nexent 数据库相关代码时值得对照的整体框架。

  • 人工智能
  • AI Agent
  • Agent 编排
  • 低代码
  • AI 技能
  • RAG

【免费下载链接】nexent

Nexent 是一个开源智能体SDK和平台,能够将描述流程的自然语言转化为完整的多模态服务 —— 无需编排,无需复杂拖拉拽。基于 MCP 工具生态系统构建,Nexent 提供灵活的模型集成、可扩展的数据处理和强大的知识库管理。我们的目标很简单:将数据、模型和工具整合到一个智能中心中,让任何人都能轻松地将 Nexent 集成到项目中,使日常工作流程更智能、更互联。

项目地址:https://gitcode.com/ModelEngine/nexent
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询