refactor(schema): rag 模块全部数据表加 rag_ 前缀(模块跨应用复用防冲突)

- models/ 重建为正确表定义格式(summary+fields+indexes),覆盖全部 11 个表
  修正原 models/ 误放 CRUD 格式副本(缺 summary)导致 json2ddl 100% 失败被静默忽略
- json/ 6 个 CRUD 定义 tblname 加前缀 + 文件改名
- 129 处 SQL/sor 表名加 rag_ 前缀(URL 路径 knowledge_bases_list 保持不变)
- 新增 init/migrate_rag_prefix.sql 幂等 RENAME TABLE 迁移
This commit is contained in:
ymq 2026-08-25 14:24:37 +08:00
parent fb2cf3e703
commit 789960a1cf
55 changed files with 1789 additions and 409 deletions

View File

@ -0,0 +1,54 @@
-- rag 模块表名加 rag_ 前缀(模块跨应用复用,避免与业务表冲突)
-- 幂等:仅当旧表存在且新表不存在时才 RENAME。
-- 用法: mysql -h <host> -u<user> -p<pwd> <db> < migrate_rag_prefix.sql
DELIMITER $$
DROP PROCEDURE IF EXISTS rag_rename_tables$$
CREATE PROCEDURE rag_rename_tables()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE oldname VARCHAR(64);
DECLARE cur CURSOR FOR
SELECT t FROM (
SELECT 'knowledge_bases' AS t UNION ALL
SELECT 'documents' UNION ALL
SELECT 'document_chunks' UNION ALL
SELECT 'tags' UNION ALL
SELECT 'media_tags' UNION ALL
SELECT 'entities' UNION ALL
SELECT 'entity_relations' UNION ALL
SELECT 'engine_configs' UNION ALL
SELECT 'subscriptions' UNION ALL
SELECT 'org_storage_limits' UNION ALL
SELECT 'usage_logs'
) x;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur;
read_loop: LOOP
FETCH cur INTO oldname;
IF done THEN LEAVE read_loop; END IF;
SET @has_old = (SELECT COUNT(*) FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = oldname);
SET @has_new = (SELECT COUNT(*) FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = CONCAT('rag_', oldname));
IF @has_old = 1 AND @has_new = 0 THEN
SET @sql = CONCAT('RENAME TABLE `', oldname, '` TO `rag_', oldname, '`');
PREPARE st FROM @sql;
EXECUTE st;
DEALLOCATE PREPARE st;
SELECT CONCAT('RENAMED: ', oldname, ' -> rag_', oldname) AS result;
ELSEIF @has_new = 1 THEN
SELECT CONCAT('SKIP (already migrated): rag_', oldname) AS result;
ELSE
SELECT CONCAT('SKIP (no such table): ', oldname) AS result;
END IF;
END LOOP;
CLOSE cur;
END$$
DELIMITER ;
CALL rag_rename_tables();
DROP PROCEDURE rag_rename_tables;

View File

@ -1,25 +0,0 @@
{
"tblname": "documents",
"params": {
"title": "文档管理",
"browserfields": [
{"field": "file_name", "title": "文件名", "width": "25%"},
{"field": "file_type", "title": "类型", "width": "10%"},
{"field": "file_size", "title": "大小", "width": "10%"},
{"field": "chunk_count", "title": "分片数", "width": "8%"},
{"field": "status", "title": "状态", "width": "10%"},
{"field": "kb_id", "title": "知识库", "width": "17%"},
{"field": "created_at", "title": "上传时间", "width": "20%"}
],
"editfields": [
{"field": "file_name", "uitype": "Text", "required": true},
{"field": "kb_id", "uitype": "Text", "required": true},
{"field": "file_type", "uitype": "Text"}
],
"searchfields": ["file_name"],
"sort": "created_at desc",
"data_filter": {
"org_id": "{{userorgid}}"
}
}
}

View File

@ -1,26 +0,0 @@
{
"tblname": "engine_configs",
"params": {
"title": "引擎配置",
"browserfields": [
{"field": "engine_type", "title": "引擎类型", "width": "15%"},
{"field": "engine_name", "title": "名称", "width": "15%"},
{"field": "endpoint_url", "title": "服务地址", "width": "25%"},
{"field": "model_name", "title": "模型", "width": "15%"},
{"field": "is_default", "title": "默认", "width": "8%"},
{"field": "status", "title": "状态", "width": "10%"},
{"field": "org_id", "title": "机构", "width": "12%"}
],
"editfields": [
{"field": "engine_type", "uitype": "Text", "required": true},
{"field": "engine_name", "uitype": "Text", "required": true},
{"field": "endpoint_url", "uitype": "Text"},
{"field": "api_key", "uitype": "Text"},
{"field": "model_name", "uitype": "Text"},
{"field": "is_default", "uitype": "Text"},
{"field": "config_json", "uitype": "Text"}
],
"searchfields": ["engine_type", "engine_name", "endpoint_url"],
"sort": "engine_type, priority desc"
}
}

View File

@ -1,26 +0,0 @@
{
"tblname": "knowledge_bases",
"params": {
"title": "知识库管理",
"browserfields": [
{"field": "name", "title": "名称", "width": "20%"},
{"field": "description", "title": "描述", "width": "30%"},
{"field": "doc_count", "title": "文档数", "width": "10%"},
{"field": "total_size", "title": "大小", "width": "10%"},
{"field": "embedding_engine", "title": "向量引擎", "width": "15%"},
{"field": "created_at", "title": "创建时间", "width": "15%"}
],
"editfields": [
{"field": "name", "uitype": "Text", "required": true},
{"field": "description", "uitype": "Text"},
{"field": "embedding_engine", "uitype": "Text", "default": "clip-vith14"},
{"field": "vdb_collection", "uitype": "Text"},
{"field": "graph_name", "uitype": "Text"}
],
"searchfields": ["name", "description"],
"sort": "created_at desc",
"data_filter": {
"org_id": "{{userorgid}}"
}
}
}

View File

@ -1,17 +0,0 @@
{
"tblname": "org_storage_limits",
"params": {
"title": "机构存储限额",
"browserfields": [
{"field": "org_id", "title": "机构ID", "width": "35%"},
{"field": "limit_bytes", "title": "限额(字节)", "width": "35%"},
{"field": "updated_at", "title": "更新时间", "width": "30%"}
],
"editfields": [
{"field": "org_id", "uitype": "Text", "required": true},
{"field": "limit_bytes", "uitype": "Text", "required": true, "default": "104857600"}
],
"searchfields": ["org_id"],
"sort": "updated_at desc"
}
}

66
json/rag_documents.json Normal file
View File

@ -0,0 +1,66 @@
{
"tblname": "rag_documents",
"params": {
"title": "文档管理",
"browserfields": [
{
"field": "file_name",
"title": "文件名",
"width": "25%"
},
{
"field": "file_type",
"title": "类型",
"width": "10%"
},
{
"field": "file_size",
"title": "大小",
"width": "10%"
},
{
"field": "chunk_count",
"title": "分片数",
"width": "8%"
},
{
"field": "status",
"title": "状态",
"width": "10%"
},
{
"field": "kb_id",
"title": "知识库",
"width": "17%"
},
{
"field": "created_at",
"title": "上传时间",
"width": "20%"
}
],
"editfields": [
{
"field": "file_name",
"uitype": "Text",
"required": true
},
{
"field": "kb_id",
"uitype": "Text",
"required": true
},
{
"field": "file_type",
"uitype": "Text"
}
],
"searchfields": [
"file_name"
],
"sort": "created_at desc",
"data_filter": {
"org_id": "{{userorgid}}"
}
}
}

View File

@ -0,0 +1,81 @@
{
"tblname": "rag_engine_configs",
"params": {
"title": "引擎配置",
"browserfields": [
{
"field": "engine_type",
"title": "引擎类型",
"width": "15%"
},
{
"field": "engine_name",
"title": "名称",
"width": "15%"
},
{
"field": "endpoint_url",
"title": "服务地址",
"width": "25%"
},
{
"field": "model_name",
"title": "模型",
"width": "15%"
},
{
"field": "is_default",
"title": "默认",
"width": "8%"
},
{
"field": "status",
"title": "状态",
"width": "10%"
},
{
"field": "org_id",
"title": "机构",
"width": "12%"
}
],
"editfields": [
{
"field": "engine_type",
"uitype": "Text",
"required": true
},
{
"field": "engine_name",
"uitype": "Text",
"required": true
},
{
"field": "endpoint_url",
"uitype": "Text"
},
{
"field": "api_key",
"uitype": "Text"
},
{
"field": "model_name",
"uitype": "Text"
},
{
"field": "is_default",
"uitype": "Text"
},
{
"field": "config_json",
"uitype": "Text"
}
],
"searchfields": [
"engine_type",
"engine_name",
"endpoint_url"
],
"sort": "engine_type, priority desc"
}
}

View File

@ -0,0 +1,70 @@
{
"tblname": "rag_knowledge_bases",
"params": {
"title": "知识库管理",
"browserfields": [
{
"field": "name",
"title": "名称",
"width": "20%"
},
{
"field": "description",
"title": "描述",
"width": "30%"
},
{
"field": "doc_count",
"title": "文档数",
"width": "10%"
},
{
"field": "total_size",
"title": "大小",
"width": "10%"
},
{
"field": "embedding_engine",
"title": "向量引擎",
"width": "15%"
},
{
"field": "created_at",
"title": "创建时间",
"width": "15%"
}
],
"editfields": [
{
"field": "name",
"uitype": "Text",
"required": true
},
{
"field": "description",
"uitype": "Text"
},
{
"field": "embedding_engine",
"uitype": "Text",
"default": "clip-vith14"
},
{
"field": "vdb_collection",
"uitype": "Text"
},
{
"field": "graph_name",
"uitype": "Text"
}
],
"searchfields": [
"name",
"description"
],
"sort": "created_at desc",
"data_filter": {
"org_id": "{{userorgid}}"
}
}
}

View File

@ -0,0 +1,40 @@
{
"tblname": "rag_org_storage_limits",
"params": {
"title": "机构存储限额",
"browserfields": [
{
"field": "org_id",
"title": "机构ID",
"width": "35%"
},
{
"field": "limit_bytes",
"title": "限额(字节)",
"width": "35%"
},
{
"field": "updated_at",
"title": "更新时间",
"width": "30%"
}
],
"editfields": [
{
"field": "org_id",
"uitype": "Text",
"required": true
},
{
"field": "limit_bytes",
"uitype": "Text",
"required": true,
"default": "104857600"
}
],
"searchfields": [
"org_id"
],
"sort": "updated_at desc"
}
}

View File

@ -0,0 +1,90 @@
{
"tblname": "rag_subscriptions",
"params": {
"title": "订阅管理",
"browserfields": [
{
"field": "org_id",
"title": "机构",
"width": "15%"
},
{
"field": "plan_name",
"title": "套餐",
"width": "15%"
},
{
"field": "disk_quota_bytes",
"title": "磁盘配额",
"width": "12%"
},
{
"field": "disk_used_bytes",
"title": "已用磁盘",
"width": "12%"
},
{
"field": "doc_used",
"title": "文档数",
"width": "10%"
},
{
"field": "start_date",
"title": "开始",
"width": "12%"
},
{
"field": "end_date",
"title": "结束",
"width": "12%"
},
{
"field": "status",
"title": "状态",
"width": "12%"
}
],
"editfields": [
{
"field": "org_id",
"uitype": "Text",
"required": true
},
{
"field": "plan_name",
"uitype": "Text",
"required": true
},
{
"field": "disk_quota_bytes",
"uitype": "Text",
"default": "1073741824"
},
{
"field": "doc_quota",
"uitype": "Text"
},
{
"field": "kb_quota",
"uitype": "Text"
},
{
"field": "api_call_quota",
"uitype": "Text"
},
{
"field": "start_date",
"uitype": "Text"
},
{
"field": "end_date",
"uitype": "Text"
}
],
"searchfields": [
"org_id",
"plan_name"
],
"sort": "created_at desc"
}
}

55
json/rag_tags.json Normal file
View File

