数据库模式与实体关系
本页是 Chameleon 数据层的“总览入口”,用于回答三个问题:
- 系统有哪些核心实体
- 实体之间如何关联
- 查询与检索如何落在索引和字段设计上
数据层总体设计
当前系统采用三层存储协作:
| 层级 | 技术 | 主要职责 |
|---|---|---|
| 关系型元数据层 | PostgreSQL + SQLAlchemy | 会话、消息、Agent、知识源、文档、摄取任务、手动 inspect run/result、文本块 |
| 向量索引层 | Qdrant | 存储向量与检索 payload |
| 对象文件层 | MinIO | 存储原始上传文档 |
其中 PostgreSQL 是业务真相源,Qdrant 和 MinIO 分别承担检索性能与文件持久化。
ORM 代码组织
当前仓库继续保持 ai_service.storage.models 作为稳定兼容入口,同时通过 Alembic 演进业务 schema。当前 canonical head 为 fa12c4d6e8b9_add_agent_public_access_and_api_keys.py;它在既有 manual_inspection_runs / manual_inspection_results、scheduled_tasks / scheduled_task_runs 等 domain 之上,继续把 Agent public access control 与 Fusion Agent API key inventory 纳入统一数据模型:
ai_service/storage/models.py仍然是稳定兼容入口,供服务层、测试与 Alembic 继续导入- 具体 ORM 模型与数据访问函数已经拆分到
ai_service/storage/model_domains/ ai_service.storage.models被导入时,仍会一次性加载全部 domain 模块并完成Base.metadata注册
管理员访问控制域
后台控制面现在新增 admin_access 领域模型,用于承载独立的管理员身份、RBAC、敏感操作验证和审计:
| 表 | 作用 | 关键字段 |
|---|---|---|
admin_users |
管理员账号主表 | email、username、password_hash、role_code、status、is_protected |
admin_permission_grants |
子管理员权限授予表 | admin_user_id、permission_code、is_active、granted_by_admin_id |
admin_refresh_tokens |
可撤销后台会话、refresh token 哈希,以及 access token 的服务端绑定锚点 | admin_user_id、token_hash、expires_at、revoked_at |
admin_second_factor_challenges |
敏感操作 challenge | admin_user_id、purpose_code、nonce_hash、expires_at、used_at |
admin_action_audits |
管理员认证、写操作与拒绝访问审计 | actor_admin_id、target_admin_id、action_code、module_code、result_code |
admin_system_settings |
数据库托管的后台运行期设置 | config_key、config_value_json、updated_by_admin_id |
实现约束:
admin_users.email与admin_users.username唯一。admin_users.role_code仅允许super_admin与sub_admin。super_admin的有效权限由服务端动态解析,不依赖 grant 记录。admin_permission_grants对(admin_user_id, permission_code)建唯一约束,用于复用已有 grant 并切换is_active。- refresh token 与 challenge 在数据库中都只保存哈希值或一次性 nonce 哈希,不保存前端可直接复用的明文。
- access token 不再是纯无状态信任;服务端会通过
admin_refresh_tokens记录校验其绑定会话是否仍处于未撤销状态。 admin_action_audits会记录认证失败、权限拒绝和敏感操作的前后快照,作为后台安全审计真相源。
当前 Alembic head 是 fa12c4d6e8b9_add_agent_public_access_and_api_keys.py。
Fusion Public Access 扩展
Fusion public access 现在直接建模在统一 Agent 控制面,而不是额外维护第二套 public registry。
| 表 | 新字段 / 实体 | 作用 |
|---|---|---|
agents |
public_access_mode |
live public access policy,控制 admin_only / public_anonymous / public_api_key |
agent_versions |
public_access_mode |
snapshot public access policy,确保 publish / rollback 后 public 行为与版本一致 |
agent_api_keys |
id、agent_id、key_label、key_prefix、token_hash、expires_at、last_used_at、created_by_admin_id、revoked_by_admin_id、revoked_at |
Fusion Agent 的 hash-only API key inventory |
fusion_runs |
authenticated_agent_api_key_id |
记录 public API key 鉴权成功后由哪个 Agent API key 发起了该 run |
实现约束:
- 历史 Agent 迁移后统一回填
public_access_mode='admin_only' token_hash唯一,明文 key 只在创建响应中返回一次- 单个 Fusion Agent 可以拥有多个未撤销 key,以支持无停机轮换
- public Fusion run 成功使用 key 时,会把认证来源记录到
fusion_runs.authenticated_agent_api_key_id
ER 图
erDiagram
AGENTS ||--o{ SESSIONS : "owns"
SESSIONS ||--o{ MESSAGES : "contains"
SESSIONS ||--o{ SESSION_TAKEOVER_EVENTS : "audits"
SESSIONS ||--o{ CONVERSATION_TURNS : "observes"
CONVERSATION_TURNS ||--o| TURN_CONTEXT_SNAPSHOTS : "captures"
AGENTS ||--o{ AGENT_VERSIONS : "snapshots"
AGENTS ||--o{ AGENT_EVALUATION_RUNS : "evaluates"
AGENT_EVALUATION_RUNS ||--o{ AGENT_EVALUATION_RESULTS : "records"
AGENTS ||--o{ SCHEDULED_TASKS : "schedules"
AGENTS ||--o{ AGENT_KNOWLEDGE_LINKS : "mounts"
KNOWLEDGE_SOURCES ||--o{ AGENT_KNOWLEDGE_LINKS : "mounted_by"
KNOWLEDGE_SOURCES ||--o{ DOCUMENTS : "contains"
KNOWLEDGE_SOURCES ||--o{ INGESTION_JOBS : "has_jobs"
KNOWLEDGE_SOURCES ||--o{ MANUAL_INSPECTION_RUNS : "inspects"
AGENTS ||--o{ INGESTION_JOBS : "scoped_jobs"
KNOWLEDGE_SOURCES ||--o{ DOCUMENT_CHUNKS : "contains_chunks"
AGENTS ||--o{ DOCUMENT_CHUNKS : "scoped_chunks"
DOCUMENTS ||--o{ MANUAL_INSPECTION_RESULTS : "reported_in"
MANUAL_INSPECTION_RUNS ||--o{ MANUAL_INSPECTION_RESULTS : "records"
SESSIONS ||--o{ SCHEDULED_TASKS : "originates"
CONVERSATION_TURNS ||--o{ SCHEDULED_TASKS : "creates"
SCHEDULED_TASKS ||--o{ SCHEDULED_TASK_RUNS : "fires"
SESSIONS ||--o{ SCHEDULED_TASK_RUNS : "targets"
CONVERSATION_TURNS ||--o{ SCHEDULED_TASK_RUNS : "produces"
AGENTS {
string id PK
string name
string status
string model_name
string model_provider
float model_temperature
string orchestrator_key
text model_routing_config_json
text response_grounding_config_json
bool human_takeover_enabled
datetime created_at
datetime updated_at
}
AGENT_VERSIONS {
string id PK
string agent_id FK
int version_number
string model_name
string model_provider
float model_temperature
string orchestrator_key
text model_routing_config_json
text response_grounding_config_json
bool human_takeover_enabled
datetime created_at
}
AGENT_EVALUATION_RUNS {
string id PK
string agent_id FK
string agent_version_id FK
string dataset_id FK
string status
string model_name
string model_provider
float model_temperature
text model_routing_config_json
int items_total
int items_done
int generation_total_tokens
int judge_total_tokens
text generation_usage_breakdown_json
text judge_usage_breakdown_json
datetime started_at
datetime completed_at
datetime created_at
datetime updated_at
}
AGENT_EVALUATION_RESULTS {
string id PK
string run_id FK
string dataset_item_id FK
string status
int generation_total_tokens
string generation_usage_source
int judge_total_tokens
string judge_usage_source
int latency_ms
datetime created_at
}
SCHEDULED_TASKS {
string id PK
string agent_id FK
string source_session_id FK
string source_turn_id FK
string schedule_kind
string timezone_name
string target_mode
string status
string conflict_policy
datetime next_run_at
datetime last_run_at
datetime last_successful_run_at
string lease_token
datetime worker_heartbeat_at
int failure_count
text schedule_payload_json
text payload_json
datetime created_at
datetime updated_at
}
SCHEDULED_TASK_RUNS {
string id PK
string task_id FK
string target_session_id FK
string conversation_turn_id FK
string status
datetime scheduled_for
datetime started_at
datetime completed_at
int attempt_count
string lease_token
datetime worker_heartbeat_at
text error_message
string trigger_event_name
datetime created_at
datetime updated_at
}
SESSIONS {
string id PK
string status
string agent_id FK
string agent_id_snapshot
string agent_name_snapshot
string takeover_owner_id_snapshot
string takeover_owner_name_snapshot
datetime takeover_started_at
datetime takeover_released_at
string model_name
string model_provider
float model_temperature
datetime created_at
datetime updated_at
}
MESSAGES {
string id PK
string session_id FK
string role
text content
string sender_id_snapshot
string sender_name_snapshot
datetime created_at
}
SESSION_TAKEOVER_EVENTS {
string id PK
string session_id FK
string event_type
string operator_id_snapshot
string operator_name_snapshot
text payload_json
datetime created_at
}
CONVERSATION_TURNS {
string id PK
string session_id FK
string agent_id FK
string agent_id_snapshot
string agent_name_snapshot
string trace_id
int input_tokens
int output_tokens
int total_tokens
string token_usage_source
datetime created_at
}
TURN_CONTEXT_SNAPSHOTS {
string id PK
string turn_id FK
text context_payload_json
text rag_context_json
text skill_context_json
text mcp_context_json
}
KNOWLEDGE_SOURCES {
string id PK
string name
string storage_type
string status
string default_chunking_strategy
text default_chunking_params
datetime created_at
datetime updated_at
}
AGENT_KNOWLEDGE_LINKS {
string id PK
string agent_id FK
string source_id FK
bool is_active
int priority
datetime created_at
datetime updated_at
}
DOCUMENTS {
string id PK
string source_id FK
string filename
string content_type
int size_bytes
string minio_object_key
string status
string vector_status
int expected_point_count
int actual_point_count
datetime vector_verified_at
text error_message
text metadata_json
datetime created_at
datetime updated_at
}
MANUAL_INSPECTION_RUNS {
string id PK
string source_id FK
string inspection_mode
string status
text document_filter_json
string candidate_chunking_strategy
text candidate_chunking_params_json
int documents_total
int documents_done
text summary_json
text error_message
datetime created_at
datetime updated_at
datetime completed_at
}
MANUAL_INSPECTION_RESULTS {
string id PK
string inspection_run_id FK
string document_id FK "nullable"
string document_filename
string document_processing_status
string current_vector_status
int expected_point_count
int actual_point_count
datetime vector_verified_at
bool snapshot_available
int preview_chunk_count
int preview_chunk_delta
string recommendation_code
text recommendation_reason
string error_code
text error_message
datetime created_at
}
INGESTION_JOBS {
string id PK
string source_id FK
string agent_id FK
string status
string status_message
int documents_total
int documents_done
int chunks_total
int chunks_done
int chunk_size
int chunk_overlap
string chunking_strategy
text chunking_params
string embedding_model
text error_message
datetime created_at
datetime updated_at
}
DOCUMENT_CHUNKS {
string id PK
string source_id FK
string agent_id FK
string document_name
int chunk_index
text content
text metadata_json
string chunking_strategy
datetime created_at
}
实体分组说明
会话域
sessions:会话主表,保存会话状态、会话级模型覆盖参数,以及 Agent 的 live FK 与历史快照。messages:会话消息流水,按session_id聚合历史上下文;人工回复会额外保存 sender 快照。session_takeover_events:会话接管审计表,记录接管、强制接管、释放与人工回复事件。conversation_turns:Turn 级观测表,记录每次用户输入/助手回复的 Agent、trace、模型信息,以及 turn 级 generation usage 汇总;同时通过exclude_from_request_limits区分该 turn 是否参与 request-limit 统计。turn_context_snapshots:Turn 上下文快照表,保存 prompt/context/rag/skill/mcp 相关观测载荷。
Agent 与知识源域
agents:AI 角色配置,包含系统提示词、默认模型参数,以及chatAgent 专用的orchestrator_key、model_routing_config_json、response_grounding_config_json、human_takeover_enabled、固定响应、MCP 响应配置与用量限制字段max_concurrent_requests/max_total_requests/max_requests_per_day/max_total_tokens_daily/max_total_tokens_monthly/max_cost_usd_daily/max_cost_usd_monthly/max_tool_calls_total_per_request。agent_versions:Agent 不可变历史快照,保存包含orchestrator_key、model_routing_config_json、response_grounding_config_json、human_takeover_enabled、固定响应配置与成本控制字段在内的完整配置快照。agent_request_leases:chatAgent 运行时并发占位表,记录每个进行中的请求 lease,并用于统计active_request_count与回收异常遗留的占位。agent_evaluation_runs:评测运行快照,保存一次离线执行使用的agent_version绑定、主模型三元组、Judge runtime 快照、运行进度,以及 generation/judge 的 run 级 token rollup 和来源 breakdown。agent_evaluation_results:评测样本结果账本,保存 prediction、规则分数、Judge 分数,以及 generation/judge 的 item 级 usage。knowledge_sources:知识库逻辑分组,保存默认分块策略,以及source_kind/managed_for_agent_id等托管纠错来源标识。agent_knowledge_links:多对多挂载关系,包含priority与is_active。knowledge_corrections:管理员纠错真相源,保存原始问题/答案快照、标准答案、人工回复关联、托管文档与 ingestion job 状态。
文档摄取域
documents:上传文件元信息,关联 MinIO 对象键,并分别保存处理状态(status)与检索向量状态(vector_status)。ingestion_jobs:异步摄取任务状态,记录进度计数与status_message。document_chunks:文本分块结果,供向量化和检索回溯使用;同时保留document_id便于按托管纠错文档做清理和回溯。
手动 Inspect 域
manual_inspection_runs:source 级手动 inspect 运行账本,记录 inspect 模式、文档过滤范围、候选 chunking 配置、进度计数、summary 聚合、run 级错误和完成时间。manual_inspection_results:每个 inspect run 下的文档级快照,记录文档处理状态、live vector 状态、preview chunk 统计、recommendation、snapshot_missing/baseline_unavailable/qdrant_unavailable等显式异常码。document_id是可空的 live 引用;当源文档被删除时,inspect 历史会保留并把该字段置空,只依赖document_filename等快照字段继续展示。
定时任务与执行审计域
scheduled_tasks:Conversation 内创建的 durable timer 定义,保存 schedule kind、timezone、target mode、conflict policy、下一次执行时间,以及 worker lease/heartbeat 状态。scheduled_task_runs:每次触发尝试的执行账本,保存queued/running/deferred/succeeded/failed/cancelled状态、目标 session、conversation turn 回写、attempt 计数与错误信息;token 统计通过conversation_turn_id派生,不在本表重复存储。
关键索引与约束
| 名称 | 类型 | 作用 |
|---|---|---|
ix_agent_knowledge_unique |
复合唯一索引 | 防止同一 Agent 重复挂载同一知识源 |
ix_documents_source_status |
复合索引 | 加速按知识源和状态筛选文档 |
ix_manual_inspection_runs_source_created |
复合索引 | 支撑 inspect history 按 source + 创建时间倒序读取 |
ix_manual_inspection_runs_source_status |
复合索引 | 支撑按 source 和运行状态轮询 inspect runs |
ix_manual_inspection_results_run_document_unique |
复合唯一索引 | 防止同一 inspect run 为同一文档重复落盘结果 |
ix_document_chunks_agent_source |
复合索引 | 加速按 Agent 与知识源过滤分块 |
ix_knowledge_corrections_source_status |
复合索引 | 加速按托管纠错知识源和发布状态查询 |
ix_knowledge_corrections_agent_updated |
复合索引 | 加速按 Agent 查看最近纠错记录 |
ix_agent_request_leases_agent_active |
复合索引 | 加速按 Agent 统计未释放 request lease,支撑并发配额判断 |
ix_scheduled_tasks_status_next_run |
复合索引 | 加速 dispatcher 按状态与 next_run_at claim due task |
ix_scheduled_task_runs_status_heartbeat |
复合索引 | 加速 supervisor 回收 heartbeat 超时的运行中任务 |
多个 index=True 外键索引 |
普通索引 | 支撑会话、任务、文档等关联查询 |
历史 Agent 身份保留
sessions.agent_id与conversation_turns.agent_id是指向agents.id的 live 外键,仅在 Agent 存在时保持非空。sessions.agent_id_snapshot/agent_name_snapshot与conversation_turns.agent_id_snapshot/agent_name_snapshot用于保存历史身份。sessions.takeover_owner_id_snapshot/takeover_owner_name_snapshot/takeover_started_at/takeover_released_at用于保存当前或最近一次人工接管状态。messages.sender_id_snapshot/sender_name_snapshot用于持久化人工回复的操作者身份。agents.orchestrator_key与agent_versions.orchestrator_key用于保存chatAgent 的具体聊天编排器选择;历史空值会在 API 响应与迁移回填中归一化到默认值chameleon_chat_v1。agents.model_routing_config_json与agent_versions.model_routing_config_json用于保存chatAgent 的角色化模型路由策略;角色槽位为空时,运行时会回退到主模型三元组。agents.response_grounding_config_json与agent_versions.response_grounding_config_json用于保存chatAgent 的最终回复防幻觉策略,并随 Agent 版本快照一起回滚。agents.human_takeover_enabled与agent_versions.human_takeover_enabled用于保存会话是否允许人工接管,并随 Agent 版本快照一起回滚。agents.hide_rag_source_filename与agent_versions.hide_rag_source_filename用于保存聊天态 RAG 注入是否对模型隐藏document_name,并随 Agent 版本快照一起回滚;后台 provenance 与审计追踪仍保留原始文件名。agents.max_concurrent_requests与agent_versions.max_concurrent_requests用于保存chatAgent 的并发请求上限;空值表示不限流,并随 Agent 版本快照一起回滚。agents.max_total_requests与agent_versions.max_total_requests用于保存chatAgent 的累计请求上限;空值表示不限量,并随 Agent 版本快照一起回滚。agents.max_requests_per_day与agent_versions.max_requests_per_day用于保存chatAgent 当前 UTC 日的请求上限;空值表示不限制,并随 Agent 版本快照一起回滚。agents.max_total_tokens_daily/max_total_tokens_monthly及对应agent_versions字段用于保存chatAgent 的 UTC 日 / 月 token 累计上限;统计来源为已持久化conversation_turns.total_tokens。agents.max_cost_usd_daily/max_cost_usd_monthly及对应agent_versions字段用于保存chatAgent 的 UTC 日 / 月估算成本上限;估算基于模型单价与持久化的 input/output token。agents.max_tool_calls_total_per_request与agent_versions.max_tool_calls_total_per_request用于保存单次 chat 请求内 Skill + MCP 的总执行次数上限;空值表示不限,并随 Agent 版本快照一起回滚。agent_evaluation_runs.model_routing_config_json用于快照一次评测运行的角色化模型路由策略;若评测请求显式提供model_*覆盖,则该字段保持为空,因为本次执行已折叠为单模型路径。agent_evaluation_runs.agent_version_id指向 run 创建当时生成的不可变agent_versions快照;evaluation review 与后台 worker 都应优先基于该快照解释和复现实验配置,而不是读取后续被修改过的 live Agent。agent_evaluation_runs.started_at表示 Run 首次进入执行态的时间。agent_evaluation_runs.completed_at是 dataset evaluation run 的规范结束时间;系统不应再为同一语义新增ended_at或其他别名字段。agents.message_type_response_config_json与agent_versions.message_type_response_config_json用于保存chatAgent 的rule/form/other固定响应配置及其版本快照;每个message_type下既可保存默认enabled + response_text兜底文案,也可保存message_key_responses[]明细映射。message_key_responses[]中的message_key采用去首尾空格、大小写不敏感的精确匹配;同一message_type下归一化后不得重复。conversation_turns.model_name/model_provider/model_temperature仍然只保存最终回复阶段实际使用的模型;更细粒度的阶段路由历史保存在turn_context_snapshots.context_payload_json内的model_routing。conversation_turns.input_tokens/output_tokens/total_tokens/token_usage_source是新生成 AI turn 的 token 真相源;scheduled task 控制面必须从关联 turn 读取 usage,而不是在scheduled_task_runs上维护第二份副本。conversation_turns.exclude_from_request_limits=true仅表示该 turn 不参与 Request Limit 统计,不影响消息、turn 或 context snapshot 的可观测性保留。agent_request_leases只记录执行中的 live 请求,不承担历史访问量真相源;累计请求量仍以conversation_turns为准,但 request-limit 相关计数会排除exclude_from_request_limits=true的 turn。agent_evaluation_results会分别持久化generation_*与judge_*item 级 usage 列,成功和失败样本都遵循同一写时 fallback 规则。agent_evaluation_runs会分别持久化generation_*与judge_*run 级聚合列,以及*_usage_breakdown_json来源质量摘要。conversation_turns与turn_context_snapshots只覆盖 AI 生成回复;人工 takeover 期间的客户消息与人工回复只写入messages/session_takeover_events,不会伪造 AI turn。knowledge_corrections.manual_reply_message_id只关联人工纠正答复消息,不会反向改写conversation_turns或 AI 原始消息内容。knowledge_sources.source_kind="managed_correction"与managed_for_agent_id用于标记系统自动维护的纠错知识源;Admin UI 必须把它与普通上传知识源区分展示。managed_correction来源是服务端托管边界:普通上传、普通删除和通用 ingestion 入口都不应再允许人工直接改写。- 当 Agent 被删除时,系统会先回填 snapshot 字段,再把 live FK 置空;这样历史会话列表与 Turn 明细仍可展示原始 Agent。
托管纠错知识源约定
knowledge_sources
| 字段 | 说明 |
|---|---|
source_kind |
逻辑来源类型;当前值包括 general_upload 与 managed_correction |
managed_for_agent_id |
当来源是系统托管纠错知识源时,指向所属 Agent |
knowledge_corrections
| 字段 | 说明 |
|---|---|
source_session_id / source_turn_id |
来源会话与 AI turn |
manual_reply_message_id |
当前会话纠正答复消息 id(若已发送) |
document_id / ingestion_job_id |
当前生效文档与最近一次发布任务;重发失败时 document_id 继续指向旧的已生效版本 |
status |
draft / publishing / published / failed |
revision_number |
同一 turn 的纠错修订号 |
original_user_message / original_ai_answer |
不可变原始快照 |
corrected_question / corrected_answer |
进入知识库检索的标准问答 |
correction_note |
可选运营边界说明 |
last_error_message |
最近一次发布失败细节 |
PostgreSQL 与 Qdrant 的映射
摄取时,系统会在 PostgreSQL 写入 document_chunks,并同步向量到 Qdrant。典型 payload 结构如下:
{
"chunk_id": "uuid",
"source_id": "knowledge_source_id",
"document_id": "document_id",
"document_name": "example.pdf",
"chunk_index": 0,
"agent_id": "agent_id"
}
检索阶段可按 source_id、source_ids、agent_id、document_id 做过滤,保证 Agent 级知识隔离。
当同一 Agent 同时挂载普通知识源与托管纠错知识源时,RAG 最终排序会额外考虑:
source_kind == managed_correction的来源优先;- 其次按
agent_knowledge_links.priority; - 最后再看向量分数 / rerank 分数。
documents 中与向量一致性相关的关键字段:
status:处理链路状态,表示上传/处理中/处理完成/处理失败。vector_status:检索链路状态,表示向量待核验、健康、需重建或核验失败。expected_point_count:系统期望该文档在 Qdrant 中存在的 point 数。actual_point_count:最近一次核验时在 Qdrant 中实际观测到的 point 数。vector_verified_at:最近一次向量核验时间。
代码入口
- ORM 模型:
ai_service/storage/models.py - 会话与引擎:
ai_service/utils/database.py - 向量服务:
ai_service/storage/qdrant_client.py - 对象存储服务:
ai_service/storage/minio_client.py