knowledge_base.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. """
  2. 知识库管理 - MongoDB 操作层
  3. 存储文档元数据、切片(chunks)、租户配置
  4. """
  5. from pymongo import MongoClient
  6. from typing import List, Optional, Dict, Any
  7. from datetime import datetime
  8. from bson.objectid import ObjectId
  9. import os
  10. # MongoDB 连接
  11. MONGO_URI = os.getenv("ARK_LOGS_MONGO_URI", "mongodb://localhost:27017/")
  12. client = MongoClient(MONGO_URI)
  13. db = client["arklogs"]
  14. # 集合定义
  15. documents_collection = db["kb_documents"] # 文档元数据
  16. chunks_collection = db["kb_chunks"] # 文档切片
  17. tenants_collection = db["kb_tenants"] # 租户配置
  18. # ==================== 租户管理 ====================
  19. def get_tenant_config(tenant_id: str) -> Optional[Dict]:
  20. """获取租户配置"""
  21. return tenants_collection.find_one({"tenant_id": tenant_id, "deleted": False})
  22. def upsert_tenant_config(tenant_id: str, config: Dict) -> bool:
  23. """创建或更新租户配置"""
  24. config.update({
  25. "tenant_id": tenant_id,
  26. "updated_at": datetime.utcnow(),
  27. })
  28. result = tenants_collection.update_one(
  29. {"tenant_id": tenant_id},
  30. {"$set": config, "$setOnInsert": {"created_at": datetime.utcnow(), "deleted": False}},
  31. upsert=True
  32. )
  33. return result.acknowledged
  34. def get_agent_config(tenant_id: str) -> Optional[Dict]:
  35. """获取租户的智能客服配置"""
  36. tenant = get_tenant_config(tenant_id)
  37. if not tenant:
  38. return None
  39. return {
  40. "enabled": tenant.get("agent_enabled", False),
  41. "name": tenant.get("agent_name", "智能小助手"),
  42. "greeting": tenant.get("agent_greeting", "您好!我是智能客服小助手,有什么可以帮您的吗?"),
  43. "h5_url": f"https://aichat.zhongsou.com/?tenant={tenant_id}",
  44. }
  45. def update_agent_config(tenant_id: str, enabled: bool, name: str, greeting: str) -> bool:
  46. """更新智能客服配置"""
  47. result = tenants_collection.update_one(
  48. {"tenant_id": tenant_id},
  49. {
  50. "$set": {
  51. "agent_enabled": enabled,
  52. "agent_name": name,
  53. "agent_greeting": greeting,
  54. "updated_at": datetime.utcnow(),
  55. }
  56. },
  57. upsert=True
  58. )
  59. return result.acknowledged
  60. # ==================== 文档管理 ====================
  61. def create_document(tenant_id: str, filename: str, file_type: str, file_size: int, oss_path: str) -> str:
  62. """创建文档记录"""
  63. doc = {
  64. "tenant_id": tenant_id,
  65. "filename": filename,
  66. "file_type": file_type,
  67. "file_size": file_size,
  68. "oss_path": oss_path,
  69. "status": "pending", # pending, processing, completed, failed
  70. "chunk_count": 0,
  71. "created_at": datetime.utcnow(),
  72. "updated_at": datetime.utcnow(),
  73. "deleted": False,
  74. }
  75. result = documents_collection.insert_one(doc)
  76. return str(result.inserted_id)
  77. def get_document_by_id(doc_id: str) -> Optional[Dict]:
  78. """根据ID获取文档"""
  79. try:
  80. doc = documents_collection.find_one({"_id": ObjectId(doc_id), "deleted": False})
  81. if doc:
  82. doc["_id"] = str(doc["_id"])
  83. return doc
  84. except:
  85. return None
  86. def list_documents(tenant_id: str, skip: int = 0, limit: int = 100) -> List[Dict]:
  87. """列出租户的所有文档"""
  88. cursor = documents_collection.find(
  89. {"tenant_id": tenant_id, "deleted": False}
  90. ).sort("created_at", -1).skip(skip).limit(limit)
  91. docs = []
  92. for doc in cursor:
  93. doc["_id"] = str(doc["_id"])
  94. docs.append(doc)
  95. return docs
  96. def count_documents(tenant_id: str) -> int:
  97. """统计租户文档数量"""
  98. return documents_collection.count_documents({"tenant_id": tenant_id, "deleted": False})
  99. def update_document_status(doc_id: str, status: str, chunk_count: int = 0, error_msg: str = None):
  100. """更新文档处理状态"""
  101. update_data = {
  102. "status": status,
  103. "updated_at": datetime.utcnow(),
  104. }
  105. if chunk_count > 0:
  106. update_data["chunk_count"] = chunk_count
  107. if error_msg:
  108. update_data["error_msg"] = error_msg
  109. documents_collection.update_one(
  110. {"_id": ObjectId(doc_id)},
  111. {"$set": update_data}
  112. )
  113. def delete_document(doc_id: str) -> bool:
  114. """软删除文档"""
  115. result = documents_collection.update_one(
  116. {"_id": ObjectId(doc_id)},
  117. {"$set": {"deleted": True, "updated_at": datetime.utcnow()}}
  118. )
  119. return result.modified_count > 0
  120. # ==================== 切片管理 ====================
  121. def create_chunks(doc_id: str, tenant_id: str, chunks_data: List[Dict]) -> List[str]:
  122. """批量创建文档切片"""
  123. chunks = []
  124. for idx, chunk_data in enumerate(chunks_data):
  125. chunk = {
  126. "doc_id": doc_id,
  127. "tenant_id": tenant_id,
  128. "chunk_index": idx,
  129. "content": chunk_data["content"],
  130. "vector_id": chunk_data.get("vector_id"), # Qdrant中的point_id
  131. "metadata": chunk_data.get("metadata", {}),
  132. "created_at": datetime.utcnow(),
  133. "deleted": False,
  134. }
  135. chunks.append(chunk)
  136. result = chunks_collection.insert_many(chunks)
  137. return [str(id) for id in result.inserted_ids]
  138. def get_chunks_by_doc(doc_id: str) -> List[Dict]:
  139. """获取文档的所有切片"""
  140. cursor = chunks_collection.find(
  141. {"doc_id": doc_id, "deleted": False}
  142. ).sort("chunk_index", 1)
  143. chunks = []
  144. for chunk in cursor:
  145. chunk["_id"] = str(chunk["_id"])
  146. chunks.append(chunk)
  147. return chunks
  148. def delete_chunks_by_doc(doc_id: str) -> int:
  149. """删除文档的所有切片"""
  150. result = chunks_collection.update_many(
  151. {"doc_id": doc_id},
  152. {"$set": {"deleted": True}}
  153. )
  154. return result.modified_count
  155. def search_chunks_by_ids(chunk_ids: List[str]) -> List[Dict]:
  156. """根据ID列表获取切片(用于向量检索后的内容获取)"""
  157. chunks = []
  158. for chunk_id in chunk_ids:
  159. chunk = chunks_collection.find_one({"vector_id": chunk_id, "deleted": False})
  160. if chunk:
  161. chunk["_id"] = str(chunk["_id"])
  162. chunks.append(chunk)
  163. return chunks
  164. # ==================== 统计 ====================
  165. def get_tenant_stats(tenant_id: str) -> Dict:
  166. """获取租户的知识库统计数据"""
  167. total_docs = count_documents(tenant_id)
  168. total_chunks = chunks_collection.count_documents({"tenant_id": tenant_id, "deleted": False})
  169. # 获取最近上传的文档
  170. recent_docs = list_documents(tenant_id, skip=0, limit=5)
  171. return {
  172. "total_documents": total_docs,
  173. "total_chunks": total_chunks,
  174. "recent_documents": recent_docs,
  175. }