From d61bb99ffa29dfacdc6fb30e592135834c8b3a83 Mon Sep 17 00:00:00 2001 From: Gaofeng Date: Fri, 28 Nov 2025 02:48:58 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E6=B7=BB=E5=8A=A0=20mem0=5Fsearch=20?= =?UTF-8?q?=E5=92=8C=20mem0=5Fdelete=20TODO=E5=AE=9E=E7=8E=B0=E5=87=BD?= =?UTF-8?q?=E6=95=B0.=202.=20=E5=9C=A8=E6=94=B6=E5=88=B0=E6=96=B0=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E6=97=B6=EF=BC=8C=E8=B0=83=E7=94=A8=20mem0=5Fsearch?= =?UTF-8?q?=20=E8=8E=B7=E5=BE=97=E7=9B=B8=E5=85=B3=E8=AE=B0=E5=BF=86?= =?UTF-8?q?=EF=BC=8C=E5=B0=81=E8=A3=85=E5=88=B0=E4=B8=8A=E4=B8=8B=E6=96=87?= =?UTF-8?q?=203.=20=E5=9C=A8=E5=88=A0=E9=99=A4=E8=81=8A=E5=A4=A9=E6=A1=86?= =?UTF-8?q?=E6=97=B6=E5=80=99=EF=BC=8C=E8=B0=83=E7=94=A8=20mem0=5Fdelete?= =?UTF-8?q?=20=E4=BB=A5=E5=88=A0=E9=99=A4=E7=9B=B8=E5=85=B3=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E6=A1=86=E6=89=80=E6=8A=BD=E5=8F=96=E7=9A=84=E8=AE=B0?= =?UTF-8?q?=E5=BF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/open_webui/main.py | 1 + backend/open_webui/memory/mem0.py | 33 +++++++++++++ backend/open_webui/routers/chats.py | 7 +++ backend/open_webui/utils/middleware.py | 66 +++++++++++++++----------- 4 files changed, 78 insertions(+), 29 deletions(-) create mode 100644 backend/open_webui/memory/mem0.py diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index f3af7a3907..42a30dee1e 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -1588,6 +1588,7 @@ async def chat_completion( """处理完整的聊天流程:Payload 处理 → LLM 调用 → 响应处理""" try: # 8.1 Payload 预处理:执行 Pipeline Filters、工具注入、RAG 检索等 + # remark:并不涉及消息的持久化,只涉及发送给 LLM 前,上下文的封装 form_data, metadata, events = await process_chat_payload( request, form_data, user, metadata, model ) diff --git a/backend/open_webui/memory/mem0.py b/backend/open_webui/memory/mem0.py new file mode 100644 index 0000000000..bab715afb0 --- /dev/null +++ b/backend/open_webui/memory/mem0.py @@ -0,0 +1,33 @@ + +async def mem0_search(user_id: str, chat_id: str, last_message: str) -> list[str]: + """ + 预留的 Mem0 检索接口:当前为占位实现。 + 未来可替换为实际检索逻辑,返回若干相关记忆条目(字符串)。 + 增加 chat_id 便于按会话窗口区分/隔离记忆。 + """ + try: + # TODO: 接入真实 Mem0 检索 + print("mem0_search") + print("user_id:", user_id) + print("chat_id:", chat_id) + print("last_message:", last_message) + return [] + except Exception as e: + log.debug(f"Mem0 search failed: {e}") + return [] + + +async def mem0_delete(user_id: str, chat_id: str) -> bool: + """ + 删除指定用户在指定 chat 窗口下的所有 Mem0 相关记忆(占位实现)。 + 未来可替换为实际删除逻辑。 + """ + try: + # TODO: 接入真实删除逻辑(如按 chat_id 过滤) + print("mem0_delete") + print("user_id:", user_id) + print("chat_id:", chat_id) + return True + except Exception as e: + log.debug(f"Mem0 delete failed: {e}") + return False diff --git a/backend/open_webui/routers/chats.py b/backend/open_webui/routers/chats.py index dd6900fd90..185d70c09d 100644 --- a/backend/open_webui/routers/chats.py +++ b/backend/open_webui/routers/chats.py @@ -19,6 +19,7 @@ from open_webui.constants import ERROR_MESSAGES from open_webui.env import SRC_LOG_LEVELS from fastapi import APIRouter, Depends, HTTPException, Request, status from pydantic import BaseModel +from open_webui.memory.mem0 import mem0_delete from open_webui.utils.auth import get_admin_user, get_verified_user @@ -654,6 +655,9 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_verified # === 管理员分支 === if user.role == "admin": chat = Chats.get_chat_by_id(id) + + # 清理该聊天的 Mem0 记忆条目 + await mem0_delete(chat.user_id, id) # 清理孤立标签(仅被该聊天使用的标签) for tag in chat.meta.get("tags", []): @@ -676,6 +680,9 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_verified chat = Chats.get_chat_by_id(id) + # 清理该聊天的 Mem0 记忆条目 + await mem0_delete(user.id, id) + # 清理孤立标签 for tag in chat.meta.get("tags", []): if Chats.count_chats_by_tag_name_and_user_id(tag, user.id) == 1: diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index d16006e618..44e5313bbf 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -54,7 +54,8 @@ from open_webui.routers.pipelines import ( process_pipeline_inlet_filter, process_pipeline_outlet_filter, ) -from open_webui.routers.memories import query_memory, QueryMemoryForm +from open_webui.models.memories import Memories +from open_webui.memory.mem0 import mem0_search from open_webui.utils.webhook import post_webhook from open_webui.utils.files import ( @@ -517,37 +518,44 @@ async def chat_completion_tools_handler( async def chat_memory_handler( - request: Request, form_data: dict, extra_params: dict, user + request: Request, form_data: dict, extra_params: dict, user, metadata ): - try: - results = await query_memory( - request, - QueryMemoryForm( - **{ - "content": get_last_user_message(form_data["messages"]) or "", - "k": 3, - } - ), - user, - ) - except Exception as e: - log.debug(e) - results = None + """ + 聊天记忆处理器 - 注入用户手动保存的记忆 + Mem0 检索结果到当前对话上下文 + + 新增行为: + 1. memory 特性开启时,直接注入用户所有记忆条目(不再做 RAG Top-K) + 2. 预留 Mem0 检索:使用最后一条用户消息查询 Mem0,返回的条目也一并注入 + 3. 上下文注入方式保持不变:统一写入 System Message + """ + user_message = get_last_user_message(form_data.get("messages", [])) or "" + + # === 1. 获取用户全部记忆(不再截断 Top-K) === + memories = Memories.get_memories_by_user_id(user.id) or [] + + # === 2. 预留的 Mem0 检索结果 === + mem0_results = await mem0_search(user.id, metadata.get("chat_id"), user_message) + + # === 3. 格式化记忆条目 === + entries = [] + + # 3.1 用户记忆库全量 + for mem in memories: + created_at_date = time.strftime("%Y-%m-%d", time.localtime(mem.created_at)) if mem.created_at else "Unknown Date" + entries.append(f"[{created_at_date}] {mem.content}") + + # 3.2 Mem0 检索结果 + for item in mem0_results: + entries.append(f"[Mem0] {item}") + + if not entries: + return form_data user_context = "" - if results and hasattr(results, "documents"): - if results.documents and len(results.documents) > 0: - for doc_idx, doc in enumerate(results.documents[0]): - created_at_date = "Unknown Date" - - if results.metadatas[0][doc_idx].get("created_at"): - created_at_timestamp = results.metadatas[0][doc_idx]["created_at"] - created_at_date = time.strftime( - "%Y-%m-%d", time.localtime(created_at_timestamp) - ) - - user_context += f"{doc_idx + 1}. [{created_at_date}] {doc}\n" + for idx, entry in enumerate(entries): + user_context += f"{idx + 1}. {entry}\n" + # === 4. 将记忆注入到系统消息中 === form_data["messages"] = add_or_update_system_message( f"User Context:\n{user_context}\n", form_data["messages"], append=True ) @@ -1189,7 +1197,7 @@ async def process_chat_payload(request, form_data, user, metadata, model): # 12.1 记忆功能 - 注入历史对话记忆 if "memory" in features and features["memory"]: form_data = await chat_memory_handler( - request, form_data, extra_params, user + request, form_data, extra_params, user, metadata ) # 12.2 网页搜索功能 - 执行网络搜索