@ -0,0 +1,55 @@
{
"tblname": "rag_tags",
"params": {
"title": "标签管理",
"browserfields": [
{
"field": "name",
"title": "标签名",
"width": "25%"
},
{
"field": "color",
"title": "颜色",
"width": "15%"
},
{
"field": "kb_id",
"title": "知识库",
"width": "30%"
},
{
"field": "created_at",
"title": "创建时间",
"width": "30%"
}
],
"editfields": [
{
"field": "name",
"uitype": "Text",
"required": true,
"label": "标签名"
},
{
"field": "kb_id",
"uitype": "Text",
"required": true,
"label": "知识库ID"
},
{
"field": "color",
"uitype": "Text",
"default": "#3b82f6",
"label": "颜色(#hex)"
}
],
"searchfields": [
"name"
],
"sort": "created_at desc",
"data_filter": {
"org_id": "{{userorgid}}"
}
}
}

View File

@ -1,28 +0,0 @@
{
"tblname": "subscriptions",
"params": {
"title": "订阅管理",
"browserfields": [
{"field": "org_id", "title": "机构", "width": "15%"},
{"field": "plan_name", "title": "套餐", "width": "15%"},
{"field": "disk_quota_bytes", "title": "磁盘配额", "width": "12%"},
{"field": "disk_used_bytes", "title": "已用磁盘", "width": "12%"},
{"field": "doc_used", "title": "文档数", "width": "10%"},
{"field": "start_date", "title": "开始", "width": "12%"},
{"field": "end_date", "title": "结束", "width": "12%"},
{"field": "status", "title": "状态", "width": "12%"}
],
"editfields": [
{"field": "org_id", "uitype": "Text", "required": true},
{"field": "plan_name", "uitype": "Text", "required": true},
{"field": "disk_quota_bytes", "uitype": "Text", "default": "1073741824"},
{"field": "doc_quota", "uitype": "Text"},
{"field": "kb_quota", "uitype": "Text"},
{"field": "api_call_quota", "uitype": "Text"},
{"field": "start_date", "uitype": "Text"},
{"field": "end_date", "uitype": "Text"}
],
"searchfields": ["org_id", "plan_name"],
"sort": "created_at desc"
}
}

View File

@ -1,22 +0,0 @@
{
"tblname": "tags",
"params": {
"title": "标签管理",
"browserfields": [
{"field": "name", "title": "标签名", "width": "25%"},
{"field": "color", "title": "颜色", "width": "15%"},
{"field": "kb_id", "title": "知识库", "width": "30%"},
{"field": "created_at", "title": "创建时间", "width": "30%"}
],
"editfields": [
{"field": "name", "uitype": "Text", "required": true, "label": "标签名"},
{"field": "kb_id", "uitype": "Text", "required": true, "label": "知识库ID"},
{"field": "color", "uitype": "Text", "default": "#3b82f6", "label": "颜色(#hex)"}
],
"searchfields": ["name"],
"sort": "created_at desc",
"data_filter": {
"org_id": "{{userorgid}}"
}
}
}

View File

@ -1,25 +0,0 @@
{
"tblname": "documents",
"params": {
"title": "文档管理",
"browserfields": [
{"field": "file_name", "title": "文件名", "width": "25%"},
{"field": "file_type", "title": "类型", "width": "10%"},
{"field": "file_size", "title": "大小", "width": "10%"},
{"field": "chunk_count", "title": "分片数", "width": "8%"},
{"field": "status", "title": "状态", "width": "10%"},
{"field": "kb_id", "title": "知识库", "width": "17%"},
{"field": "created_at", "title": "上传时间", "width": "20%"}
],
"editfields": [
{"field": "file_name", "uitype": "Text", "required": true},
{"field": "kb_id", "uitype": "Text", "required": true},
{"field": "file_type", "uitype": "Text"}
],
"searchfields": ["file_name"],
"sort": "created_at desc",
"data_filter": {
"org_id": "{{userorgid}}"
}
}
}

View File

@ -1,26 +0,0 @@
{
"tblname": "engine_configs",
"params": {
"title": "引擎配置",
"browserfields": [
{"field": "engine_type", "title": "引擎类型", "width": "15%"},
{"field": "engine_name", "title": "名称", "width": "15%"},
{"field": "endpoint_url", "title": "服务地址", "width": "25%"},
{"field": "model_name", "title": "模型", "width": "15%"},
{"field": "is_default", "title": "默认", "width": "8%"},
{"field": "status", "title": "状态", "width": "10%"},
{"field": "org_id", "title": "机构", "width": "12%"}
],
"editfields": [
{"field": "engine_type", "uitype": "Text", "required": true},
{"field": "engine_name", "uitype": "Text", "required": true},
{"field": "endpoint_url", "uitype": "Text"},
{"field": "api_key", "uitype": "Text"},
{"field": "model_name", "uitype": "Text"},
{"field": "is_default", "uitype": "Text"},
{"field": "config_json", "uitype": "Text"}
],
"searchfields": ["engine_type", "engine_name", "endpoint_url"],
"sort": "engine_type, priority desc"
}
}

View File

@ -1,26 +0,0 @@
{
"tblname": "knowledge_bases",
"params": {
"title": "知识库管理",
"browserfields": [
{"field": "name", "title": "名称", "width": "20%"},
{"field": "description", "title": "描述", "width": "30%"},
{"field": "doc_count", "title": "文档数", "width": "10%"},
{"field": "total_size", "title": "大小", "width": "10%"},
{"field": "embedding_engine", "title": "向量引擎", "width": "15%"},
{"field": "created_at", "title": "创建时间", "width": "15%"}
],
"editfields": [
{"field": "name", "uitype": "Text", "required": true},
{"field": "description", "uitype": "Text"},
{"field": "embedding_engine", "uitype": "Text", "default": "clip-vith14"},
{"field": "vdb_collection", "uitype": "Text"},
{"field": "graph_name", "uitype": "Text"}
],
"searchfields": ["name", "description"],
"sort": "created_at desc",
"data_filter": {
"org_id": "{{userorgid}}"
}
}
}

View File

@ -1,17 +0,0 @@
{
"tblname": "org_storage_limits",
"params": {
"title": "机构存储限额",
"browserfields": [
{"field": "org_id", "title": "机构ID", "width": "35%"},
{"field": "limit_bytes", "title": "限额(字节)", "width": "35%"},
{"field": "updated_at", "title": "更新时间", "width": "30%"}
],
"editfields": [
{"field": "org_id", "uitype": "Text", "required": true},
{"field": "limit_bytes", "uitype": "Text", "required": true, "default": "104857600"}
],
"searchfields": ["org_id"],
"sort": "updated_at desc"
}
}

View File

@ -0,0 +1,129 @@
{
"summary": [
{
"name": "rag_document_chunks",
"title": "文档分片",
"primary": [
"id"
]
}
],
"fields": [
{
"name": "id",
"title": "ID",
"type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "doc_id",
"title": "文档ID",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "kb_id",
"title": "知识库ID",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "chunk_index",
"title": "分片序号",
"type": "int",
"nullable": "yes"
},
{
"name": "chunk_type",
"title": "分片类型",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "content",
"title": "文本内容",
"type": "text",
"nullable": "yes"
},
{
"name": "description",
"title": "描述",
"type": "text",
"nullable": "yes"
},
{
"name": "vector_id",
"title": "向量库ID",
"type": "str",
"length": 128,
"nullable": "yes"
},
{
"name": "start_offset",
"title": "起始偏移",
"type": "int",
"nullable": "yes"
},
{
"name": "end_offset",
"title": "结束偏移",
"type": "int",
"nullable": "yes"
},
{
"name": "time_start",
"title": "时间起始(秒)",
"type": "double",
"length": 10,
"dec": 3,
"nullable": "yes"
},
{
"name": "time_end",
"title": "时间结束(秒)",
"type": "double",
"length": 10,
"dec": 3,
"nullable": "yes"
},
{
"name": "metadata",
"title": "元数据JSON",
"type": "text",
"nullable": "yes"
},
{
"name": "created_at",
"title": "创建时间",
"type": "datetime",
"nullable": "yes"
}
],
"indexes": [
{
"name": "idx_chunk_doc",
"idxtype": "index",
"idxfields": [
"doc_id"
]
},
{
"name": "idx_chunk_kb",
"idxtype": "index",
"idxfields": [
"kb_id"
]
},
{
"name": "idx_chunk_vector",
"idxtype": "index",
"idxfields": [
"vector_id"
]
}
]
}

151
models/rag_documents.json Normal file
View File

@ -0,0 +1,151 @@
{
"summary": [
{
"name": "rag_documents",
"title": "文档",
"primary": [
"id"
]
}
],
"fields": [
{
"name": "id",
"title": "ID",
"type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "kb_id",
"title": "知识库ID",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "folder_id",
"title": "目录ID",
"type": "str",
"length": 32,
"nullable": "yes",
"default": ""
},
{
"name": "file_name",
"title": "文件名",
"type": "str",
"length": 255,
"nullable": "yes"
},
{
"name": "file_type",
"title": "文件类型",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "file_size",
"title": "文件大小",
"type": "int",
"nullable": "yes"
},
{
"name": "file_path",
"title": "文件路径",
"type": "str",
"length": 500,
"nullable": "yes"
},
{
"name": "mime_type",
"title": "MIME类型",
"type": "str",
"length": 128,
"nullable": "yes"
},
{
"name": "chunk_count",
"title": "分片数",
"type": "int",
"nullable": "yes"
},
{
"name": "embed_dim",
"title": "向量维度",
"type": "int",
"nullable": "yes",
"default": "1024"
},
{
"name": "metadata",
"title": "元数据JSON",
"type": "text",
"nullable": "yes"
},
{
"name": "status",
"title": "状态",
"type": "str",
"length": 16,
"nullable": "yes",
"default": "pending"
},
{
"name": "error_msg",
"title": "错误信息",
"type": "text",
"nullable": "yes"
},
{
"name": "org_id",
"title": "所属机构",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "created_by",
"title": "创建人",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "created_at",
"title": "创建时间",
"type": "datetime",
"nullable": "yes"
},
{
"name": "updated_at",
"title": "更新时间",
"type": "datetime",
"nullable": "yes"
}
],
"indexes": [
{
"name": "idx_doc_kb",
"idxtype": "index",
"idxfields": [
"kb_id"
]
},
{
"name": "idx_doc_org",
"idxtype": "index",
"idxfields": [
"org_id"
]
},
{
"name": "idx_doc_status",
"idxtype": "index",
"idxfields": [
"status"
]
}
]
}

View File

@ -0,0 +1,124 @@
{
"summary": [
{
"name": "rag_engine_configs",
"title": "引擎配置",
"primary": [
"id"
]
}
],
"fields": [
{
"name": "id",
"title": "ID",
"type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "org_id",
"title": "机构ID",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "engine_type",
"title": "引擎类型",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "engine_name",
"title": "引擎名称",
"type": "str",
"length": 64,
"nullable": "yes"
},
{
"name": "endpoint_url",
"title": "服务地址",
"type": "str",
"length": 255,
"nullable": "yes"
},
{
"name": "api_key",
"title": "API密钥",
"type": "str",
"length": 255,
"nullable": "yes"
},
{
"name": "model_name",
"title": "模型名称",
"type": "str",
"length": 128,
"nullable": "yes"
},
{
"name": "config_json",
"title": "扩展配置JSON",
"type": "text",
"nullable": "yes"
},
{
"name": "is_default",
"title": "是否默认",
"type": "int",
"nullable": "yes"
},
{
"name": "priority",
"title": "优先级",
"type": "int",
"nullable": "yes"
},
{
"name": "status",
"title": "状态",
"type": "str",
"length": 16,
"nullable": "yes",
"default": "active"
},
{
"name": "health_check_at",
"title": "最近健康检查",
"type": "datetime",
"nullable": "yes"
},
{
"name": "created_at",
"title": "创建时间",
"type": "datetime",
"nullable": "yes"
},
{
"name": "updated_at",
"title": "更新时间",
"type": "datetime",
"nullable": "yes"
}
],
"indexes": [
{
"name": "idx_eng_type",
"idxtype": "index",
"idxfields": [
"engine_type",
"is_default"
]
},
{
"name": "idx_eng_org",
"idxtype": "index",
"idxfields": [
"org_id",
"engine_type"
]
}
]
}

138
models/rag_entities.json Normal file
View File

