mongo.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import threading
  2. from pymongo import MongoClient
  3. from bson import ObjectId
  4. from datetime import datetime
  5. from dotenv import load_dotenv
  6. import os
  7. load_dotenv()
  8. MONGO_URI = os.getenv("ARK_LOGS_MONGO_URI")
  9. client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=5000)
  10. # 数据库
  11. db = client["arklogs"]
  12. # 豆包大模型的对话日志
  13. chat_logs = db["chat_logs"]
  14. # 聊天历史记录
  15. chat_history_col = db["chat_history"]
  16. # 兴趣圈集合
  17. circle_prompts = db["circle_prompt"]
  18. # 历史人物集合
  19. historical_figures = db["historical_figures"]
  20. def _ensure_index():
  21. try:
  22. chat_logs.create_index([("user_id", 1), ("asked_at", -1)])
  23. chat_history_col.create_index([("user_id", 1), ("session_id", 1), ("timestamp", -1)])
  24. except Exception:
  25. pass
  26. def _do_save_chat_log(doc: dict):
  27. try:
  28. _ensure_index()
  29. chat_logs.insert_one(doc)
  30. except Exception as e:
  31. print(f"MongoDB 日志写入失败: {e}")
  32. def save_chat_log(
  33. user_id: str,
  34. question: str,
  35. stream_mode: bool,
  36. raw_response: str = None,
  37. status: str = "success",
  38. error: str = None,
  39. ):
  40. """
  41. 保存聊天原始响应日志到 MongoDB(异步写入,不阻塞调用方)
  42. Args:
  43. user_id: 提问人
  44. question: 提问的问题
  45. stream_mode: 回答方式(流式或非流式)
  46. raw_response: API 原始响应的 repr 字符串
  47. status: 响应状态 success | error
  48. error: 异常时的错误信息
  49. """
  50. doc = {
  51. "user_id": user_id,
  52. "question": question,
  53. "stream_mode": stream_mode,
  54. "raw_response": raw_response,
  55. "status": status,
  56. "error": error,
  57. "asked_at": datetime.now(),
  58. }
  59. threading.Thread(target=_do_save_chat_log, args=(doc,), daemon=True).start()
  60. def save_chat_history(
  61. user_id: str,
  62. session_id: str,
  63. role: str,
  64. content: str,
  65. timestamp: datetime,
  66. response_id: str = None,
  67. thinking: str = None,
  68. searching: str = None,
  69. attachments: list = None,
  70. input_tokens: int = None,
  71. output_tokens: int = None,
  72. total_tokens: int = None,
  73. app_name:str=None,
  74. ):
  75. try:
  76. _ensure_index()
  77. doc = {
  78. "user_id": user_id,
  79. "session_id": session_id,
  80. "role": role,
  81. "content": content,
  82. "thinking": thinking,
  83. "searching": searching,
  84. "response_id": response_id,
  85. "input_tokens": input_tokens,
  86. "output_tokens": output_tokens,
  87. "total_tokens": total_tokens,
  88. "timestamp": timestamp,
  89. "app_name": app_name,
  90. }
  91. if attachments:
  92. doc["attachments"] = attachments
  93. chat_history_col.insert_one(doc)
  94. except Exception as e:
  95. print(f"MongoDB 聊天历史写入失败: {e}")
  96. def get_chat_history(user_id: str, session_id: str) -> list:
  97. try:
  98. docs = chat_history_col.find(
  99. {"user_id": user_id, "session_id": session_id},
  100. {"_id": 0}
  101. ).sort("timestamp", 1)
  102. return list(docs)
  103. except Exception as e:
  104. print(f"MongoDB 聊天历史读取失败: {e}")
  105. return []
  106. def get_last_response_id(user_id: str, session_id: str) -> str | None:
  107. try:
  108. doc = chat_history_col.find_one(
  109. {"user_id": user_id, "session_id": session_id, "role": "assistant", "response_id": {"$ne": None}},
  110. {"response_id": 1, "_id": 0},
  111. sort=[("timestamp", -1)]
  112. )
  113. return doc["response_id"] if doc else None
  114. except Exception as e:
  115. print(f"MongoDB 查询 response_id 失败: {e}")
  116. return None
  117. def delete_chat_history(user_id: str, session_id: str) -> int:
  118. try:
  119. result = chat_history_col.delete_many({"user_id": user_id, "session_id": session_id})
  120. return result.deleted_count
  121. except Exception as e:
  122. print(f"MongoDB 聊天历史删除失败: {e}")
  123. return 0
  124. def get_sessions(user_id: str) -> list:
  125. try:
  126. pipeline = [
  127. {"$match": {"user_id": user_id, "role": "user"}},
  128. {"$sort": {"timestamp": 1}},
  129. {"$group": {
  130. "_id": "$session_id",
  131. "createdAt": {"$first": "$timestamp"},
  132. "preview": {"$first": "$content"},
  133. }},
  134. {"$sort": {"createdAt": -1}},
  135. {"$project": {
  136. "_id": 0,
  137. "sessionId": "$_id",
  138. "createdAt": 1,
  139. "preview": {"$substrCP": ["$preview", 0, 20]},
  140. }},
  141. ]
  142. return list(chat_history_col.aggregate(pipeline))
  143. except Exception as e:
  144. print(f"MongoDB 会话列表查询失败: {e}")
  145. return []
  146. _DEFAULT_PROMPT_CONFIG = {
  147. "name": "兴趣圈",
  148. "role": "活跃用户",
  149. "style": "自然亲切,有活人感",
  150. "keywords": [],
  151. "forbidden": [],
  152. }
  153. def get_circle_prompt(app_name: str) -> dict:
  154. try:
  155. doc = circle_prompts.find_one({"appName": app_name})
  156. return doc if doc else _DEFAULT_PROMPT_CONFIG
  157. except Exception:
  158. return _DEFAULT_PROMPT_CONFIG
  159. def upsert_circle_prompt(data: dict) -> None:
  160. circle_prompts.update_one(
  161. {"appName": data["appName"]},
  162. {"$set": data},
  163. upsert=True,
  164. )
  165. # ===================== 历史人物 =====================
  166. def get_all_figures() -> list:
  167. try:
  168. docs = historical_figures.find({})
  169. return [{"_id": str(doc["_id"]), **{k: v for k, v in doc.items() if k != "_id"}} for doc in docs]
  170. except Exception as e:
  171. print(f"MongoDB 历史人物列表查询失败: {e}")
  172. return []
  173. def get_figure_by_id(figure_id: str) -> dict | None:
  174. try:
  175. doc = historical_figures.find_one({"_id": ObjectId(figure_id)})
  176. if doc:
  177. doc["_id"] = str(doc["_id"])
  178. return doc
  179. except Exception as e:
  180. print(f"MongoDB 历史人物查询失败: {e}")
  181. return None
  182. def insert_figure(data: dict) -> str:
  183. try:
  184. result = historical_figures.insert_one(data)
  185. return str(result.inserted_id)
  186. except Exception as e:
  187. print(f"MongoDB 历史人物新增失败: {e}")
  188. return None
  189. def update_figure(figure_id: str, data: dict) -> int:
  190. try:
  191. result = historical_figures.update_one(
  192. {"_id": ObjectId(figure_id)},
  193. {"$set": data},
  194. )
  195. return result.matched_count
  196. except Exception as e:
  197. print(f"MongoDB 历史人物修改失败: {e}")
  198. return 0
  199. def delete_figure(figure_id: str) -> int:
  200. try:
  201. result = historical_figures.delete_one({"_id": ObjectId(figure_id)})
  202. return result.deleted_count
  203. except Exception as e:
  204. print(f"MongoDB 历史人物删除失败: {e}")
  205. return 0