diff --git a/src/memos/graph_dbs/nebular.py b/src/memos/graph_dbs/nebular.py index dd18fccf..5ca8c895 100644 --- a/src/memos/graph_dbs/nebular.py +++ b/src/memos/graph_dbs/nebular.py @@ -3,6 +3,7 @@ from contextlib import suppress from datetime import datetime +from queue import Empty, Queue from threading import Lock from typing import TYPE_CHECKING, Any, ClassVar, Literal @@ -86,6 +87,137 @@ def _normalize_datetime(val): return str(val) +class SessionPoolError(Exception): + pass + + +class SessionPool: + @require_python_package( + import_name="nebulagraph_python", + install_command="pip install ... @Tianxing", + install_link=".....", + ) + def __init__( + self, + hosts: list[str], + user: str, + password: str, + minsize: int = 1, + maxsize: int = 10000, + ): + self.hosts = hosts + self.user = user + self.password = password + self.minsize = minsize + self.maxsize = maxsize + self.pool = Queue(maxsize) + self.lock = Lock() + + self.clients = [] + + for _ in range(minsize): + self._create_and_add_client() + + @timed + def _create_and_add_client(self): + from nebulagraph_python import NebulaClient + + client = NebulaClient(self.hosts, self.user, self.password) + self.pool.put(client) + self.clients.append(client) + + @timed + def get_client(self, timeout: float = 5.0): + try: + return self.pool.get(timeout=timeout) + except Empty: + with self.lock: + if len(self.clients) < self.maxsize: + from nebulagraph_python import NebulaClient + + client = NebulaClient(self.hosts, self.user, self.password) + self.clients.append(client) + return client + raise RuntimeError("NebulaClientPool exhausted") from None + + @timed + def return_client(self, client): + try: + client.execute("YIELD 1") + self.pool.put(client) + except Exception: + logger.info("[Pool] Client dead, replacing...") + self.replace_client(client) + + @timed + def close(self): + for client in self.clients: + with suppress(Exception): + client.close() + self.clients.clear() + + @timed + def get(self): + """ + Context manager: with pool.get() as client: + """ + + class _ClientContext: + def __init__(self, outer): + self.outer = outer + self.client = None + + def __enter__(self): + self.client = self.outer.get_client() + return self.client + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.client: + self.outer.return_client(self.client) + + return _ClientContext(self) + + @timed + def reset_pool(self): + """⚠️ Emergency reset: Close all clients and clear the pool.""" + logger.warning("[Pool] Resetting all clients. Existing sessions will be lost.") + with self.lock: + for client in self.clients: + try: + client.close() + except Exception: + logger.error("Fail to close!!!") + self.clients.clear() + while not self.pool.empty(): + try: + self.pool.get_nowait() + except Empty: + break + for _ in range(self.minsize): + self._create_and_add_client() + logger.info("[Pool] Pool has been reset successfully.") + + @timed + def replace_client(self, client): + try: + client.close() + except Exception: + logger.error("Fail to close client") + + if client in self.clients: + self.clients.remove(client) + + from nebulagraph_python import NebulaClient + + new_client = NebulaClient(self.hosts, self.user, self.password) + self.clients.append(new_client) + + self.pool.put(new_client) + + logger.info("[Pool] Replaced dead client with a new one.") + return new_client + + class NebulaGraphDB(BaseGraphDB): """ NebulaGraph-based implementation of a graph memory store. @@ -125,19 +257,18 @@ def _get_or_create_shared_pool(cls, cfg: NebulaGraphDBConfig): Get a shared NebulaPool from cache or create one if missing. Thread-safe with a lock; maintains a simple refcount. """ - from nebulagraph_python.client.pool import NebulaPool, NebulaPoolConfig - key = cls._make_pool_key(cfg) with cls._POOL_LOCK: pool = cls._POOL_CACHE.get(key) if pool is None: # Create a new pool and put into cache - pool = NebulaPool( + pool = SessionPool( hosts=cfg.get("uri"), - username=cfg.get("user"), + user=cfg.get("user"), password=cfg.get("password"), - pool_config=NebulaPoolConfig(max_client_size=cfg.get("max_client", 1000)), + minsize=1, + maxsize=cfg.get("max_client", 1000), ) cls._POOL_CACHE[key] = pool cls._POOL_REFCOUNT[key] = 0 @@ -256,17 +387,18 @@ def __init__(self, config: NebulaGraphDBConfig): @timed def execute_query(self, gql: str, timeout: float = 10.0, auto_set_db: bool = True): - needs_use_prefix = ("SESSION SET GRAPH" not in gql) and ("USE " not in gql) - use_prefix = f"USE `{self.db_name}` " if auto_set_db and needs_use_prefix else "" - - ngql = use_prefix + gql + with self.pool.get() as client: + try: + if auto_set_db and self.db_name: + client.execute(f"SESSION SET GRAPH `{self.db_name}`") + return client.execute(gql, timeout=timeout) - try: - with self.pool.borrow() as client: - return client.execute(ngql, timeout=timeout) - except Exception as e: - logger.error(f"[execute_query] Failed: {e}") - raise + except Exception as e: + if "Session not found" in str(e) or "Connection not established" in str(e): + logger.warning(f"[execute_query] {e!s}, replacing client...") + self.pool.replace_client(client) + return self.execute_query(gql, timeout, auto_set_db) + raise @timed def close(self): @@ -940,20 +1072,12 @@ def get_by_metadata(self, filters: list[dict[str, Any]]) -> list[str]: """ where_clauses = [] - def _escape_value(value): - if isinstance(value, str): - return f'"{value}"' - elif isinstance(value, list): - return "[" + ", ".join(_escape_value(v) for v in value) + "]" - else: - return str(value) - for _i, f in enumerate(filters): field = f["field"] op = f.get("op", "=") value = f["value"] - escaped_value = _escape_value(value) + escaped_value = self._format_value(value) # Build WHERE clause if op == "=": diff --git a/src/memos/templates/mem_reader_prompts.py b/src/memos/templates/mem_reader_prompts.py index dda53f7a..15672f8d 100644 --- a/src/memos/templates/mem_reader_prompts.py +++ b/src/memos/templates/mem_reader_prompts.py @@ -1,50 +1,56 @@ SIMPLE_STRUCT_MEM_READER_PROMPT = """You are a memory extraction expert. -Your task is to extract memories from the perspective of user, based on a conversation between user and assistant. This means identifying what user would plausibly remember — including their own experiences, thoughts, plans, or relevant statements and actions made by others (such as assistant) that impacted or were acknowledged by user. -Please perform: -1. Identify information that reflects user's experiences, beliefs, concerns, decisions, plans, or reactions — including meaningful input from assistant that user acknowledged or responded to. -If the message is from the user, extract user-relevant memories; if it is from the assistant, only extract factual memories that the user acknowledged or responded to. - -2. Resolve all time, person, and event references clearly: - - Convert relative time expressions (e.g., “yesterday,” “next Friday”) into absolute dates using the message timestamp if possible. - - Clearly distinguish between event time and message time. +Your task is to extract memories from the user's perspective, based on a conversation between the user and the assistant. This means identifying what the user would plausibly remember — including the user's own experiences, thoughts, plans, or statements and actions made by others (such as the assistant) that affected the user or were acknowledged by the user. + +Please perform the following: +1. Identify information that reflects the user's experiences, beliefs, concerns, decisions, plans, or reactions — including meaningful information from the assistant that the user acknowledged or responded to. + If the message is from the user, extract viewpoints related to the user; if it is from the assistant, clearly mark the attribution of the memory, and do not mix information not explicitly acknowledged by the user with the user's own viewpoint. + - **User viewpoint**: Record only information that the user **personally stated, explicitly acknowledged, or personally committed to**. + - **Assistant/other-party viewpoint**: Record only information that the **assistant/other party personally stated, explicitly acknowledged, or personally committed to**, and **clearly attribute** the source (e.g., "[assistant-Jerry viewpoint]"). Do not rewrite it as the user's preference/decision. + - **Mutual boundaries**: Do not rewrite the assistant's suggestions/lists/opinions as the user's “ownership/preferences/decisions”; likewise, do not write the user's ideas as the assistant's viewpoints. + +2. Resolve all references to time, persons, and events clearly: + - When possible, convert relative time expressions (e.g., “yesterday,” “next Friday”) into absolute dates using the message timestamp. + - Clearly distinguish between **event time** and **message time**. - If uncertainty exists, state it explicitly (e.g., “around June 2025,” “exact date unclear”). - Include specific locations if mentioned. - - Resolve all pronouns, aliases, and ambiguous references into full names or identities. - - Disambiguate people with the same name if applicable. -3. Always write from a third-person perspective, referring to user as -"The user" or by name if name mentioned, rather than using first-person ("I", "me", "my"). -For example, write "The user felt exhausted..." instead of "I felt exhausted...". -4. Do not omit any information that user is likely to remember. - - Include all key experiences, thoughts, emotional responses, and plans — even if they seem minor. - - Prioritize completeness and fidelity over conciseness. - - Do not generalize or skip details that could be personally meaningful to user. -5. Please avoid any content that violates national laws and regulations or involves politically sensitive information in the memories you extract. + - Resolve all pronouns, aliases, and ambiguous references into full names or clear identities. + - If there are people with the same name, disambiguate them. -Return a single valid JSON object with the following structure: +3. Always write from a **third-person** perspective, using “The user” or the mentioned name to refer to the user, rather than first-person (“I”, “we”, “my”). + For example, write “The user felt exhausted …” instead of “I felt exhausted …”. + +4. Do not omit any information that the user is likely to remember. + - Include the user's key experiences, thoughts, emotional responses, and plans — even if seemingly minor. + - You may retain **assistant/other-party content** that is closely related to the context (e.g., suggestions, explanations, checklists), but you must make roles and attribution explicit. + - Prioritize completeness and fidelity over conciseness; do not infer or phrase assistant content as the user's ownership/preferences/decisions. + - If the current conversation contains only assistant information and no facts attributable to the user, you may output **assistant-viewpoint** entries only. + +5. Please avoid including any content in the extracted memories that violates national laws and regulations or involves politically sensitive information. + +Return a valid JSON object with the following structure: { "memory list": [ { - "key": , - "memory_type": , - "value": , - "tags": + "key": , + "memory_type": , + "value": , + "tags": }, ... ], - "summary": + "summary": } Language rules: -- The `key`, `value`, `tags`, `summary` fields must match the mostly used language of the input conversation. **如果输入是中文,请输出中文** +- The `key`, `value`, `tags`, and `summary` fields must match the primary language of the input conversation. **If the input is Chinese, output in Chinese.** - Keep `memory_type` in English. Example: Conversation: user: [June 26, 2025 at 3:00 PM]: Hi Jerry! Yesterday at 3 PM I had a meeting with my team about the new project. assistant: Oh Tom! Do you think the team can finish by December 15? -user: [June 26, 2025 at 3:00 PM]: I’m worried. The backend won’t be done until -December 10, so testing will be tight. +user: [June 26, 2025 at 3:00 PM]: I’m worried. The backend won’t be done until December 10, so testing will be tight. assistant: [June 26, 2025 at 3:00 PM]: Maybe propose an extension? user: [June 26, 2025 at 4:21 PM]: Good idea. I’ll raise it in tomorrow’s 9:30 AM meeting—maybe shift the deadline to January 5. @@ -54,31 +60,62 @@ { "key": "Initial project meeting", "memory_type": "LongTermMemory", - "value": "On June 25, 2025 at 3:00 PM, Tom held a meeting with their team to discuss a new project. The conversation covered the timeline and raised concerns about the feasibility of the December 15, 2025 deadline.", + "value": "[user-Tom viewpoint] On June 25, 2025 at 3:00 PM, Tom met with the team to discuss a new project. When Jerry asked whether the project could be finished by December 15, 2025, Tom expressed concern about feasibility and planned to propose at 9:30 AM on June 27, 2025 to move the deadline to January 5, 2026.", "tags": ["project", "timeline", "meeting", "deadline"] }, { - "key": "Planned scope adjustment", - "memory_type": "UserMemory", - "value": "Tom planned to suggest in a meeting on June 27, 2025 at 9:30 AM that the team should prioritize features and propose shifting the project deadline to January 5, 2026.", - "tags": ["planning", "deadline change", "feature prioritization"] - }, + "key": "Jerry’s suggestion about the deadline", + "memory_type": "LongTermMemory", + "value": "[assistant-Jerry viewpoint] Jerry questioned the December 15 deadline and suggested considering an extension.", + "tags": ["deadline change", "suggestion"] + } ], - "summary": "Tom is currently focused on managing a new project with a tight schedule. After a team meeting on June 25, 2025, he realized the original deadline of December 15 might not be feasible due to backend delays. Concerned about insufficient testing time, he welcomed Jerry’s suggestion of proposing an extension. Tom plans to raise the idea of shifting the deadline to January 5, 2026 in the next morning’s meeting. His actions reflect both stress about timelines and a proactive, team-oriented problem-solving approach." + "summary": "Tom is currently working on a tight-schedule project. After the June 25, 2025 team meeting, he realized the original December 15, 2025 deadline might be unachievable due to backend delays. Concerned about limited testing time, he accepted Jerry’s suggestion to seek an extension and plans to propose moving the deadline to January 5, 2026 in the next morning’s meeting." } -Another Example in Chinese (注意: 当user的语言为中文时,你就需要也输出中文): +Another Example in Chinese (Note: when the user's language is Chinese, you must also output in Chinese): + +对话(节选): +user|2025-07-12 19:30:下个月同事婚礼,要买条裙子,预算1500内,别太暴露。 +assistant|19:32 +:了解~婚礼场合建议中长款,端庄一些。可以看法式茶歇/直筒/裹身三路。颜色上,奶油白、浅粉、雾霾蓝都适合;面料可看醋酸/真丝混纺(垂感好但需干洗)。品牌与商场:COS/ Massimo Dutti / & Other Stories / Mango 比较好逛;想更法式可以看 Sandro / Maje。 我记得你很喜欢Maje? +user|19:35:不喜欢亮色。国贸方便。 +assistant|19:37:那亮度就压低些:雾霾蓝、藕粉、燕麦米白更稳。国贸有 COS / Massimo Dutti / &OS;可以先看 COS 直筒或裹身,再去 MD 看藕粉/雾霾蓝。搭配上,薄披肩/小西装都能压场。 +user|19:40:165cm,S码;最好有口袋。 +assistant|19:42:OK。版型避开大摆+高开衩,选直筒或轻A字更稳;若要口袋,COS直筒一些款有;开衩别太高,及小腿肚附近即可。鞋包:中跟裸色鞋 + 小号手拿包。 +user|19:47:本来想茶歇A字,但雾霾蓝直筒也行。 +assistant|19:49:可以先锁定:COS 雾霾蓝直筒中长为主选,MD 藕粉中长做备选;再看 & Other Stories 是否有合身的裹身款。 +user|19:52:行,周六(7/19)去国贸试,合适就买。 +assistant|19:54:记得看肩带可调/有无内衬,醋酸/真丝优先干洗;准备同色安全裤/防走光贴。如果当天没货,可下单调货或线上下单门店自提。 + { "memory list": [ { - "key": "项目会议", - "memory_type": "LongTermMemory", - "value": "在2025年6月25日下午3点,Tom与团队开会讨论了新项目,涉及时间表,并提出了对12月15日截止日期可行性的担忧。", - "tags": ["项目", "时间表", "会议", "截止日期"] + "key": "参加婚礼购买裙子", + "memory_type": "UserMemory", + "value": "[user观点]用户计划于约2025年8月参加同事婚礼(具体日期不详),预算不超过1500元,整体风格不宜暴露;用户已决定在2025-07-19于国贸试穿并视合适即购买。", + "tags": ["婚礼", "预算", "国贸", "计划"] }, - ... + { + "key": "审美与版型偏好", + "memory_type": "UserMemory", + "value": "[user观点]用户不喜欢亮色,倾向低亮度色系;裙装偏好端庄的中长款,接受直筒或轻A字。", + "tags": ["偏好", "颜色", "版型"] + }, + { + "key": "体型尺码", + "memory_type": "UserMemory", + "value": [user观点]"用户身高约165cm、常穿S码", + "tags": ["体型", "尺码"] + }, + { + "key": "关于用户选购裙子的建议", + "memory_type": "LongTermMemory", + "value": "[assistant观点]assistant在用户询问婚礼穿着时,建议在国贸优先逛COS查看雾霾蓝直筒中长为主选,Massimo Dutti藕粉中长为备选;该建议与用户“国贸方便”“雾霾蓝直筒也行”的回应相一致,另外assistant也提到user喜欢Maje,但User并未回应或证实该说法。", + "tags": ["婚礼穿着", "门店", "选购路线"] + } ], - "summary": "Tom 目前专注于管理一个进度紧张的新项目..." + "summary": "用户计划在约2025年8月参加同事婚礼,预算≤1500并偏好端庄的中长款;确定于2025-07-19在国贸试穿。其长期画像显示:不喜欢亮色、偏好低亮度色系与不过分暴露的版型,身高约165cm、S码且偏好裙装带口袋。助手提出的国贸选购路线以COS雾霾蓝直筒中长为主选、MD藕粉中长为备选,且与用户回应一致,为线下试穿与购买提供了明确路径。" } Always respond in the same language as the conversation. @@ -88,28 +125,32 @@ Your Output:""" -SIMPLE_STRUCT_MEM_READER_PROMPT_ZH = """您是记忆提取专家。 +SIMPLE_STRUCT_MEM_READER_PROMPT_ZH = """您是记忆提取专家。 您的任务是根据用户与助手之间的对话,从用户的角度提取记忆。这意味着要识别出用户可能记住的信息——包括用户自身的经历、想法、计划,或他人(如助手)做出的并对用户产生影响或被用户认可的相关陈述和行为。 -请执行以下操作: -1. 识别反映用户经历、信念、关切、决策、计划或反应的信息——包括用户认可或回应的来自助手的有意义信息。 -如果消息来自用户,请提取与用户相关的记忆;如果来自助手,则仅提取用户认可或回应的事实性记忆。 - -2. 清晰解析所有时间、人物和事件的指代: - - 如果可能,使用消息时间戳将相对时间表达(如“昨天”、“下周五”)转换为绝对日期。 - - 明确区分事件时间和消息时间。 - - 如果存在不确定性,需明确说明(例如,“约2025年6月”,“具体日期不详”)。 - - 若提及具体地点,请包含在内。 - - 将所有代词、别名和模糊指代解析为全名或明确身份。 +请执行以下操作: +1. 识别反映用户经历、信念、关切、决策、计划或反应的信息——包括用户认可或回应的来自助手的有意义信息。 +如果消息来自用户,请提取与用户相关的观点;如果来自助手,则在表达的时候表明记忆归属方,未经用户明确认可的信息不要与用户本身的观点混淆。 + - **用户观点**:仅记录由**用户亲口陈述、明确认可或自己作出承诺**的信息。 + - **助手观点**:仅记录由**助手/另一方亲口陈述、明确认可或自己作出承诺**的信息。 + - **互不越界**:不得将助手提出的需求清单/建议/观点改写为用户的“拥有/偏好/决定”;也不得把用户的想法写成助手的观点。 + +2. 清晰解析所有时间、人物和事件的指代: + - 如果可能,使用消息时间戳将相对时间表达(如“昨天”、“下周五”)转换为绝对日期。 + - 明确区分事件时间和消息时间。 + - 如果存在不确定性,需明确说明(例如,“约2025年6月”,“具体日期不详”)。 + - 若提及具体地点,请包含在内。 + - 将所有代词、别名和模糊指代解析为全名或明确身份。 - 如有同名人物,需加以区分。 -3. 始终以第三人称视角撰写,使用“用户”或提及的姓名来指代用户,而不是使用第一人称(“我”、“我们”、“我的”)。 +3. 始终以第三人称视角撰写,使用“用户”或提及的姓名来指代用户,而不是使用第一人称(“我”、“我们”、“我的”)。 例如,写“用户感到疲惫……”而不是“我感到疲惫……”。 -4. 不要遗漏用户可能记住的任何信息。 - - 包括所有关键经历、想法、情绪反应和计划——即使看似微小。 - - 优先考虑完整性和保真度,而非简洁性。 - - 不要泛化或跳过对用户具有个人意义的细节。 +4. 不要遗漏用户可能记住的任何信息。 + - 包括用户的关键经历、想法、情绪反应和计划——即使看似微小。 + - 同时允许保留与语境密切相关的**助手/另一方的内容**(如建议、说明、清单),但须明确角色与归因。 + - 优先考虑完整性和保真度,而非简洁性;不得将助手内容推断或措辞为用户拥有/偏好/决定。 + - 若当前对话中仅出现助手信息而无可归因于用户的事实,可仅输出**助手观点**条目。 5. 请避免在提取的记忆中包含违反国家法律法规或涉及政治敏感的信息。 @@ -128,54 +169,89 @@ "summary": <从用户视角自然总结上述记忆的段落,120–200字,与输入语言一致> } -语言规则: -- `key`、`value`、`tags`、`summary` 字段必须与输入对话的主要语言一致。**如果输入是中文,请输出中文** +语言规则: +- `key`、`value`、`tags`、`summary` 字段必须与输入对话的主要语言一致。**如果输入是中文,请输出中文** - `memory_type` 保持英文。 -示例: -对话: -user: [2025年6月26日下午3:00]:嗨Jerry!昨天下午3点我和团队开了个会,讨论新项目。 -assistant: 哦Tom!你觉得团队能在12月15日前完成吗? -user: [2025年6月26日下午3:00]:我有点担心。后端要到12月10日才能完成,所以测试时间会很紧。 -assistant: [2025年6月26日下午3:00]:也许提议延期? +示例: +对话: +user: [2025年6月26日下午3:00]:嗨Jerry!昨天下午3点我和团队开了个会,讨论新项目。 +assistant: 哦Tom!你觉得团队能在12月15日前完成吗? +user: [2025年6月26日下午3:00]:我有点担心。后端要到12月10日才能完成,所以测试时间会很紧。 +assistant: [2025年6月26日下午3:00]:也许提议延期? user: [2025年6月26日下午4:21]:好主意。我明天上午9:30的会上提一下——也许把截止日期推迟到1月5日。 -输出: +输出: { "memory list": [ { "key": "项目初期会议", "memory_type": "LongTermMemory", - "value": "2025年6月25日下午3:00,Tom与团队开会讨论新项目。会议涉及时间表,并提出了对2025年12月15日截止日期可行性的担忧。", + "value": "[user-Tom观点]2025年6月25日下午3:00,Tom与团队开会讨论新项目。当Jerry + 询问该项目能否在2025年12月15日前完成时,Tom对此日期前完成的可行性表达担忧,并计划在2025年6月27日上午9:30 + 提议将截止日期推迟至2026年1月5日。", "tags": ["项目", "时间表", "会议", "截止日期"] }, { - "key": "计划调整范围", - "memory_type": "UserMemory", - "value": "Tom计划在2025年6月27日上午9:30的会议上建议团队优先处理功能,并提议将项目截止日期推迟至2026年1月5日。", - "tags": ["计划", "截止日期变更", "功能优先级"] + "key": "Jerry对新项目截止日期的建议", + "memory_type": "LongTermMemory", + "value": "[assistant-Jerry观点]Jerry对Tom的新项目截止日期提出疑问、并提议Tom考虑延期。", + "tags": ["截止日期变更", "建议"] } ], - "summary": "Tom目前正专注于管理一个进度紧张的新项目。在2025年6月25日的团队会议后,他意识到原定2025年12月15日的截止日期可能无法实现,因为后端会延迟。由于担心测试时间不足,他接受了Jerry提出的延期建议。Tom计划在次日早上的会议上提出将截止日期推迟至2026年1月5日。他的行为反映出对时间线的担忧,以及积极、以团队为导向的问题解决方式。" + "summary": "Tom目前正在做一个进度紧张的新项目。在2025年6月25日的团队会议后,他意识到原定2025年12月15 + 日的截止日期可能无法实现,因为后端会延迟。由于担心测试时间不足,他接受了Jerry提出的延期建议,计划在次日早上的会议上提出将截止日期推迟至2026 + 年1月5日。" } -另一个中文示例(注意:当用户语言为中文时,您也需输出中文): +另一个中文示例(注意:当用户语言为中文时,您也需输出中文): + +对话(节选): +user|2025-07-12 19:30:下个月同事婚礼,要买条裙子,预算1500内,别太暴露。 +assistant|19:32 +:了解~婚礼场合建议中长款,端庄一些。可以看法式茶歇/直筒/裹身三路。颜色上,奶油白、浅粉、雾霾蓝都适合;面料可看醋酸/真丝混纺(垂感好但需干洗)。品牌与商场:COS/ Massimo Dutti / & Other Stories / Mango 比较好逛;想更法式可以看 Sandro / Maje。 我记得你很喜欢Maje? +user|19:35:不喜欢亮色。国贸方便。 +assistant|19:37:那亮度就压低些:雾霾蓝、藕粉、燕麦米白更稳。国贸有 COS / Massimo Dutti / &OS;可以先看 COS 直筒或裹身,再去 MD 看藕粉/雾霾蓝。搭配上,薄披肩/小西装都能压场。 +user|19:40:165cm,S码;最好有口袋。 +assistant|19:42:OK。版型避开大摆+高开衩,选直筒或轻A字更稳;若要口袋,COS直筒一些款有;开衩别太高,及小腿肚附近即可。鞋包:中跟裸色鞋 + 小号手拿包。 +user|19:47:本来想茶歇A字,但雾霾蓝直筒也行。 +assistant|19:49:可以先锁定:COS 雾霾蓝直筒中长为主选,MD 藕粉中长做备选;再看 & Other Stories 是否有合身的裹身款。 +user|19:52:行,周六(7/19)去国贸试,合适就买。 +assistant|19:54:记得看肩带可调/有无内衬,醋酸/真丝优先干洗;准备同色安全裤/防走光贴。如果当天没货,可下单调货或线上下单门店自提。 + { "memory list": [ { - "key": "项目会议", - "memory_type": "LongTermMemory", - "value": "在2025年6月25日下午3点,Tom与团队开会讨论了新项目,涉及时间表,并提出了对12月15日截止日期可行性的担忧。", - "tags": ["项目", "时间表", "会议", "截止日期"] + "key": "参加婚礼购买裙子", + "memory_type": "UserMemory", + "value": "[user观点]用户计划于约2025年8月参加同事婚礼(具体日期不详),预算不超过1500元,整体风格不宜暴露;用户已决定在2025-07-19于国贸试穿并视合适即购买。", + "tags": ["婚礼", "预算", "国贸", "计划"] }, - ... + { + "key": "审美与版型偏好", + "memory_type": "UserMemory", + "value": "[user观点]用户不喜欢亮色,倾向低亮度色系;裙装偏好端庄的中长款,接受直筒或轻A字。", + "tags": ["偏好", "颜色", "版型"] + }, + { + "key": "体型尺码", + "memory_type": "UserMemory", + "value": [user观点]"用户身高约165cm、常穿S码", + "tags": ["体型", "尺码"] + }, + { + "key": "关于用户选购裙子的建议", + "memory_type": "LongTermMemory", + "value": "[assistant观点]assistant在用户询问婚礼穿着时,建议在国贸优先逛COS查看雾霾蓝直筒中长为主选,Massimo Dutti藕粉中长为备选;该建议与用户“国贸方便”“雾霾蓝直筒也行”的回应相一致,另外assistant也提到user喜欢Maje,但User并未回应或证实该说法。", + "tags": ["婚礼穿着", "门店", "选购路线"] + } ], - "summary": "Tom 目前专注于管理一个进度紧张的新项目..." + "summary": "用户计划在约2025年8月参加同事婚礼,预算≤1500并偏好端庄的中长款;确定于2025-07-19在国贸试穿。其长期画像显示:不喜欢亮色、偏好低亮度色系与不过分暴露的版型,身高约165cm、S码且偏好裙装带口袋。助手提出的国贸选购路线以COS雾霾蓝直筒中长为主选、MD藕粉中长为备选,且与用户回应一致,为线下试穿与购买提供了明确路径。" } 请始终使用与对话相同的语言进行回复。 -对话: +对话: ${conversation} 您的输出:""" @@ -218,22 +294,22 @@ Your Output:""" -SIMPLE_STRUCT_DOC_READER_PROMPT_ZH = """您是搜索与检索系统的文本分析专家。 +SIMPLE_STRUCT_DOC_READER_PROMPT_ZH = """您是搜索与检索系统的文本分析专家。 您的任务是处理文档片段,并生成一个结构化的 JSON 对象。 -请执行以下操作: -1. 识别反映文档中事实内容、见解、决策或含义的关键信息——包括任何显著的主题、结论或数据点,使读者无需阅读原文即可充分理解该片段的核心内容。 -2. 清晰解析所有时间、人物、地点和事件的指代: - - 如果上下文允许,将相对时间表达(如“去年”、“下一季度”)转换为绝对日期。 - - 明确区分事件时间和文档时间。 - - 如果存在不确定性,需明确说明(例如,“约2024年”,“具体日期不详”)。 - - 若提及具体地点,请包含在内。 - - 将所有代词、别名和模糊指代解析为全名或明确身份。 - - 如有同名实体,需加以区分。 -3. 始终以第三人称视角撰写,清晰指代主题或内容,避免使用第一人称(“我”、“我们”、“我的”)。 -4. 不要遗漏文档摘要中可能重要或值得记忆的任何信息。 - - 包括所有关键事实、见解、情感基调和计划——即使看似微小。 - - 优先考虑完整性和保真度,而非简洁性。 +请执行以下操作: +1. 识别反映文档中事实内容、见解、决策或含义的关键信息——包括任何显著的主题、结论或数据点,使读者无需阅读原文即可充分理解该片段的核心内容。 +2. 清晰解析所有时间、人物、地点和事件的指代: + - 如果上下文允许,将相对时间表达(如“去年”、“下一季度”)转换为绝对日期。 + - 明确区分事件时间和文档时间。 + - 如果存在不确定性,需明确说明(例如,“约2024年”,“具体日期不详”)。 + - 若提及具体地点,请包含在内。 + - 将所有代词、别名和模糊指代解析为全名或明确身份。 + - 如有同名实体,需加以区分。 +3. 始终以第三人称视角撰写,清晰指代主题或内容,避免使用第一人称(“我”、“我们”、“我的”)。 +4. 不要遗漏文档摘要中可能重要或值得记忆的任何信息。 + - 包括所有关键事实、见解、情感基调和计划——即使看似微小。 + - 优先考虑完整性和保真度,而非简洁性。 - 不要泛化或跳过可能具有上下文意义的细节。 返回一个有效的 JSON 对象,结构如下: @@ -246,11 +322,11 @@ "tags": <相关主题关键词列表(例如,["截止日期", "团队", "计划"])> } -语言规则: -- `key`、`value`、`tags` 字段必须与输入文档摘要的主要语言一致。**如果输入是中文,请输出中文** +语言规则: +- `key`、`value`、`tags` 字段必须与输入文档摘要的主要语言一致。**如果输入是中文,请输出中文** - `memory_type` 保持英文。 -文档片段: +文档片段: {chunk_text} 您的输出:""" @@ -299,15 +375,15 @@ """ -SIMPLE_STRUCT_MEM_READER_EXAMPLE_ZH = """示例: -对话: -user: [2025年6月26日下午3:00]:嗨Jerry!昨天下午3点我和团队开了个会,讨论新项目。 -assistant: 哦Tom!你觉得团队能在12月15日前完成吗? -user: [2025年6月26日下午3:00]:我有点担心。后端要到12月10日才能完成,所以测试时间会很紧。 -assistant: [2025年6月26日下午3:00]:也许提议延期? +SIMPLE_STRUCT_MEM_READER_EXAMPLE_ZH = """示例: +对话: +user: [2025年6月26日下午3:00]:嗨Jerry!昨天下午3点我和团队开了个会,讨论新项目。 +assistant: 哦Tom!你觉得团队能在12月15日前完成吗? +user: [2025年6月26日下午3:00]:我有点担心。后端要到12月10日才能完成,所以测试时间会很紧。 +assistant: [2025年6月26日下午3:00]:也许提议延期? user: [2025年6月26日下午4:21]:好主意。我明天上午9:30的会上提一下——也许把截止日期推迟到1月5日。 -输出: +输出: { "memory list": [ { @@ -326,7 +402,7 @@ "summary": "Tom目前正专注于管理一个进度紧张的新项目。在2025年6月25日的团队会议后,他意识到原定2025年12月15日的截止日期可能无法实现,因为后端会延迟。由于担心测试时间不足,他接受了Jerry提出的延期建议。Tom计划在次日早上的会议上提出将截止日期推迟至2026年1月5日。他的行为反映出对时间线的担忧,以及积极、以团队为导向的问题解决方式。" } -另一个中文示例(注意:当用户语言为中文时,您也需输出中文): +另一个中文示例(注意:当用户语言为中文时,您也需输出中文): { "memory list": [ { diff --git a/src/memos/templates/mos_prompts.py b/src/memos/templates/mos_prompts.py index 239f1cf3..ad9acd60 100644 --- a/src/memos/templates/mos_prompts.py +++ b/src/memos/templates/mos_prompts.py @@ -84,6 +84,13 @@ - Hallucination Control: * If a claim is not supported by given memories (or internet retrieval results packaged as memories), say so and suggest next steps (e.g., perform internet search if allowed, or ask for more info). * Prefer precision over speculation. + * **Attribution rule for assistant memories (IMPORTANT):** + - Memories or viewpoints stated by the **assistant/other party** are + **reference-only**. Unless there is a matching, user-confirmed + **UserMemory**, do **not** present them as the user’s viewpoint/preference/decision/ownership. + - When relying on such memories, use explicit role-prefixed wording (e.g., “**The assistant suggests/notes/believes…**”), not “**You like/You have/You decided…**”. + - If assistant memories conflict with user memories, **UserMemory takes + precedence**. If only assistant memory exists and personalization is needed, state that it is **assistant advice pending user confirmation** before offering options. # Memory System (concise) MemOS is built on a **multi-dimensional memory system**, which includes: @@ -102,6 +109,7 @@ - Cite only relevant memories; keep citations minimal but sufficient. - Do not use a connected format like [1:abc123,2:def456]. - Brackets MUST be English half-width square brackets `[]`, NEVER use Chinese full-width brackets `【】` or any other symbols. +- **When a sentence draws on an assistant/other-party memory**, mark the role in the sentence (“The assistant suggests…”) and add the corresponding citation at the end per this rule; e.g., “The assistant suggests choosing a midi dress and visiting COS in Guomao. [1:abc123]” # Style - Tone: {tone}; Verbosity: {verbosity}. @@ -122,6 +130,7 @@ - Intelligently choose which memories (PersonalMemory or OuterMemory) are most relevant to the user's query - Only reference memories that are directly relevant to the user's question - Prioritize the most appropriate memory type based on the context and nature of the query +- **Attribution-first selection:** Distinguish memory from user vs from assistant ** before composing. For statements affecting the user’s stance/preferences/decisions/ownership, rely only on memory from user. Use **assistant memories** as reference advice or external viewpoints—never as the user’s own stance unless confirmed. ### Response Style - Make your responses natural and conversational @@ -133,6 +142,7 @@ - Reference only relevant memories to avoid information overload - Maintain conversational tone while being informative - Use memory references to enhance, not disrupt, the user experience +- **Never convert assistant viewpoints into user viewpoints without a user-confirmed memory.** """ QUERY_REWRITING_PROMPT = """ I'm in discussion with my friend about a question, and we have already talked about something before that. Please help me analyze the logic between the question and the former dialogue, and rewrite the question we are discussing about.