@ -0,0 +1,138 @@
{
"summary": [
{
"name": "rag_entities",
"title": "实体",
"primary": [
"id"
]
}
],
"fields": [
{
"name": "id",
"title": "ID",
"type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "kb_id",
"title": "知识库ID",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "name",
"title": "实体名",
"type": "str",
"length": 255,
"nullable": "yes"
},
{
"name": "entity_type",
"title": "实体类型",
"type": "str",
"length": 64,
"nullable": "yes"
},
{
"name": "description",
"title": "描述",
"type": "text",
"nullable": "yes"
},
{
"name": "graph_node_id",
"title": "图节点ID",
"type": "str",
"length": 128,
"nullable": "yes"
},
{
"name": "source_doc_ids",
"title": "来源文档ID列表",
"type": "text",
"nullable": "yes"
},
{
"name": "face_embedding_id",
"title": "人脸特征ID",
"type": "str",
"length": 128,
"nullable": "yes"
},
{
"name": "voice_embedding_id",
"title": "声纹特征ID",
"type": "str",
"length": 128,
"nullable": "yes"
},
{
"name": "metadata",
"title": "元数据JSON",
"type": "text",
"nullable": "yes"
},
{
"name": "org_id",
"title": "所属机构",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "created_at",
"title": "创建时间",
"type": "datetime",
"nullable": "yes"
},
{
"name": "updated_at",
"title": "更新时间",
"type": "datetime",
"nullable": "yes"
}
],
"indexes": [
{
"name": "idx_entity_kb",
"idxtype": "index",
"idxfields": [
"kb_id"
]
},
{
"name": "idx_entity_name",
"idxtype": "index",
"idxfields": [
"kb_id",
"name"
]
},
{
"name": "idx_entity_type",
"idxtype": "index",
"idxfields": [
"kb_id",
"entity_type"
]
},
{
"name": "idx_entity_face",
"idxtype": "index",
"idxfields": [
"face_embedding_id"
]
},
{
"name": "idx_entity_voice",
"idxtype": "index",
"idxfields": [
"voice_embedding_id"
]
}
]
}

View File

@ -0,0 +1,103 @@
{
"summary": [
{
"name": "rag_entity_relations",
"title": "实体关系",
"primary": [
"id"
]
}
],
"fields": [
{
"name": "id",
"title": "ID",
"type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "kb_id",
"title": "知识库ID",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "subject_id",
"title": "主体实体ID",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "object_id",
"title": "客体实体ID",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "relation_type",
"title": "关系类型",
"type": "str",
"length": 64,
"nullable": "yes"
},
{
"name": "description",
"title": "关系描述",
"type": "text",
"nullable": "yes"
},
{
"name": "graph_edge_id",
"title": "图边ID",
"type": "str",
"length": 128,
"nullable": "yes"
},
{
"name": "source_chunk_ids",
"title": "来源分片ID列表",
"type": "text",
"nullable": "yes"
},
{
"name": "org_id",
"title": "所属机构",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "created_at",
"title": "创建时间",
"type": "datetime",
"nullable": "yes"
}
],
"indexes": [
{
"name": "idx_rel_kb",
"idxtype": "index",
"idxfields": [
"kb_id"
]
},
{
"name": "idx_rel_subject",
"idxtype": "index",
"idxfields": [
"subject_id"
]
},
{
"name": "idx_rel_object",
"idxtype": "index",
"idxfields": [
"object_id"
]
}
]
}

View File

@ -0,0 +1,124 @@
{
"summary": [
{
"name": "rag_knowledge_bases",
"title": "知识库",
"primary": [
"id"
]
}
],
"fields": [
{
"name": "id",
"title": "ID",
"type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "name",
"title": "名称",
"type": "str",
"length": 100,
"nullable": "yes"
},
{
"name": "description",
"title": "描述",
"type": "text",
"nullable": "yes"
},
{
"name": "org_id",
"title": "所属机构",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "embedding_engine",
"title": "向量化引擎",
"type": "str",
"length": 64,
"nullable": "yes",
"default": "clip-vith14"
},
{
"name": "vdb_collection",
"title": "向量库集合名",
"type": "str",
"length": 128,
"nullable": "yes"
},
{
"name": "graph_name",
"title": "图名称",
"type": "str",
"length": 128,
"nullable": "yes"
},
{
"name": "doc_count",
"title": "文档数",
"type": "int",
"nullable": "yes"
},
{
"name": "chunk_count",
"title": "分片数",
"type": "int",
"nullable": "yes"
},
{
"name": "total_size",
"title": "总大小(字节)",
"type": "int",
"nullable": "yes"
},
{
"name": "status",
"title": "状态",
"type": "str",
"length": 16,
"nullable": "yes",
"default": "active"
},
{
"name": "created_by",
"title": "创建人",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "created_at",
"title": "创建时间",
"type": "datetime",
"nullable": "yes"
},
{
"name": "updated_at",
"title": "更新时间",
"type": "datetime",
"nullable": "yes"
}
],
"indexes": [
{
"name": "idx_kb_name",
"idxtype": "unique",
"idxfields": [
"org_id",
"name"
]
},
{
"name": "idx_kb_org",
"idxtype": "index",
"idxfields": [
"org_id"
]
}
]
}

View File

@ -0,0 +1,87 @@
{
"summary": [
{
"name": "rag_media_tags",
"title": "媒体标签关联",
"primary": [
"id"
]
}
],
"fields": [
{
"name": "id",
"title": "ID",
"type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "kb_id",
"title": "知识库ID",
"type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "media_type",
"title": "媒体类型",
"type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "media_id",
"title": "媒体ID",
"type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "tag_id",
"title": "标签ID",
"type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "created_at",
"title": "创建时间",
"type": "datetime",
"nullable": "yes"
}
],
"indexes": [
{
"name": "idx_mt_unique",
"idxtype": "unique",
"idxfields": [
"media_type",
"media_id",
"tag_id"
]
},
{
"name": "idx_mt_media",
"idxtype": "index",
"idxfields": [
"media_type",
"media_id"
]
},
{
"name": "idx_mt_tag",
"idxtype": "index",
"idxfields": [
"tag_id"
]
},
{
"name": "idx_mt_kb",
"idxtype": "index",
"idxfields": [
"kb_id"
]
}
]
}

View File

@ -0,0 +1,39 @@
{
"summary": [
{
"name": "rag_org_storage_limits",
"title": "机构存储限额",
"primary": [
"org_id"
]
}
],
"fields": [
{
"name": "org_id",
"title": "机构ID",
"type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "limit_bytes",
"title": "限额(字节)",
"type": "bigint",
"nullable": "no",
"default": "104857600"
},
{
"name": "created_at",
"title": "创建时间",
"type": "datetime",
"nullable": "yes"
},
{
"name": "updated_at",
"title": "更新时间",
"type": "datetime",
"nullable": "yes"
}
]
}

View File

@ -0,0 +1,134 @@
{
"summary": [
{
"name": "rag_subscriptions",
"title": "租户订阅",
"primary": [
"id"
]
}
],
"fields": [
{
"name": "id",
"title": "ID",
"type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "org_id",
"title": "机构ID",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "plan_name",
"title": "套餐名称",
"type": "str",
"length": 64,
"nullable": "yes"
},
{
"name": "disk_quota_bytes",
"title": "磁盘配额(字节)",
"type": "int",
"nullable": "yes",
"default": "1073741824"
},
{
"name": "disk_used_bytes",
"title": "已用磁盘(字节)",
"type": "int",
"nullable": "yes"
},
{
"name": "doc_quota",
"title": "文档数配额",
"type": "int",
"nullable": "yes",
"default": "1000"
},
{
"name": "doc_used",
"title": "已用文档数",
"type": "int",
"nullable": "yes"
},
{
"name": "kb_quota",
"title": "知识库数配额",
"type": "int",
"nullable": "yes",
"default": "10"
},
{
"name": "kb_used",
"title": "已用知识库数",
"type": "int",
"nullable": "yes"
},
{
"name": "api_call_quota",
"title": "API调用配额(月)",
"type": "int",
"nullable": "yes",
"default": "10000"
},
{
"name": "api_call_used",
"title": "已用API调用(月)",
"type": "int",
"nullable": "yes"
},
{
"name": "start_date",
"title": "开始日期",
"type": "date",
"nullable": "yes"
},
{
"name": "end_date",
"title": "结束日期",
"type": "date",
"nullable": "yes"
},
{
"name": "status",
"title": "状态",
"type": "str",
"length": 16,
"nullable": "yes",
"default": "active"
},
{
"name": "created_at",
"title": "创建时间",
"type": "datetime",
"nullable": "yes"
},
{
"name": "updated_at",
"title": "更新时间",
"type": "datetime",
"nullable": "yes"
}
],
"indexes": [
{
"name": "idx_sub_org",
"idxtype": "unique",
"idxfields": [
"org_id"
]
},
{
"name": "idx_sub_status",
"idxtype": "index",
"idxfields": [
"status"
]
}
]
}

73
models/rag_tags.json Normal file
View File

@ -0,0 +1,73 @@
{
"summary": [
{
"name": "rag_tags",
"title": "标签",
"primary": [
"id"
]
}
],
"fields": [
{
"name": "id",
"title": "ID",
"type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "kb_id",
"title": "知识库ID",
"type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "name",
"title": "标签名",
"type": "str",
"length": 64,
"nullable": "no"
},
{
"name": "color",
"title": "颜色",
"type": "str",
"length": 16,
"nullable": "no",
"default": "#3b82f6"
},
{
"name": "org_id",
"title": "所属机构",
"type": "str",
"length": 32,
"nullable": "no",
"default": ""
},
{
"name": "created_at",
"title": "创建时间",
"type": "datetime",
"nullable": "yes"
}
],
"indexes": [
{
"name": "idx_tag_name",
"idxtype": "unique",
"idxfields": [
"kb_id",
"name"
]
},
{
"name": "idx_tag_kb",
"idxtype": "index",
"idxfields": [
"kb_id"
]
}
]
}

110
models/rag_usage_logs.json Normal file
View File

@ -0,0 +1,110 @@
{
"summary": [
{
"name": "rag_usage_logs",
"title": "用量日志",
"primary": [
"id"
]
}
],
"fields": [
{
"name": "id",
"title": "ID",
"type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "org_id",
"title": "机构ID",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "kb_id",
"title": "知识库ID",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "operation",
"title": "操作类型",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "bytes_delta",
"title": "磁盘变化(字节)",
"type": "int",
"nullable": "yes"
},
{
"name": "bytes_total",
"title": "累计磁盘",
"type": "int",
"nullable": "yes"
},
{
"name": "api_calls",
"title": "API调用数",
"type": "int",
"nullable": "yes",
"default": "1"
},
{
"name": "engine_type",
"title": "引擎类型",
"type": "str",
"length": 32,
"nullable": "yes"
},
{
"name": "tokens_used",
"title": "Token消耗",
"type": "int",
"nullable": "yes"
},
{
"name": "cost_estimate",
"title": "预估费用",
"type": "double",
"length": 20,
"dec": 4,
"nullable": "yes"
},
{
"name": "detail",
"title": "详情JSON",
"type": "text",
"nullable": "yes"
},
{
"name": "created_at",
"title": "创建时间",
"type": "datetime",
"nullable": "yes"
}
],
"indexes": [
{
"name": "idx_usage_org",
"idxtype": "index",
"idxfields": [
"org_id",
"created_at"
]
},
{
"name": "idx_usage_kb",
"idxtype": "index",
"idxfields": [
"kb_id"
]
}
]
}

View File

@ -1,28 +0,0 @@
{
"tblname": "subscriptions",
"params": {
"title": "订阅管理",
"browserfields": [
{"field": "org_id", "title": "机构", "width": "15%"},
{"field": "plan_name", "title": "套餐", "width": "15%"},
{"field": "disk_quota_bytes", "title": "磁盘配额", "width": "12%"},
{"field": "disk_used_bytes", "title": "已用磁盘", "width": "12%"},
{"field": "doc_used", "title": "文档数", "width": "10%"},
{"field": "start_date", "title": "开始", "width": "12%"},
{"field": "end_date", "title": "结束", "width": "12%"},
{"field": "status", "title": "状态", "width": "12%"}
],
"editfields": [
{"field": "org_id", "uitype": "Text", "required": true},
{"field": "plan_name", "uitype": "Text", "required": true},
{"field": "disk_quota_bytes", "uitype": "Text", "default": "1073741824"},
{"field": "doc_quota", "uitype": "Text"},
{"field": "kb_quota", "uitype": "Text"},
{"field": "api_call_quota", "uitype": "Text"},
{"field": "start_date", "uitype": "Text"},
{"field": "end_date", "uitype": "Text"}
],
"searchfields": ["org_id", "plan_name"],
"sort": "created_at desc"
}
}

View File

@ -1,22 +0,0 @@
{
"tblname": "tags",
"params": {
"title": "标签管理",
"browserfields": [
{"field": "name", "title": "标签名", "width": "25%"},
{"field": "color", "title": "颜色", "width": "15%"},
{"field": "kb_id", "title": "知识库", "width": "30%"},
{"field": "created_at", "title": "创建时间", "width": "30%"}
],
"editfields": [
{"field": "name", "uitype": "Text", "required": true, "label": "标签名"},
{"field": "kb_id", "uitype": "Text", "required": true, "label": "知识库ID"},
{"field": "color", "uitype": "Text", "default": "#3b82f6", "label": "颜色(#hex)"}
],
"searchfields": ["name"],
"sort": "created_at desc",
"data_filter": {
"org_id": "{{userorgid}}"
}
}
}

View File

@ -27,10 +27,10 @@ async def kb_list_handler(request, params_kw, *args, **kwargs):
try:
userorgid = await env.get_userorgid()
async with get_sor_context(env, 'rag') as sor:
recs = await sor.R("knowledge_bases", {})
recs = await sor.R("rag_knowledge_bases", {})
cards = []
for r in recs:
doc_recs = await sor.R("documents", {"kb_id": r.id})
doc_recs = await sor.R("rag_documents", {"kb_id": r.id})
doc_count = len(doc_recs)
total_size = r.total_size or 0
if total_size >= 1073741824:
@ -64,7 +64,7 @@ async def engines_handler(request, params_kw, *args, **kwargs):
try:
userorgid = await env.get_userorgid()
async with get_sor_context(env, 'rag') as sor:
sql = "SELECT * FROM engine_configs WHERE status='active' AND (org_id IS NULL OR org_id=${org_id}$) ORDER BY engine_type, priority DESC"
sql = "SELECT * FROM rag_engine_configs WHERE status='active' AND (org_id IS NULL OR org_id=${org_id}$) ORDER BY engine_type, priority DESC"
recs = await sor.sqlExe(sql, {"org_id": userorgid})
rows = [dict(r) for r in recs]
return json.dumps({"status": "SUCCEEDED", "data": {"rows": rows, "total": len(rows)}}, ensure_ascii=False, default=str)
@ -173,10 +173,10 @@ async def _resolve_search_kbs(env, userorgid, kb_id):
"""Resolve KB IDs: specific or all org KBs"""
async with get_sor_context(env, 'rag') as sor:
if kb_id:
recs = await sor.R("knowledge_bases", {"id": kb_id})
recs = await sor.R("rag_knowledge_bases", {"id": kb_id})
return [r.id for r in recs]
# All org KBs (global + org-specific)
sql = "SELECT id FROM knowledge_bases WHERE org_id IS NULL OR org_id=${org_id}$"
sql = "SELECT id FROM rag_knowledge_bases WHERE org_id IS NULL OR org_id=${org_id}$"
recs = await sor.sqlExe(sql, {"org_id": userorgid})
return [r.id for r in recs]
@ -219,7 +219,7 @@ async def _build_search_vector(query, file_data, file_name, env=None, kb_id=''):
emb_engine = 'clip-vith14'
if env is not None and kb_id:
async with get_sor_context(env, 'rag') as sor:
krecs = await sor.sqlExe("SELECT embedding_engine FROM knowledge_bases WHERE id=${kb_id}$", {"kb_id": kb_id})
krecs = await sor.sqlExe("SELECT embedding_engine FROM rag_knowledge_bases WHERE id=${kb_id}$", {"kb_id": kb_id})
if krecs:
emb_engine = (getattr(krecs[0], 'embedding_engine', '') or 'clip-vith14').strip()
if emb_engine == 'bge-m3':
@ -287,7 +287,7 @@ async def _enrich_search_results(env, hits):
async with get_sor_context(env, 'rag') as sor:
recs = await sor.sqlExe(
"SELECT id, file_name, file_type, file_size, status, kb_id, created_at "
"FROM documents WHERE id IN (" + ",".join(repr(d) for d in doc_ids) + ")", {})
"FROM rag_documents WHERE id IN (" + ",".join(repr(d) for d in doc_ids) + ")", {})
doc_map = {r.id: dict(r) for r in recs}
for h in hits:
@ -325,9 +325,9 @@ async def doc_upload_handler(request, params_kw, *args, **kwargs):
quota_limit = 104857600
used = 0
async with get_sor_context(env, 'rag') as sor:
rec = await sor.sqlExe("SELECT COALESCE(SUM(file_size),0) AS used FROM documents WHERE org_id=${org_id}$", {"org_id": userorgid})
rec = await sor.sqlExe("SELECT COALESCE(SUM(file_size),0) AS used FROM rag_documents WHERE org_id=${org_id}$", {"org_id": userorgid})
if rec: used = int(rec[0].used)
lim = await sor.sqlExe("SELECT limit_bytes FROM org_storage_limits WHERE org_id=${org_id}$", {"org_id": userorgid})
lim = await sor.sqlExe("SELECT limit_bytes FROM rag_org_storage_limits WHERE org_id=${org_id}$", {"org_id": userorgid})
if lim: quota_limit = int(lim[0].limit_bytes)
if used + file_size > quota_limit:
return json.dumps({"error": "storage_quota_exceeded",
@ -342,7 +342,7 @@ async def doc_upload_handler(request, params_kw, *args, **kwargs):
# Create document record
async with get_sor_context(env, 'rag') as sor:
await sor.sqlExe(
"INSERT INTO documents (id, kb_id, file_name, file_type, file_size, file_path, mime_type, status, org_id, created_at, updated_at) "
"INSERT INTO rag_documents (id, kb_id, file_name, file_type, file_size, file_path, mime_type, status, org_id, created_at, updated_at) "
"VALUES (${id}$, ${kb_id}$, ${file_name}$, ${file_type}$, ${file_size}$, ${file_path}$, ${mime_type}$, 'pending', ${org_id}$, NOW(), NOW())",
{"id": doc_id, "kb_id": kb_id, "file_name": file_name, "file_type": file_type,
"file_size": file_size, "file_path": web_path,
@ -350,7 +350,7 @@ async def doc_upload_handler(request, params_kw, *args, **kwargs):
# Update KB stats
await sor.sqlExe(
"UPDATE knowledge_bases SET doc_count=doc_count+1, total_size=total_size+${size}$ WHERE id=${kb_id}$",
"UPDATE rag_knowledge_bases SET doc_count=doc_count+1, total_size=total_size+${size}$ WHERE id=${kb_id}$",
{"size": file_size, "kb_id": kb_id})
# Trigger async ingest for text-based files via uapi
@ -363,17 +363,17 @@ async def doc_upload_handler(request, params_kw, *args, **kwargs):
async with get_sor_context(env, 'rag') as sor:
chunks_n = ingest_result.get("chunks", 0) if ingest_result else 0
await sor.sqlExe(
"UPDATE documents SET status='done', chunk_count=${chunks}$ WHERE id=${id}$",
"UPDATE rag_documents SET status='done', chunk_count=${chunks}$ WHERE id=${id}$",
{"chunks": chunks_n, "id": doc_id})
if chunks_n:
await sor.sqlExe(
"UPDATE knowledge_bases SET chunk_count=chunk_count+${n}$ WHERE id=${kb_id}$",
"UPDATE rag_knowledge_bases SET chunk_count=chunk_count+${n}$ WHERE id=${kb_id}$",
{"n": chunks_n, "kb_id": kb_id})
except Exception as e:
exception(f"uapi ingest failed: {e}")
async with get_sor_context(env, 'rag') as sor:
await sor.sqlExe(
"UPDATE documents SET status='error' WHERE id=${id}$", {"id": doc_id})
"UPDATE rag_documents SET status='error' WHERE id=${id}$", {"id": doc_id})
return json.dumps({
"status": "SUCCEEDED",
@ -399,13 +399,13 @@ async def doc_delete_handler(request, params_kw, *args, **kwargs):
async with get_sor_context(env, 'rag') as sor:
# Get document info
recs = await sor.R("documents", {"id": doc_id})
recs = await sor.R("rag_documents", {"id": doc_id})
if not recs:
return json.dumps({"error": "document not found"})
doc = recs[0]
# Get chunks to clean VDB
chunks = await sor.R("document_chunks", {"doc_id": doc_id})
chunks = await sor.R("rag_document_chunks", {"doc_id": doc_id})
# Delete from VDB
if chunks:
@ -424,14 +424,14 @@ async def doc_delete_handler(request, params_kw, *args, **kwargs):
exception(f"graph delete failed: {e}")
# Delete DB records
await sor.sqlExe("DELETE FROM document_chunks WHERE doc_id=${id}$", {"id": doc_id})
await sor.sqlExe("DELETE FROM entities WHERE kb_id=${kb_id}$", {"kb_id": doc.kb_id})
await sor.sqlExe("DELETE FROM entity_relations WHERE kb_id=${kb_id}$", {"kb_id": doc.kb_id})
await sor.sqlExe("DELETE FROM documents WHERE id=${id}$", {"id": doc_id})
await sor.sqlExe("DELETE FROM rag_document_chunks WHERE doc_id=${id}$", {"id": doc_id})
await sor.sqlExe("DELETE FROM rag_entities WHERE kb_id=${kb_id}$", {"kb_id": doc.kb_id})
await sor.sqlExe("DELETE FROM rag_entity_relations WHERE kb_id=${kb_id}$", {"kb_id": doc.kb_id})
await sor.sqlExe("DELETE FROM rag_documents WHERE id=${id}$", {"id": doc_id})
# Update KB stats
await sor.sqlExe(
"UPDATE knowledge_bases SET doc_count=GREATEST(doc_count-1,0), total_size=GREATEST(total_size-${size}$,0), chunk_count=GREATEST(chunk_count-${n}$,0) WHERE id=${kb_id}$",
"UPDATE rag_knowledge_bases SET doc_count=GREATEST(doc_count-1,0), total_size=GREATEST(total_size-${size}$,0), chunk_count=GREATEST(chunk_count-${n}$,0) WHERE id=${kb_id}$",
{"size": doc.file_size, "n": len(chunks), "kb_id": doc.kb_id})
# Delete file from disk
@ -496,7 +496,7 @@ async def _rag_ingest_async(env, text, kb_id, doc_id):
try:
emb_engine = 'clip-vith14'
async with get_sor_context(env, 'rag') as sor:
krecs = await sor.sqlExe("SELECT embedding_engine FROM knowledge_bases WHERE id=${kb_id}$", {"kb_id": kb_id})
krecs = await sor.sqlExe("SELECT embedding_engine FROM rag_knowledge_bases WHERE id=${kb_id}$", {"kb_id": kb_id})
if krecs:
emb_engine = (getattr(krecs[0], 'embedding_engine', '') or 'clip-vith14').strip()
if emb_engine == 'bge-m3':
@ -552,7 +552,7 @@ async def _rag_ingest_async(env, text, kb_id, doc_id):
async with get_sor_context(env, 'rag') as sor:
for i, (chunk_text, vid) in enumerate(zip(chunks, vector_ids)):
await sor.sqlExe(
"INSERT INTO document_chunks (id, doc_id, kb_id, chunk_index, content, vector_id, metadata, created_at) "
"INSERT INTO rag_document_chunks (id, doc_id, kb_id, chunk_index, content, vector_id, metadata, created_at) "
"VALUES (${id}$, ${doc_id}$, ${kb_id}$, ${idx}$, ${content}$, ${vid}$, NOW())",
{"id": f"{doc_id}_c{i}", "doc_id": doc_id, "kb_id": kb_id,
"idx": i, "content": chunk_text[:2000], "vid": vid})
@ -642,7 +642,7 @@ async def dir_create_handler(request, params_kw, *args, **kwargs):
async with get_sor_context(env, 'rag') as sor:
dir_id = uuid.uuid4().hex
await sor.sqlExe(
"INSERT INTO document_chunks (id, doc_id, kb_id, chunk_index, chunk_type, content, description, created_at) "
"INSERT INTO rag_document_chunks (id, doc_id, kb_id, chunk_index, chunk_type, content, description, created_at) "
"VALUES (${id}$, '', ${kb_id}$, 0, 'directory', ${name}$, ${parent}$, NOW())",
{"id": dir_id, "kb_id": kb_id, "name": dir_name, "parent": parent_id})
return json.dumps({"status": "SUCCEEDED", "dir_id": dir_id})
@ -659,7 +659,7 @@ async def dir_delete_handler(request, params_kw, *args, **kwargs):
if not item_id:
return json.dumps({"error": "item_id required"})
async with get_sor_context(env, 'rag') as sor:
await sor.sqlExe("DELETE FROM document_chunks WHERE id=${id}$ OR doc_id=${id}$", {"id": item_id})
await sor.sqlExe("DELETE FROM rag_document_chunks WHERE id=${id}$ OR doc_id=${id}$", {"id": item_id})
return json.dumps({"status": "SUCCEEDED"})
except Exception as e:
exception(f"dir_delete: {e}")
@ -676,11 +676,11 @@ async def dir_list_handler(request, params_kw, *args, **kwargs):
async with get_sor_context(env, 'rag') as sor:
# Get directories (chunk_type='directory')
dirs = await sor.sqlExe(
"SELECT id, content as label, description as parent_id FROM document_chunks WHERE kb_id=${kb_id}$ AND chunk_type='directory'",
"SELECT id, content as label, description as parent_id FROM rag_document_chunks WHERE kb_id=${kb_id}$ AND chunk_type='directory'",
{"kb_id": kb_id})
# Get files (documents table)
docs = await sor.sqlExe(
"SELECT id, file_name as label, '' as parent_id FROM documents WHERE kb_id=${kb_id}$",
"SELECT id, file_name as label, '' as parent_id FROM rag_documents WHERE kb_id=${kb_id}$",
{"kb_id": kb_id})
items = []
for d in dirs:
@ -705,14 +705,14 @@ async def tag_create_handler(request, params_kw, *args, **kwargs):
userorgid = await env.get_userorgid()
async with get_sor_context(env, 'rag') as sor:
existing = await sor.sqlExe(
"SELECT id, color FROM tags WHERE kb_id=${kb_id}$ AND name=${name}$ AND org_id=${org_id}$",
"SELECT id, color FROM rag_tags WHERE kb_id=${kb_id}$ AND name=${name}$ AND org_id=${org_id}$",
{"kb_id": kb_id, "name": name, "org_id": userorgid})
if existing:
return json.dumps({"status": "SUCCEEDED", "tag_id": existing[0].id, "name": name,
"color": existing[0].color, "duplicate": True}, ensure_ascii=False)
tag_id = uuid.uuid4().hex
await sor.sqlExe(
"INSERT INTO tags (id, kb_id, name, color, org_id, created_at) "
"INSERT INTO rag_tags (id, kb_id, name, color, org_id, created_at) "
"VALUES (${id}$, ${kb_id}$, ${name}$, ${color}$, ${org_id}$, NOW())",
{"id": tag_id, "kb_id": kb_id, "name": name, "color": color, "org_id": userorgid})
return json.dumps({"status": "SUCCEEDED", "tag_id": tag_id, "name": name, "color": color})
@ -730,7 +730,7 @@ async def tag_list_handler(request, params_kw, *args, **kwargs):
return json.dumps({"error": "kb_id required"})
userorgid = await env.get_userorgid()
async with get_sor_context(env, 'rag') as sor:
recs = await sor.R("tags", {"kb_id": kb_id, "org_id": userorgid})
recs = await sor.R("rag_tags", {"kb_id": kb_id, "org_id": userorgid})
tags = [{"id": r.id, "name": r.name, "color": r.color, "created_at": str(r.created_at)} for r in recs]
return json.dumps({"status": "SUCCEEDED", "tags": tags}, ensure_ascii=False, default=str)
except Exception as e:
@ -746,8 +746,8 @@ async def tag_delete_handler(request, params_kw, *args, **kwargs):
if not tag_id:
return json.dumps({"error": "tag_id required"})
async with get_sor_context(env, 'rag') as sor:
await sor.sqlExe("DELETE FROM media_tags WHERE tag_id=${id}$", {"id": tag_id})
await sor.sqlExe("DELETE FROM tags WHERE id=${id}$", {"id": tag_id})
await sor.sqlExe("DELETE FROM rag_media_tags WHERE tag_id=${id}$", {"id": tag_id})
await sor.sqlExe("DELETE FROM rag_tags WHERE id=${id}$", {"id": tag_id})
return json.dumps({"status": "SUCCEEDED", "tag_id": tag_id})
except Exception as e:
exception(f"tag_delete: {e}, {format_exc()}")
@ -769,7 +769,7 @@ async def tag_assign_handler(request, params_kw, *args, **kwargs):
async with get_sor_context(env, 'rag') as sor:
mt_id = uuid.uuid4().hex
await sor.sqlExe(
"INSERT INTO media_tags (id, kb_id, media_type, media_id, tag_id, created_at) "
"INSERT INTO rag_media_tags (id, kb_id, media_type, media_id, tag_id, created_at) "
"VALUES (${id}$, ${kb_id}$, ${type}$, ${mid}$, ${tid}$, NOW())",
{"id": mt_id, "kb_id": kb_id, "type": media_type, "mid": media_id, "tid": tag_id})
return json.dumps({"status": "SUCCEEDED", "media_tag_id": mt_id})
@ -789,7 +789,7 @@ async def tag_unassign_handler(request, params_kw, *args, **kwargs):
return json.dumps({"error": "media_type, media_id, tag_id required"})
async with get_sor_context(env, 'rag') as sor:
await sor.sqlExe(
"DELETE FROM media_tags WHERE media_type=${type}$ AND media_id=${mid}$ AND tag_id=${tid}$",
"DELETE FROM rag_media_tags WHERE media_type=${type}$ AND media_id=${mid}$ AND tag_id=${tid}$",
{"type": media_type, "mid": media_id, "tid": tag_id})
return json.dumps({"status": "SUCCEEDED"})
except Exception as e:
@ -807,8 +807,8 @@ async def tag_media_tags_handler(request, params_kw, *args, **kwargs):
return json.dumps({"error": "media_type and media_id required"})
async with get_sor_context(env, 'rag') as sor:
recs = await sor.sqlExe(
"SELECT t.id, t.name, t.color FROM media_tags mt "
"JOIN tags t ON mt.tag_id=t.id "
"SELECT t.id, t.name, t.color FROM rag_media_tags mt "
"JOIN rag_tags t ON mt.tag_id=t.id "
"WHERE mt.media_type=${type}$ AND mt.media_id=${mid}$",
{"type": media_type, "mid": media_id})
tags = [{"id": r.id, "name": r.name, "color": r.color} for r in recs]
@ -835,7 +835,7 @@ async def tag_search_handler(request, params_kw, *args, **kwargs):
media_ids_by_tag = []
for tid in tag_ids:
recs = await sor.sqlExe(
"SELECT media_type, media_id FROM media_tags WHERE kb_id=${kb_id}$ AND tag_id=${tid}$",
"SELECT media_type, media_id FROM rag_media_tags WHERE kb_id=${kb_id}$ AND tag_id=${tid}$",
{"kb_id": kb_id, "tid": tid})
mids = {(r.media_type, r.media_id) for r in recs}
media_ids_by_tag.append(mids)
@ -855,19 +855,19 @@ async def tag_search_handler(request, params_kw, *args, **kwargs):
results = []
if doc_ids:
docs = await sor.sqlExe(
"SELECT id, file_name, file_type, file_size, status, created_at FROM documents WHERE id IN (${ids}$)",
"SELECT id, file_name, file_type, file_size, status, created_at FROM rag_documents WHERE id IN (${ids}$)",
{"ids": doc_ids})
for d in docs:
results.append({"type": "document", "id": d.id, "name": d.file_name, "file_type": d.file_type, "size": d.file_size, "status": d.status, "created_at": str(d.created_at)})
if face_ids:
faces = await sor.sqlExe(
"SELECT id, name, description, face_embedding_id, created_at FROM entities WHERE id IN (${ids}$) AND entity_type='person'",
"SELECT id, name, description, face_embedding_id, created_at FROM rag_entities WHERE id IN (${ids}$) AND entity_type='person'",
{"ids": face_ids})
for f in faces:
results.append({"type": "face", "id": f.id, "name": f.name, "description": f.description, "created_at": str(f.created_at)})
if voice_ids:
voices = await sor.sqlExe(
"SELECT id, name, description, voice_embedding_id, created_at FROM entities WHERE id IN (${ids}$) AND entity_type='voice'",
"SELECT id, name, description, voice_embedding_id, created_at FROM rag_entities WHERE id IN (${ids}$) AND entity_type='voice'",
{"ids": voice_ids})
for v in voices:
results.append({"type": "voice", "id": v.id, "name": v.name, "description": v.description, "created_at": str(v.created_at)})
@ -877,7 +877,7 @@ async def tag_search_handler(request, params_kw, *args, **kwargs):
vdb_docs = []
for doc in tag_filtered_docs:
chunks = await sor.sqlExe(
"SELECT content FROM document_chunks WHERE doc_id=${id}$ LIMIT 3",
"SELECT content FROM rag_document_chunks WHERE doc_id=${id}$ LIMIT 3",
{"id": doc["id"]})
for c in chunks:
vdb_docs.append({"doc_id": doc["id"], "content": c.content})
@ -911,20 +911,20 @@ async def tag_sync_handler(request, params_kw, *args, **kwargs):
wanted_ids = [t.strip() for t in tag_ids_str.split(",") if t.strip()]
async with get_sor_context(env, 'rag') as sor:
recs = await sor.sqlExe(
"SELECT id, tag_id FROM media_tags WHERE media_type=${type}$ AND media_id=${mid}$",
"SELECT id, tag_id FROM rag_media_tags WHERE media_type=${type}$ AND media_id=${mid}$",
{"type": media_type, "mid": media_id})
current = {r.tag_id: r.id for r in recs}
removed = 0
for tid, mt_id in current.items():
if tid not in wanted_ids:
await sor.sqlExe("DELETE FROM media_tags WHERE id=${id}$", {"id": mt_id})
await sor.sqlExe("DELETE FROM rag_media_tags WHERE id=${id}$", {"id": mt_id})
removed += 1
added = 0
for tid in wanted_ids:
if tid not in current:
mt_id = uuid.uuid4().hex
await sor.sqlExe(
"INSERT INTO media_tags (id, kb_id, media_type, media_id, tag_id, created_at) "
"INSERT INTO rag_media_tags (id, kb_id, media_type, media_id, tag_id, created_at) "
"VALUES (${id}$, ${kb_id}$, ${type}$, ${mid}$, ${tid}$, NOW())",
{"id": mt_id, "kb_id": kb_id, "type": media_type, "mid": media_id, "tid": tid})
added += 1

View File

@ -185,19 +185,19 @@ async def process_upload(env, file_data, kb_id, folder_id, file_name):
from sqlor.dbpools import get_sor_context
async with get_sor_context(env, 'rag') as sor:
await sor.sqlExe(
"INSERT INTO documents (id, kb_id, folder_id, file_name, file_type, file_size, file_path, mime_type, status, metadata, org_id, created_at, updated_at) "
"INSERT INTO rag_documents (id, kb_id, folder_id, file_name, file_type, file_size, file_path, mime_type, status, metadata, org_id, created_at, updated_at) "
"VALUES (" + "${id}$, ${kb_id}$, ${folder_id}$, ${file_name}$, 'other', ${file_size}$, ${file_path}$, 'application/octet-stream', ${status}$, ${meta}$, ${org_id}$, NOW(), NOW())",
{"id": doc_id, "kb_id": kb_id, "folder_id": folder_id, "file_name": file_name,
"file_size": file_size, "file_path": "/idfile/files/" + saved_name,
"status": status, "meta": meta, "org_id": userorgid})
await sor.sqlExe(
"UPDATE knowledge_bases SET doc_count=doc_count+1, total_size=total_size+" + "${size}$ WHERE id=${kb_id}$",
"UPDATE rag_knowledge_bases SET doc_count=doc_count+1, total_size=total_size+" + "${size}$ WHERE id=${kb_id}$",
{"size": file_size, "kb_id": kb_id})
if ingest_result and status == 'done':
chunks_n = ingest_result.get('chunks', 0) if isinstance(ingest_result, dict) else 0
if chunks_n:
await sor.sqlExe("UPDATE documents SET chunk_count=${chunks}$ WHERE id=${id}$", {"chunks": chunks_n, "id": doc_id})
await sor.sqlExe("UPDATE knowledge_bases SET chunk_count=chunk_count+${n}$ WHERE id=${kb_id}$", {"n": chunks_n, "kb_id": kb_id})
await sor.sqlExe("UPDATE rag_documents SET chunk_count=${chunks}$ WHERE id=${id}$", {"chunks": chunks_n, "id": doc_id})
await sor.sqlExe("UPDATE rag_knowledge_bases SET chunk_count=chunk_count+${n}$ WHERE id=${kb_id}$", {"n": chunks_n, "kb_id": kb_id})
faces_n = face_result.get('faces', 0) if face_result and isinstance(face_result, dict) else 0
speakers_n = voice_result.get('speakers', 0) if voice_result and isinstance(voice_result, dict) else 0

View File

@ -9,7 +9,7 @@ if not doc_id or not tag:
async with get_sor_context(env, 'rag') as sor:
# Get existing tags
recs = await sor.sqlExe("SELECT metadata FROM documents WHERE id=${id}$ AND kb_id=${kb_id}$",
recs = await sor.sqlExe("SELECT metadata FROM rag_documents WHERE id=${id}$ AND kb_id=${kb_id}$",
{"id": doc_id, "kb_id": kb_id})
if not recs:
return json.dumps({"status": "error", "error": "document not found"}, ensure_ascii=False)
@ -29,7 +29,7 @@ async with get_sor_context(env, 'rag') as sor:
tags.append(tag)
meta['tags'] = tags
await sor.sqlExe(
"UPDATE documents SET metadata=${meta}$ WHERE id=${id}$",
"UPDATE rag_documents SET metadata=${meta}$ WHERE id=${id}$",
{"meta": _json.dumps(meta, ensure_ascii=False), "id": doc_id})
return _json.dumps({"status": "SUCCEEDED", "tags": tags, "added": tag}, ensure_ascii=False)
else:

View File

@ -6,7 +6,7 @@ rows = []
async with get_sor_context(env, 'rag') as sor:
recs = await sor.sqlExe(
"SELECT id, file_name, file_size, file_type, file_path, metadata, status, chunk_count "
"FROM documents WHERE kb_id=${kb_id}$ AND metadata IS NOT NULL AND metadata != '' "
"FROM rag_documents WHERE kb_id=${kb_id}$ AND metadata IS NOT NULL AND metadata != '' "
"ORDER BY created_at DESC LIMIT 50",
{"kb_id": kb_id})
rows = [dict(r) for r in recs]

View File

@ -6,10 +6,10 @@ env = request._run_ns
rows = []
async with get_sor_context(env, 'rag') as sor:
recs = await sor.sqlExe(
"SELECT id, file_name, file_size, file_path FROM documents WHERE kb_id=${kb_id}$ AND (chunk_count IS NULL OR chunk_count=0 OR status='pending') LIMIT 20",
"SELECT id, file_name, file_size, file_path FROM rag_documents WHERE kb_id=${kb_id}$ AND (chunk_count IS NULL OR chunk_count=0 OR status='pending') LIMIT 20",
{"kb_id": kb_id})
rows = [dict(r) for r in recs]
krecs = await sor.sqlExe("SELECT embedding_engine FROM knowledge_bases WHERE id=${kb_id}$", {"kb_id": kb_id})
krecs = await sor.sqlExe("SELECT embedding_engine FROM rag_knowledge_bases WHERE id=${kb_id}$", {"kb_id": kb_id})
emb_engine = (getattr(krecs[0], 'embedding_engine', '') or 'clip-vith14').strip() if krecs else 'clip-vith14'
if emb_engine == 'bge-m3':
emb_url = 'https://embedding.opencomputing.net:10443/txte/api/embed'
@ -156,7 +156,7 @@ async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=60)) as ses
meta = j2.dumps({"face": face_count, "speakers": speakers}, ensure_ascii=False)
async with get_sor_context(env, 'rag') as sor:
await sor.sqlExe(
"UPDATE documents SET status='done',chunk_count=0,metadata=${meta}$ WHERE id=${id}$",
"UPDATE rag_documents SET status='done',chunk_count=0,metadata=${meta}$ WHERE id=${id}$",
{"meta": meta, "id": fid})
results.append(f"🎬 {fn}: faces={face_count} speakers={speakers}")
continue
@ -201,10 +201,10 @@ async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=60)) as ses
async with get_sor_context(env, 'rag') as sor:
for i, ct in enumerate(chunks):
await sor.sqlExe(
"INSERT INTO document_chunks (id,doc_id,kb_id,chunk_index,content,vector_id,created_at) VALUES (${id}$,${doc_id}$,${kb_id}$,${idx}$,${content}$,${vid}$,NOW())",
"INSERT INTO rag_document_chunks (id,doc_id,kb_id,chunk_index,content,vector_id,created_at) VALUES (${id}$,${doc_id}$,${kb_id}$,${idx}$,${content}$,${vid}$,NOW())",
{"id": f"{fid}_c{i}", "doc_id": fid, "kb_id": kb_id, "idx": i, "content": ct[:2000], "vid": ""})
await sor.sqlExe("UPDATE documents SET status='done',chunk_count=${n}$ WHERE id=${id}$", {"n": len(chunks), "id": fid})
await sor.sqlExe("UPDATE knowledge_bases SET chunk_count=chunk_count+${n}$ WHERE id=${kb_id}$", {"n": len(chunks), "kb_id": kb_id})
await sor.sqlExe("UPDATE rag_documents SET status='done',chunk_count=${n}$ WHERE id=${id}$", {"n": len(chunks), "id": fid})
await sor.sqlExe("UPDATE rag_knowledge_bases SET chunk_count=chunk_count+${n}$ WHERE id=${kb_id}$", {"n": len(chunks), "kb_id": kb_id})
results.append(f"✓ {fn}: {len(text)} chars → {len(chunks)} chunks")

View File

@ -12,7 +12,7 @@ async with db.sqlorContext(dbname) as sor:
if emb_type not in ("bge-m3", "clip-vith14"):
emb_type = "clip-vith14"
await sor.sqlExe(
"INSERT INTO knowledge_bases (id, name, description, org_id, embedding_engine, vdb_collection, doc_count, total_size, chunk_count, status, created_at) "
"INSERT INTO rag_knowledge_bases (id, name, description, org_id, embedding_engine, vdb_collection, doc_count, total_size, chunk_count, status, created_at) "
"VALUES (${id}$, ${name}$, ${desc}$, ${org_id}$, ${emb}$, 'rag_collection', 0, 0, 0, 'active', NOW())",
{"id": kb_id, "name": name, "desc": desc, "org_id": userorgid, "emb": emb_type})
return {

View File

@ -12,7 +12,7 @@ userorgid = await env.get_userorgid()
async with get_sor_context(env, 'rag') as sor:
recs = await sor.sqlExe(
"SELECT file_path, file_size, kb_id FROM documents WHERE id=${id}$",
"SELECT file_path, file_size, kb_id FROM rag_documents WHERE id=${id}$",
{"id": doc_id})
if not recs:
return json.dumps({"status": "error", "error": "file not found"}, ensure_ascii=False)
@ -21,12 +21,12 @@ async with get_sor_context(env, 'rag') as sor:
file_size = doc.file_size
# Delete document + chunks
await sor.sqlExe("DELETE FROM document_chunks WHERE doc_id=${doc_id}$", {"doc_id": doc_id})
await sor.sqlExe("DELETE FROM documents WHERE id=${id}$", {"id": doc_id})
await sor.sqlExe("DELETE FROM rag_document_chunks WHERE doc_id=${doc_id}$", {"doc_id": doc_id})
await sor.sqlExe("DELETE FROM rag_documents WHERE id=${id}$", {"id": doc_id})
# Update KB stats
await sor.sqlExe(
"UPDATE knowledge_bases SET doc_count=GREATEST(doc_count-1, 0), total_size=GREATEST(total_size-${size}$, 0) WHERE id=${kb_id}$",
"UPDATE rag_knowledge_bases SET doc_count=GREATEST(doc_count-1, 0), total_size=GREATEST(total_size-${size}$, 0) WHERE id=${kb_id}$",
{"size": file_size, "kb_id": kb_id})
# Delete physical file via env.realpath
@ -45,11 +45,11 @@ rows = []
async with db.sqlorContext(dbname) as sor:
if folder_id and folder_id != '__root__':
recs = await sor.sqlExe(
"SELECT id, file_name, file_type, file_size, file_path, status, chunk_count, created_at FROM documents WHERE kb_id=${kb_id}$ AND folder_id=${folder_id}$ ORDER BY created_at DESC",
"SELECT id, file_name, file_type, file_size, file_path, status, chunk_count, created_at FROM rag_documents WHERE kb_id=${kb_id}$ AND folder_id=${folder_id}$ ORDER BY created_at DESC",
{"kb_id": kb_id, "folder_id": folder_id})
else:
recs = await sor.sqlExe(
"SELECT id, file_name, file_type, file_size, file_path, status, chunk_count, created_at FROM documents WHERE kb_id=${kb_id}$ AND (folder_id IS NULL OR folder_id='') ORDER BY created_at DESC",
"SELECT id, file_name, file_type, file_size, file_path, status, chunk_count, created_at FROM rag_documents WHERE kb_id=${kb_id}$ AND (folder_id IS NULL OR folder_id='') ORDER BY created_at DESC",
{"kb_id": kb_id})
for r in recs:
rows.append({

View File

@ -14,21 +14,21 @@ deleted = {"documents": 0, "chunks": 0, "tags": 0, "media_tags": 0,
async with get_sor_context(env, 'rag') as sor:
kb = await sor.sqlExe(
"SELECT id, name FROM knowledge_bases WHERE id=${id}$ AND (org_id=${org_id}$ OR org_id IS NULL)",
"SELECT id, name FROM rag_knowledge_bases WHERE id=${id}$ AND (org_id=${org_id}$ OR org_id IS NULL)",
{"id": kb_id, "org_id": userorgid})
if not kb:
return {"widgettype": "urlwidget",
"options": {"url": entire_url('/rag/knowledge_bases_list/index.ui')}}
docs = await sor.sqlExe(
"SELECT id, file_path FROM documents WHERE kb_id=${kb_id}$",
"SELECT id, file_path FROM rag_documents WHERE kb_id=${kb_id}$",
{"kb_id": kb_id})
doc_ids = [d.id for d in docs]
# 1. VDB cleanup
if doc_ids:
chunks = await sor.sqlExe(
"SELECT vector_id FROM document_chunks WHERE doc_id IN (${ids}$) AND vector_id IS NOT NULL AND vector_id != ''",
"SELECT vector_id FROM rag_document_chunks WHERE doc_id IN (${ids}$) AND vector_id IS NOT NULL AND vector_id != ''",
{"ids": doc_ids})
vector_ids = [c.vector_id for c in chunks]
if vector_ids:
@ -53,40 +53,40 @@ async with get_sor_context(env, 'rag') as sor:
# 3. Entities & relations
er = await sor.sqlExe(
"SELECT COUNT(*) AS cnt FROM entity_relations WHERE kb_id=${kb_id}$",
"SELECT COUNT(*) AS cnt FROM rag_entity_relations WHERE kb_id=${kb_id}$",
{"kb_id": kb_id})
await sor.sqlExe("DELETE FROM entity_relations WHERE kb_id=${kb_id}$", {"kb_id": kb_id})
await sor.sqlExe("DELETE FROM rag_entity_relations WHERE kb_id=${kb_id}$", {"kb_id": kb_id})
deleted["entities"] += er[0].cnt if er else 0
ent = await sor.sqlExe(
"SELECT COUNT(*) AS cnt FROM entities WHERE kb_id=${kb_id}$",
"SELECT COUNT(*) AS cnt FROM rag_entities WHERE kb_id=${kb_id}$",
{"kb_id": kb_id})
await sor.sqlExe("DELETE FROM entities WHERE kb_id=${kb_id}$", {"kb_id": kb_id})
await sor.sqlExe("DELETE FROM rag_entities WHERE kb_id=${kb_id}$", {"kb_id": kb_id})
deleted["entities"] += ent[0].cnt if ent else 0
# 4. Chunks
ch = await sor.sqlExe(
"SELECT COUNT(*) AS cnt FROM document_chunks WHERE doc_id IN (${ids}$)",
"SELECT COUNT(*) AS cnt FROM rag_document_chunks WHERE doc_id IN (${ids}$)",
{"ids": doc_ids})
await sor.sqlExe("DELETE FROM document_chunks WHERE doc_id IN (${ids}$)", {"ids": doc_ids})
await sor.sqlExe("DELETE FROM rag_document_chunks WHERE doc_id IN (${ids}$)", {"ids": doc_ids})
deleted["chunks"] = ch[0].cnt if ch else 0
# Media tags
mt = await sor.sqlExe("SELECT COUNT(*) AS cnt FROM media_tags WHERE kb_id=${kb_id}$", {"kb_id": kb_id})
await sor.sqlExe("DELETE FROM media_tags WHERE kb_id=${kb_id}$", {"kb_id": kb_id})
mt = await sor.sqlExe("SELECT COUNT(*) AS cnt FROM rag_media_tags WHERE kb_id=${kb_id}$", {"kb_id": kb_id})
await sor.sqlExe("DELETE FROM rag_media_tags WHERE kb_id=${kb_id}$", {"kb_id": kb_id})
deleted["media_tags"] = mt[0].cnt if mt else 0
# Tags
t = await sor.sqlExe("SELECT COUNT(*) AS cnt FROM tags WHERE kb_id=${kb_id}$", {"kb_id": kb_id})
await sor.sqlExe("DELETE FROM tags WHERE kb_id=${kb_id}$", {"kb_id": kb_id})
t = await sor.sqlExe("SELECT COUNT(*) AS cnt FROM rag_tags WHERE kb_id=${kb_id}$", {"kb_id": kb_id})
await sor.sqlExe("DELETE FROM rag_tags WHERE kb_id=${kb_id}$", {"kb_id": kb_id})
deleted["tags"] = t[0].cnt if t else 0
# Documents
await sor.sqlExe("DELETE FROM documents WHERE kb_id=${kb_id}$", {"kb_id": kb_id})
await sor.sqlExe("DELETE FROM rag_documents WHERE kb_id=${kb_id}$", {"kb_id": kb_id})
deleted["documents"] = len(doc_ids)
# KB itself
await sor.sqlExe("DELETE FROM knowledge_bases WHERE id=${id}$", {"id": kb_id})
await sor.sqlExe("DELETE FROM rag_knowledge_bases WHERE id=${id}$", {"id": kb_id})
# Physical files — paths are like /44/126/174/61/file.mp4
for d in docs:

View File

@ -2,6 +2,6 @@ ns = params_kw.copy()
db = DBPools()
dbname = get_module_dbname('rag')
async with db.sqlorContext(dbname) as sor:
await sor.sqlExe("DELETE FROM document_chunks WHERE id=${id}$", {"id": ns.get("id","")})
await sor.sqlExe("DELETE FROM rag_document_chunks WHERE id=${id}$", {"id": ns.get("id","")})
return {"widgettype": "Message", "options": {"user_data": {"id": ns.get("id","")}}}
return {"error": "failed"}

View File

@ -14,11 +14,11 @@ try:
async with db.sqlorContext(dbname) as sor:
if folder_id:
recs = await sor.sqlExe(
"SELECT id, file_name, file_type, file_size, file_path, status, chunk_count, created_at FROM documents WHERE kb_id=${kb_id}$ AND folder_id=${folder_id}$ ORDER BY created_at DESC",
"SELECT id, file_name, file_type, file_size, file_path, status, chunk_count, created_at FROM rag_documents WHERE kb_id=${kb_id}$ AND folder_id=${folder_id}$ ORDER BY created_at DESC",
{"kb_id": kb_id, "folder_id": folder_id})
else:
recs = await sor.sqlExe(
"SELECT id, file_name, file_type, file_size, file_path, status, chunk_count, created_at FROM documents WHERE kb_id=${kb_id}$ AND (folder_id IS NULL OR folder_id IN ('','__root__')) ORDER BY created_at DESC",
"SELECT id, file_name, file_type, file_size, file_path, status, chunk_count, created_at FROM rag_documents WHERE kb_id=${kb_id}$ AND (folder_id IS NULL OR folder_id IN ('','__root__')) ORDER BY created_at DESC",
{"kb_id": kb_id})
for r in recs:
rows.append({

View File

@ -7,7 +7,7 @@ try:
async with db.sqlorContext(dbname) as sor:
if not id:
recs = await sor.sqlExe(
"SELECT id, '' as parentid, content as label FROM document_chunks WHERE kb_id=${kb_id}$ AND chunk_type='directory' AND (description IS NULL OR description='') ORDER BY content",
"SELECT id, '' as parentid, content as label FROM rag_document_chunks WHERE kb_id=${kb_id}$ AND chunk_type='directory' AND (description IS NULL OR description='') ORDER BY content",
{"kb_id": kb_id})
result = [{"id": "__root__", "parentid": "", "label": "📁 根目录"}]
for r in recs:
@ -15,7 +15,7 @@ try:
return result
else:
recs = await sor.sqlExe(
"SELECT id, description as parentid, content as label FROM document_chunks WHERE kb_id=${kb_id}$ AND chunk_type='directory' AND description=${id}$ ORDER BY content",
"SELECT id, description as parentid, content as label FROM rag_document_chunks WHERE kb_id=${kb_id}$ AND chunk_type='directory' AND description=${id}$ ORDER BY content",
{"kb_id": kb_id, "id": id})
return [dict(r) for r in recs]
except:

View File

@ -3,7 +3,7 @@ userorgid = await env.get_userorgid()
cards = []
async with get_sor_context(env, 'rag') as sor:
recs = await sor.sqlExe(
"SELECT * FROM knowledge_bases WHERE org_id=${org_id}$ ORDER BY created_at DESC",
"SELECT * FROM rag_knowledge_bases WHERE org_id=${org_id}$ ORDER BY created_at DESC",
{"org_id": userorgid})
for r in recs:
doc_count = r.doc_count or 0

View File

@ -3,7 +3,7 @@ env = request._run_ns
userorgid = await env.get_userorgid()
async with get_sor_context(env, 'rag') as sor:
recs = await sor.sqlExe(
"SELECT id, name FROM knowledge_bases WHERE org_id=${org_id}$ ORDER BY created_at DESC",
"SELECT id, name FROM rag_knowledge_bases WHERE org_id=${org_id}$ ORDER BY created_at DESC",
{"org_id": userorgid})
items = [{"value": r.id, "text": r.name} for r in recs]
return json.dumps(items, ensure_ascii=False)

View File

@ -12,14 +12,14 @@ try:
async with db.sqlorContext(dbname) as sor:
if kind == 'voice':
recs = await sor.sqlExe(
"SELECT id, file_name, file_path, metadata, created_at FROM documents "
"SELECT id, file_name, file_path, metadata, created_at FROM rag_documents "
"WHERE kb_id=${kb_id}$ AND (metadata LIKE '%%voiceprint_status%%done%%' OR LOWER(file_name) LIKE '%%.mp3' OR LOWER(file_name) LIKE '%%.wav' "
"OR LOWER(file_name) LIKE '%%.m4a' OR LOWER(file_name) LIKE '%%.aac' OR LOWER(file_name) LIKE '%%.ogg' "
"OR LOWER(file_name) LIKE '%%.flac') ORDER BY created_at DESC",
{"kb_id": kb_id})
else:
recs = await sor.sqlExe(
"SELECT id, file_name, file_path, metadata, created_at FROM documents "
"SELECT id, file_name, file_path, metadata, created_at FROM rag_documents "
"WHERE kb_id=${kb_id}$ AND (LOWER(file_name) LIKE '%%.jpg' OR LOWER(file_name) LIKE '%%.jpeg' "
"OR LOWER(file_name) LIKE '%%.png' OR LOWER(file_name) LIKE '%%.gif' OR LOWER(file_name) LIKE '%%.webp' "
"OR LOWER(file_name) LIKE '%%.bmp') ORDER BY created_at DESC",
@ -41,8 +41,8 @@ try:
try:
id_list = ','.join(["'" + str(x) + "'" for x in doc_ids])
mt_recs = await sor.sqlExe(
"SELECT mt.media_id, t.name, t.color FROM media_tags mt " +
"JOIN tags t ON mt.tag_id=t.id " +
"SELECT mt.media_id, t.name, t.color FROM rag_media_tags mt " +
"JOIN rag_tags t ON mt.tag_id=t.id " +
"WHERE mt.media_type='document' AND mt.media_id IN (" + id_list + ")",
ns={})
for mt in mt_recs:

View File

@ -7,7 +7,7 @@ async with db.sqlorContext(dbname) as sor:
parent_id = ns.get("parentid", "")
kb_id = ns.get("kb_id", "")
await sor.sqlExe(
"INSERT INTO document_chunks (id, doc_id, kb_id, chunk_index, chunk_type, content, description, created_at) "
"INSERT INTO rag_document_chunks (id, doc_id, kb_id, chunk_index, chunk_type, content, description, created_at) "
"VALUES (${id}$, '', ${kb_id}$, 0, 'directory', ${name}$, ${parentid}$, NOW())",
{"id": dir_id, "kb_id": kb_id, "name": name, "parentid": parent_id})
return {"widgettype": "Message", "options": {"user_data": {"id": dir_id, "label": name, "parentid": parent_id}}}

View File

@ -11,7 +11,7 @@ userorgid = await env.get_userorgid()
async with get_sor_context(env, 'rag') as sor:
await sor.sqlExe(
"UPDATE knowledge_bases SET name=${name}$, description=${desc}$, updated_at=NOW() "
"UPDATE rag_knowledge_bases SET name=${name}$, description=${desc}$, updated_at=NOW() "
"WHERE id=${id}$ AND (org_id=${org_id}$ OR org_id IS NULL)",
{"name": name, "desc": desc, "id": kb_id, "org_id": userorgid})

View File

@ -11,7 +11,7 @@ env = request._run_ns
if not name:
async with get_sor_context(env, 'rag') as sor:
recs = await sor.sqlExe(
"SELECT name, description FROM knowledge_bases WHERE id=${id}$",
"SELECT name, description FROM rag_knowledge_bases WHERE id=${id}$",
{"id": kb_id})
if recs:
name = recs[0].name or ''

View File

@ -22,18 +22,18 @@ else:
added = 0; removed = 0
async with get_sor_context(env, 'rag') as sor:
recs = await sor.sqlExe(
"SELECT id, tag_id FROM media_tags WHERE media_type='document' AND media_id=${mid}$",
"SELECT id, tag_id FROM rag_media_tags WHERE media_type='document' AND media_id=${mid}$",
{"mid": doc_id})
current = {r.tag_id: r.id for r in recs}
for tid, mt_id in list(current.items()):
if tid not in wanted_ids:
await sor.sqlExe("DELETE FROM media_tags WHERE id=${id}$", {"id": mt_id})
await sor.sqlExe("DELETE FROM rag_media_tags WHERE id=${id}$", {"id": mt_id})
removed += 1
for tid in wanted_ids:
if tid not in current:
mt_id = uuid()
await sor.sqlExe(
"INSERT INTO media_tags (id, kb_id, media_type, media_id, tag_id, created_at) "
"INSERT INTO rag_media_tags (id, kb_id, media_type, media_id, tag_id, created_at) "
"VALUES (${id}$, ${kb_id}$, 'document', ${mid}$, ${tid}$, NOW())",
{"id": mt_id, "kb_id": kb_id, "mid": doc_id, "tid": tid})
added += 1

View File

@ -51,7 +51,7 @@ if tag_ids_str:
for i, tid in enumerate(wanted_tags):
placeholders.append("${tid_" + str(i) + "}$")
nsq["tid_" + str(i)] = tid
sql = ("SELECT media_id FROM media_tags "
sql = ("SELECT media_id FROM rag_media_tags "
"WHERE " + kb_filter + "media_type='document' AND tag_id IN (" + ",".join(placeholders) + ") "
"GROUP BY media_id HAVING COUNT(DISTINCT tag_id)=" + str(len(wanted_tags)))
recs = await sor.sqlExe(sql, nsq)
@ -79,7 +79,7 @@ kw_rows = []
emb_engine = 'clip-vith14'
try:
async with get_sor_context(env, 'rag') as sor:
krecs = await sor.sqlExe("SELECT embedding_engine FROM knowledge_bases WHERE id=${kb_id}$", {"kb_id": kb_id})
krecs = await sor.sqlExe("SELECT embedding_engine FROM rag_knowledge_bases WHERE id=${kb_id}$", {"kb_id": kb_id})
if krecs:
emb_engine = (getattr(krecs[0], 'embedding_engine', '') or 'clip-vith14').strip()
except: pass
@ -126,7 +126,7 @@ if query:
for i, t in enumerate(tokens):
conds.append("content LIKE ${kw_" + str(i) + "}$")
nsq["kw_" + str(i)] = "%" + t + "%"
ksql = "SELECT id, doc_id, content FROM document_chunks WHERE kb_id=${kb_id}$ AND (" + " OR ".join(conds) + ") LIMIT 20"
ksql = "SELECT id, doc_id, content FROM rag_document_chunks WHERE kb_id=${kb_id}$ AND (" + " OR ".join(conds) + ") LIMIT 20"
async with get_sor_context(env, 'rag') as sor:
krecs = await sor.sqlExe(ksql, nsq)
for r in krecs:
@ -145,7 +145,7 @@ if query:
chunk_text = ''
async with get_sor_context(env, 'rag') as sor:
recs = await sor.sqlExe(
"SELECT content, doc_id, metadata FROM document_chunks WHERE id=${id}$",
"SELECT content, doc_id, metadata FROM rag_document_chunks WHERE id=${id}$",
{"id": rid})
chunk_meta = {}
if recs:
@ -190,7 +190,7 @@ else:
placeholders2.append("${did_" + str(i) + "}$")
nsq2["did_" + str(i)] = did
# Get documents (exclude face/voice generated derivatives)
sql2 = ("SELECT id, file_name, kb_id, file_path FROM documents WHERE " + kb_cond +
sql2 = ("SELECT id, file_name, kb_id, file_path FROM rag_documents WHERE " + kb_cond +
"id IN (" + ",".join(placeholders2) + ") AND (metadata IS NULL OR metadata NOT LIKE '%%\"face\"%%') ORDER BY created_at DESC LIMIT " + str(top_k * 2))
docs = await sor.sqlExe(sql2, nsq2)
doc_list = [(r.id, r.file_name or '', r.kb_id or '', r.file_path or '') for r in docs]
@ -203,7 +203,7 @@ else:
for i, did in enumerate(doc_ids):
chunk_placeholders.append("${cdid_" + str(i) + "}$")
nsq_c["cdid_" + str(i)] = did
chunks_sql = ("SELECT id, doc_id, content FROM document_chunks WHERE doc_id IN (" +
chunks_sql = ("SELECT id, doc_id, content FROM rag_document_chunks WHERE doc_id IN (" +
",".join(chunk_placeholders) + ") ORDER BY created_at DESC LIMIT " + str(top_k))
chunk_recs = await sor.sqlExe(chunks_sql, nsq_c)
chunks_by_doc = {}

View File

@ -2,7 +2,7 @@ ns = params_kw.copy()
env = request._run_ns
async with get_sor_context(env, 'rag') as sor:
rec = await sor.sqlExe("SELECT COALESCE(SUM(total_size),0) used FROM knowledge_bases", {})
rec = await sor.sqlExe("SELECT COALESCE(SUM(total_size),0) used FROM rag_knowledge_bases", {})
used_bytes = int(rec[0].used) if rec else 0
used_mb = round(used_bytes / 1048576, 1)

View File

@ -9,13 +9,13 @@ docs = 0
async with get_sor_context(env, 'rag') as sor:
rec = await sor.sqlExe(
"SELECT COALESCE(SUM(d.file_size),0) AS used, COUNT(*) AS docs "
"FROM documents d JOIN knowledge_bases k ON d.kb_id=k.id "
"FROM rag_documents d JOIN rag_knowledge_bases k ON d.kb_id=k.id "
"WHERE k.org_id=${org_id}$",
{"org_id": userorgid})
if rec:
used = int(rec[0].used)
docs = int(rec[0].docs)
lim = await sor.sqlExe("SELECT limit_bytes FROM org_storage_limits WHERE org_id=${org_id}$", {"org_id": userorgid})
lim = await sor.sqlExe("SELECT limit_bytes FROM rag_org_storage_limits WHERE org_id=${org_id}$", {"org_id": userorgid})
if lim:
limit = int(lim[0].limit_bytes)

View File

@ -17,14 +17,14 @@ all_tags = [] # all tags for kb: list of (id, name, color)
try:
async with db.sqlorContext(dbname) as sor:
recs = await sor.sqlExe(
"SELECT file_name FROM documents WHERE id=${id}$ AND kb_id=${kb_id}$",
"SELECT file_name FROM rag_documents WHERE id=${id}$ AND kb_id=${kb_id}$",
{"id": doc_id, "kb_id": kb_id})
if recs:
fname = recs[0].file_name or ''
# 有哪些标签
tag_recs = await sor.sqlExe(
"SELECT id, name, color FROM tags WHERE kb_id=${kb_id}$ ORDER BY created_at",
"SELECT id, name, color FROM rag_tags WHERE kb_id=${kb_id}$ ORDER BY created_at",
{"kb_id": kb_id})
for t in tag_recs:
all_tags.append((t.id, t.name, t.color or '#3b82f6'))
@ -32,7 +32,7 @@ try:
# 当前已关联的
mt_recs = await sor.sqlExe(
"SELECT tag_id FROM media_tags WHERE media_type=${type}$ AND media_id=${mid}$",
"SELECT tag_id FROM rag_media_tags WHERE media_type=${type}$ AND media_id=${mid}$",
{"type": media_type, "mid": doc_id})
cur_tag_ids = [r.tag_id for r in mt_recs]
except Exception:

View File

@ -3,7 +3,7 @@ env = request._run_ns
userorgid = await env.get_userorgid()
async with get_sor_context(env, 'rag') as sor:
recs = await sor.sqlExe(
"SELECT id, name, color FROM tags WHERE org_id=${org_id}$ ORDER BY created_at",
"SELECT id, name, color FROM rag_tags WHERE org_id=${org_id}$ ORDER BY created_at",
{"org_id": userorgid})
items = [{"value": r.id, "text": r.name, "color": r.color or '#3b82f6'} for r in recs]
return json.dumps(items, ensure_ascii=False)

View File

@ -4,7 +4,7 @@ dbname = get_module_dbname('rag')
async with db.sqlorContext(dbname) as sor:
id = ns.get("id", "")
name = ns.get("name", "")
await sor.sqlExe("UPDATE document_chunks SET content=${name}$ WHERE id=${id}$",
await sor.sqlExe("UPDATE rag_document_chunks SET content=${name}$ WHERE id=${id}$",
{"name": name, "id": id})
return {"widgettype": "Message", "options": {"user_data": {"id": id, "label": name}}}
return {"error": "failed"}

View File

@ -23,9 +23,9 @@ def fmt_bytes(n):
quota_limit = 104857600
used = 0
async with get_sor_context(env, 'rag') as sor:
rec = await sor.sqlExe("SELECT COALESCE(SUM(file_size),0) AS used FROM documents WHERE org_id=${org_id}$", {"org_id": userorgid})
rec = await sor.sqlExe("SELECT COALESCE(SUM(file_size),0) AS used FROM rag_documents WHERE org_id=${org_id}$", {"org_id": userorgid})
if rec: used = int(rec[0].used)
lim = await sor.sqlExe("SELECT limit_bytes FROM org_storage_limits WHERE org_id=${org_id}$", {"org_id": userorgid})
lim = await sor.sqlExe("SELECT limit_bytes FROM rag_org_storage_limits WHERE org_id=${org_id}$", {"org_id": userorgid})
if lim: quota_limit = int(lim[0].limit_bytes)
if used + file_size > quota_limit:
return json.dumps({"status": "error", "error": "storage_quota_exceeded",
@ -52,7 +52,7 @@ async def ingest_doc(doc_id, kb_id, file_name, ext_l, real_path):
emb_engine = 'clip-vith14'
try:
async with db.sqlorContext('rag') as sor:
krecs = await sor.sqlExe("SELECT embedding_engine FROM knowledge_bases WHERE id=${kb_id}$", {"kb_id": kb_id})
krecs = await sor.sqlExe("SELECT embedding_engine FROM rag_knowledge_bases WHERE id=${kb_id}$", {"kb_id": kb_id})
if krecs:
emb_engine = (getattr(krecs[0], 'embedding_engine', '') or 'clip-vith14').strip()
except: pass
@ -118,7 +118,7 @@ async def ingest_doc(doc_id, kb_id, file_name, ext_l, real_path):
try:
async with db.sqlorContext('rag') as sor:
await sor.sqlExe(
"UPDATE documents SET status='failed', metadata=${meta}$, updated_at=NOW() WHERE id=${id}$",
"UPDATE rag_documents SET status='failed', metadata=${meta}$, updated_at=NOW() WHERE id=${id}$",
{"id": doc_id, "meta": json.dumps({"error": "文本知识库不支持媒体文件,请上传文本类文件(txt/md/pdf/docx等)或改用多媒体知识库"}, ensure_ascii=False)})
except: pass
return
@ -195,7 +195,7 @@ async def ingest_doc(doc_id, kb_id, file_name, ext_l, real_path):
chunk_meta["bboxes"] = frame_bboxes
async with db.sqlorContext('rag') as sor:
await sor.sqlExe(
"INSERT INTO document_chunks (id, doc_id, kb_id, chunk_index, content, vector_id, metadata, created_at) "
"INSERT INTO rag_document_chunks (id, doc_id, kb_id, chunk_index, content, vector_id, metadata, created_at) "
"VALUES (${id}$, ${doc_id}$, ${kb_id}$, 0, ${content}$, ${vid}$, ${meta}$, NOW())",
{"id": doc_id + "_c0", "doc_id": doc_id, "kb_id": kb_id,
"content": file_name, "vid": doc_id + "_c0",
@ -277,7 +277,7 @@ async def ingest_doc(doc_id, kb_id, file_name, ext_l, real_path):
for i, chunk_text in enumerate(chunks):
vid = vector_ids[i] if i < len(vector_ids) else ''
await sor.sqlExe(
"INSERT INTO document_chunks (id, doc_id, kb_id, chunk_index, content, vector_id, created_at) "
"INSERT INTO rag_document_chunks (id, doc_id, kb_id, chunk_index, content, vector_id, created_at) "
"VALUES (${id}$, ${doc_id}$, ${kb_id}$, ${idx}$, ${content}$, ${vid}$, NOW())",
{"id": doc_id + "_c" + str(i), "doc_id": doc_id, "kb_id": kb_id,
"idx": i, "content": chunk_text[:2000], "vid": vid})
@ -286,7 +286,7 @@ async def ingest_doc(doc_id, kb_id, file_name, ext_l, real_path):
try:
async with db.sqlorContext('rag') as sor:
await sor.sqlExe(
"UPDATE documents SET status='failed', metadata=${meta}$, updated_at=NOW() WHERE id=${id}$",
"UPDATE rag_documents SET status='failed', metadata=${meta}$, updated_at=NOW() WHERE id=${id}$",
{"id": doc_id, "meta": json.dumps({"error": str(e)[:300]}, ensure_ascii=False)})
except: pass
return
@ -296,11 +296,11 @@ async def ingest_doc(doc_id, kb_id, file_name, ext_l, real_path):
try:
async with db.sqlorContext('rag') as sor:
await sor.sqlExe(
"UPDATE documents SET status='done', chunk_count=${chunks}$, metadata=${meta}$, updated_at=NOW() WHERE id=${id}$",
"UPDATE rag_documents SET status='done', chunk_count=${chunks}$, metadata=${meta}$, updated_at=NOW() WHERE id=${id}$",
{"id": doc_id, "chunks": chunks_n, "meta": meta_json})
if chunks_n:
await sor.sqlExe(
"UPDATE knowledge_bases SET chunk_count=chunk_count+${n}$ WHERE id=${kb_id}$",
"UPDATE rag_knowledge_bases SET chunk_count=chunk_count+${n}$ WHERE id=${kb_id}$",
{"n": chunks_n, "kb_id": kb_id})
except: pass
@ -310,12 +310,12 @@ async def ingest_doc(doc_id, kb_id, file_name, ext_l, real_path):
# ============================================================
async with get_sor_context(env, 'rag') as sor:
await sor.sqlExe(
"INSERT INTO documents (id, kb_id, folder_id, file_name, file_type, file_size, file_path, mime_type, status, chunk_count, metadata, org_id, created_at, updated_at) "
"INSERT INTO rag_documents (id, kb_id, folder_id, file_name, file_type, file_size, file_path, mime_type, status, chunk_count, metadata, org_id, created_at, updated_at) "
"VALUES (${id}$, ${kb_id}$, ${folder_id}$, ${file_name}$, 'other', ${file_size}$, ${file_path}$, 'application/octet-stream', 'pending', 0, '{}', ${org_id}$, NOW(), NOW())",
{"id": doc_id, "kb_id": kb_id, "folder_id": folder_id, "file_name": file_name,
"file_size": file_size, "file_path": web_path, "org_id": userorgid})
await sor.sqlExe(
"UPDATE knowledge_bases SET doc_count=doc_count+1, total_size=total_size+${size}$ WHERE id=${kb_id}$",
"UPDATE rag_knowledge_bases SET doc_count=doc_count+1, total_size=total_size+${size}$ WHERE id=${kb_id}$",
{"size": file_size, "kb_id": kb_id})
# Fire background ingestion — pass primitives only (no env/request/proxy objects)