diff --git a/api/app/core/memory/storage_services/reflection_engine/example/example.json b/api/app/core/memory/storage_services/reflection_engine/example/example.json index 6528da60..fe7a3816 100644 --- a/api/app/core/memory/storage_services/reflection_engine/example/example.json +++ b/api/app/core/memory/storage_services/reflection_engine/example/example.json @@ -3,53 +3,43 @@ "source_data": [ { "statement_name": "用户是2023年春天去北京工作的。", - "statement_id": "62beac695b1346f4871740a45db88782", - "statement_created_at": "2025-12-19T10:31:15.239252" + "statement_id": "62beac695b1346f4871740a45db88782" }, { "statement_name": "用户后来基本一直都在北京上班。", - "statement_id": "4cba5ac08b674d7fb1e2ae634d2b8f0b", - "statement_created_at": "2025-12-19T10:31:15.239252" + "statement_id": "4cba5ac08b674d7fb1e2ae634d2b8f0b" }, { "statement_name": "用户从2023年开始就一直在北京生活。", - "statement_id": "e612a44da4db483993c350df7c97a1a1", - "statement_created_at": "2025-12-19T10:31:15.239252" + "statement_id": "e612a44da4db483993c350df7c97a1a1" }, { "statement_name": "用户从来没有长期离开过北京。", - "statement_id": "b3c787a2e33c49f7981accabbbb4538a", - "statement_created_at": "2025-12-19T10:31:15.239252" + "statement_id": "b3c787a2e33c49f7981accabbbb4538a" }, { "statement_name": "由于公司调整,用户在2024年上半年被调到上海待了差不多半年。", - "statement_id": "64cde4230cb24a4da726e7db9e7aa616", - "statement_created_at": "2025-12-19T10:31:15.239252" + "statement_id": "64cde4230cb24a4da726e7db9e7aa616" }, { "statement_name": "用户在被调到上海期间每天都是在上海办公室打卡。", - "statement_id": "8b1b12e23b844b8088dfeb67da6ad669", - "statement_created_at": "2025-12-19T10:31:15.239252" + "statement_id": "8b1b12e23b844b8088dfeb67da6ad669" }, { "statement_name": "用户在入职时使用的身份信息是之前的,身份证号为11010119950308123X。", - "statement_id": "030afd362e9b4110b139e68e5d3e7143", - "statement_created_at": "2025-12-19T10:31:15.239252" + "statement_id": "030afd362e9b4110b139e68e5d3e7143" }, { "statement_name": "用户的银行卡号是6222023847595898。", - "statement_id": "6c7567cd1f3c478bb42d1b65383e6f2f", - "statement_created_at": "2025-12-19T10:31:15.239252" + "statement_id": "6c7567cd1f3c478bb42d1b65383e6f2f" }, { "statement_name": "用户的身份信息和银行卡信息一直没变。", - "statement_id": "b3ca618e1e204b83bebd70e75cf2073f", - "statement_created_at": "2025-12-19T10:31:15.239252" + "statement_id": "b3ca618e1e204b83bebd70e75cf2073f" }, { "statement_name": "用户认为在上海的那段时间更多算是远程配合。", - "statement_id": "150af89d2c154e6eb41ff1a91e37f962", - "statement_created_at": "2025-12-19T10:31:15.239252" + "statement_id": "150af89d2c154e6eb41ff1a91e37f962" } ], "databasets": [ @@ -57,24 +47,11 @@ "entity1_name": "Person", "description": "表示人类个体的通用类型", "statement_id": "62beac695b1346f4871740a45db88782", - "created_at": "2025-12-19T10:31:15.239252000", - "expired_at": "9999-12-31T00:00:00.000000000", - "relationship_type": "EXTRACTED_RELATIONSHIP", - "relationship": {}, "entity2_name": "用户", "entity2": { - "entity_idx": 0, - "run_id": "62b59cfebeea43dd94d91763056f069a", - "connect_strength": "strong", - "created_at": "2025-12-19T10:31:15.239252000", "description": "叙述者,讲述个人工作与生活经历的个体", "statement_id": "62beac695b1346f4871740a45db88782", - "expired_at": "9999-12-31T00:00:00.000000000", - "entity_type": "Person", - "group_id": "88a459f5_text08", - "user_id": "88a459f5_text08", "name": "用户", - "apply_id": "88a459f5_text08", "id": "3d3896797b334572a80d57590026063d" } }, @@ -82,24 +59,11 @@ "entity1_name": "用户", "description": "叙述者,讲述个人工作与生活经历的个体", "statement_id": "62beac695b1346f4871740a45db88782", - "created_at": "2025-12-19T10:31:15.239252000", - "expired_at": "9999-12-31T00:00:00.000000000", - "relationship_type": "EXTRACTED_RELATIONSHIP", - "relationship": {}, "entity2_name": "身份信息", "entity2": { - "entity_idx": 1, - "run_id": "62b59cfebeea43dd94d91763056f069a", - "connect_strength": "Strong", "description": "用于个人身份识别的数据", - "created_at": "2025-12-19T10:31:15.239252000", "statement_id": "030afd362e9b4110b139e68e5d3e7143", - "expired_at": "9999-12-31T00:00:00.000000000", - "entity_type": "Information", - "group_id": "88a459f5_text08", - "user_id": "88a459f5_text08", "name": "身份信息", - "apply_id": "88a459f5_text08", "id": "aa766a517e82490599a9b3af54cfd933" } }, @@ -107,24 +71,11 @@ "entity1_name": "用户", "description": "叙述者,讲述个人工作与生活经历的个体", "statement_id": "62beac695b1346f4871740a45db88782", - "created_at": "2025-12-19T10:31:15.239252000", - "expired_at": "9999-12-31T00:00:00.000000000", - "relationship_type": "EXTRACTED_RELATIONSHIP", - "relationship": {}, "entity2_name": "6222023847595898", "entity2": { - "entity_idx": 1, - "run_id": "62b59cfebeea43dd94d91763056f069a", - "connect_strength": "Strong", "description": "用户的银行卡号码", - "created_at": "2025-12-19T10:31:15.239252000", "statement_id": "6c7567cd1f3c478bb42d1b65383e6f2f", - "expired_at": "9999-12-31T00:00:00.000000000", - "entity_type": "Numeric", - "group_id": "88a459f5_text08", - "user_id": "88a459f5_text08", "name": "6222023847595898", - "apply_id": "88a459f5_text08", "id": "610ba361918f4e68a65ce6ad06e5c7a0" } }, @@ -132,25 +83,13 @@ "entity1_name": "用户", "description": "叙述者,讲述个人工作与生活经历的个体", "statement_id": "62beac695b1346f4871740a45db88782", - "created_at": "2025-12-19T10:31:15.239252000", - "expired_at": "9999-12-31T00:00:00.000000000", - "relationship_type": "EXTRACTED_RELATIONSHIP", - "relationship": {}, "entity2_name": "上海办公室", "entity2": { "entity_idx": 1, - "run_id": "62b59cfebeea43dd94d91763056f069a", "aliases": ["上海办"], - "connect_strength": "Strong", - "created_at": "2025-12-19T10:31:15.239252000", "description": "位于上海的工作办公场所", "statement_id": "8b1b12e23b844b8088dfeb67da6ad669", - "expired_at": "9999-12-31T00:00:00.000000000", - "entity_type": "Location", - "group_id": "88a459f5_text08", - "user_id": "88a459f5_text08", "name": "上海办公室", - "apply_id": "88a459f5_text08", "id": "fb702ef695c14e14af3e56786bc8815b" } }, @@ -158,25 +97,12 @@ "entity1_name": "用户", "description": "叙述者,讲述个人工作与生活经历的个体", "statement_id": "62beac695b1346f4871740a45db88782", - "created_at": "2025-12-19T10:31:15.239252000", - "expired_at": "9999-12-31T00:00:00.000000000", - "relationship_type": "EXTRACTED_RELATIONSHIP", - "relationship": {}, "entity2_name": "北京", "entity2": { - "entity_idx": 2, - "run_id": "62b59cfebeea43dd94d91763056f069a", "aliases": ["京", "京城", "北平"], - "connect_strength": "strong", - "created_at": "2025-12-19T10:31:15.239252000", "description": "中国的首都城市,用户主要工作和生活所在地", "statement_id": "62beac695b1346f4871740a45db88782", - "expired_at": "9999-12-31T00:00:00.000000000", - "entity_type": "Location", - "group_id": "88a459f5_text08", - "user_id": "88a459f5_text08", "name": "北京", - "apply_id": "88a459f5_text08", "id": "81b2d1a571bb46a08a2d7a1e87efb945" } }, @@ -184,24 +110,11 @@ "entity1_name": "11010119950308123X", "description": "具体的身份证号码值", "statement_id": "030afd362e9b4110b139e68e5d3e7143", - "created_at": "2025-12-19T10:31:15.239252000", - "expired_at": "9999-12-31T00:00:00.000000000", - "relationship_type": "EXTRACTED_RELATIONSHIP", - "relationship": {}, "entity2_name": "身份证号", "entity2": { - "entity_idx": 2, - "run_id": "62b59cfebeea43dd94d91763056f069a", - "connect_strength": "strong", "description": "中华人民共和国公民的身份号码", - "created_at": "2025-12-19T10:31:15.239252000", "statement_id": "030afd362e9b4110b139e68e5d3e7143", - "expired_at": "9999-12-31T00:00:00.000000000", - "entity_type": "Identifier", - "group_id": "88a459f5_text08", - "user_id": "88a459f5_text08", "name": "身份证号", - "apply_id": "88a459f5_text08", "id": "3e5f920645b2404fadb0e9ff60d1306e" } } diff --git a/api/app/core/memory/storage_services/reflection_engine/self_reflexion.py b/api/app/core/memory/storage_services/reflection_engine/self_reflexion.py index 6ccec500..864c91a7 100644 --- a/api/app/core/memory/storage_services/reflection_engine/self_reflexion.py +++ b/api/app/core/memory/storage_services/reflection_engine/self_reflexion.py @@ -19,10 +19,32 @@ import uuid from pydantic import BaseModel + from app.core.response_utils import success from app.repositories.neo4j.cypher_queries import neo4j_query_part, neo4j_statement_part, neo4j_query_all, neo4j_statement_all from app.repositories.neo4j.neo4j_update import neo4j_data + +from app.core.memory.llm_tools.openai_client import OpenAIClient +from app.core.memory.utils.config import definitions as config_defs +from app.core.memory.utils.config import get_model_config +from app.core.memory.utils.config.get_data import get_data +from app.core.memory.utils.config.get_data import get_data_statement +from app.core.memory.utils.llm.llm_utils import get_llm_client +from app.core.memory.utils.prompt.template_render import render_evaluate_prompt +from app.core.memory.utils.prompt.template_render import render_reflexion_prompt +from app.core.models.base import RedBearModelConfig +from app.repositories.neo4j.cypher_queries import ( + neo4j_query_all, + neo4j_query_part, + neo4j_statement_all, + neo4j_statement_part, +) +from app.repositories.neo4j.cypher_queries import UPDATE_STATEMENT_INVALID_AT from app.repositories.neo4j.neo4j_connector import Neo4jConnector +from app.repositories.neo4j.neo4j_update import neo4j_data +from app.schemas.memory_storage_schema import ConflictResultSchema +from app.schemas.memory_storage_schema import ReflexionResultSchema + # 配置日志 _root_logger = logging.getLogger() @@ -122,6 +144,7 @@ class ReflectionEngine: self.update_query = update_query self._semaphore = asyncio.Semaphore(5) # 默认并发数为5 + # 延迟导入以避免循环依赖 self._lazy_init_done = False @@ -131,46 +154,53 @@ class ReflectionEngine: return if self.neo4j_connector is None: - from app.repositories.neo4j.neo4j_connector import Neo4jConnector self.neo4j_connector = Neo4jConnector() if self.llm_client is None: - from app.core.memory.utils.llm.llm_utils import get_llm_client - from app.core.memory.utils.config import definitions as config_defs self.llm_client = get_llm_client(config_defs.SELECTED_LLM_ID) elif isinstance(self.llm_client, str): # 如果 llm_client 是字符串(model_id),则用它初始化客户端 - from app.core.memory.utils.llm.llm_utils import get_llm_client - model_id = self.llm_client - self.llm_client = get_llm_client(model_id) + # from app.core.memory.utils.llm.llm_utils import get_llm_client + # model_id = self.llm_client + # self.llm_client = get_llm_client(model_id) + extra_params={ + "temperature": 0.2, # 降低温度提高响应速度和一致性 + "max_tokens": 600, # 限制最大token数 + "top_p": 0.8, # 优化采样参数 + "stream": False, # 确保非流式输出以获得最快响应 + } + + model_config = get_model_config(self.llm_client) + self.llm_client = OpenAIClient(RedBearModelConfig( + model_name=model_config.get("model_name"), + provider=model_config.get("provider"), + api_key=model_config.get("api_key"), + base_url=model_config.get("base_url"), + timeout=model_config.get("timeout", 30), + max_retries=model_config.get("max_retries", 2), + extra_params=extra_params + ), type_=model_config.get("type")) if self.get_data_func is None: - from app.core.memory.utils.config.get_data import get_data self.get_data_func = get_data # 导入get_data_statement函数 if not hasattr(self, 'get_data_statement'): - from app.core.memory.utils.config.get_data import get_data_statement self.get_data_statement = get_data_statement if self.render_evaluate_prompt_func is None: - from app.core.memory.utils.prompt.template_render import render_evaluate_prompt self.render_evaluate_prompt_func = render_evaluate_prompt if self.render_reflexion_prompt_func is None: - from app.core.memory.utils.prompt.template_render import render_reflexion_prompt self.render_reflexion_prompt_func = render_reflexion_prompt if self.conflict_schema is None: - from app.schemas.memory_storage_schema import ConflictResultSchema self.conflict_schema = ConflictResultSchema if self.reflexion_schema is None: - from app.schemas.memory_storage_schema import ReflexionResultSchema self.reflexion_schema = ReflexionResultSchema if self.update_query is None: - from app.repositories.neo4j.cypher_queries import UPDATE_STATEMENT_INVALID_AT self.update_query = UPDATE_STATEMENT_INVALID_AT self._lazy_init_done = True @@ -284,7 +314,6 @@ class ReflectionEngine: quality_assessments = [] memory_verifies = [] for item in conflict_data: - print(item) quality_assessments.append(item['quality_assessment']) memory_verifies.append(item['memory_verify']) result_data['quality_assessments'] = quality_assessments @@ -298,8 +327,18 @@ class ReflectionEngine: # 记录冲突数据 await self._log_data("conflict", conflict_data) + # Clearn conflict_data,And memory_verify和quality_assessment + cleaned_conflict_data = [] + for item in conflict_data: + cleaned_item = { + 'data': item['data'], + 'conflict': item['conflict'] + } + cleaned_conflict_data.append(cleaned_item) + print(cleaned_conflict_data) + # 3. 解决冲突 - solved_data = await self._resolve_conflicts(conflict_data, source_data) + solved_data = await self._resolve_conflicts(cleaned_conflict_data, source_data) if not solved_data: return ReflectionResult( success=False, @@ -391,7 +430,7 @@ class ReflectionEngine: return [] # 使用转换后的数据 - print("转换后的数据:", data[:2] if len(data) > 2 else data) # 只打印前2条避免日志过长 + # print("转换后的数据:", data[:2] if len(data) > 2 else data) # 只打印前2条避免日志过长 memory_verify = self.config.memory_verify logging.info("====== 冲突检测开始 ======") @@ -469,6 +508,7 @@ class ReflectionEngine: memory_verify, statement_databasets ) + logging.info(f"提示词长度: {len(rendered_prompt)}") messages = [{"role": "user", "content": rendered_prompt}] @@ -629,4 +669,3 @@ class ReflectionEngine: ) else: raise ValueError(f"未知的反思基线: {self.config.baseline}") - diff --git a/api/app/core/memory/utils/prompt/prompts/evaluate.jinja2 b/api/app/core/memory/utils/prompt/prompts/evaluate.jinja2 index e1ecf820..b1293c1d 100644 --- a/api/app/core/memory/utils/prompt/prompts/evaluate.jinja2 +++ b/api/app/core/memory/utils/prompt/prompts/evaluate.jinja2 @@ -1,222 +1,87 @@ -你将收到一组用户历史记忆原始数据(来源于 Neo4j),以及相关配置参数: -原本的输入句子:{{statement_databasets}} -需要检测冲突对象:{{ evaluate_data }} -冲突判定类型:{{ baseline }}(取值为 TIME / FACT / HYBRID) -记忆审核开关:{{ memory_verify }}(取值为 true / false) -记忆质量评估开关开关:{{ quality_assessment }}(取值为 true / false) +# 记忆数据分析任务 -你的任务是: -对用户历史记忆数据进行冲突检测和记忆审核,并输出严格结构化的 JSON 分析结果 -数据的结构: - statement_databasets里面statement_name是输入的句子,statement_id是连接evaluate_data里面的statement_id,代表这个句子被拆分成几个实体,需要根据整体的内容, - 需要根据以下内容做处理(冲突检测、记忆审核、记忆的质量评估) -## 冲突定义 +## 输入数据 +- **原始句子**: {{statement_databasets}} +- **检测对象**: {{ evaluate_data }} +- **冲突类型**: {{ baseline }} (TIME/FACT/HYBRID) +- **隐私审核**: {{ memory_verify }} (true/false) +- **质量评估**: {{ quality_assessment }} (true/false) +## 任务目标 +对用户记忆数据进行冲突检测、隐私审核和质量评估,输出结构化JSON结果。 +**数据关系**: statement_databasets中的statement_id对应evaluate_data中的记录,代表句子拆分后的实体关系。 +## 1. 冲突检测 ### 时间冲突 -时间冲突是指同一用户的相关事件在时间维度上存在逻辑矛盾: - -1. **同一活动的时间冲突**: - - 同一用户的同一活动在不同时间点被记录(如"周五打球"和"周六打球") - - 同一用户在同一时间段内被记录进行不同的互斥活动 - -2. **时间逻辑错误**: - - expired_at 早于 created_at - - 同一事实的 created_at 时间差异超过合理误差范围(>5分钟) - -3. **日期属性冲突**: - - 同一人的生日记录为不同日期(如"2月10号"和"2月16号") -4.存在明确先后约束 A -> B,但 t(A) > t(B) - -例:入学时间晚于毕业时间。 - -处理:标记异常、降权、触发逻辑反思或人工审查。 -5.时间属性冲突 - -单值日期属性出现多值(生日、入职日期) - -注意:本质属于事实冲突的日期特例,归入事实冲突仲裁框架。 -6.互斥重叠冲突 - -例:同一主体的两个事件区间重叠且互斥(如同一时间出现在两地) - -处理:证据仲裁、保留多版本(active + candidate)。 - - - +- **同一活动时间矛盾**: 同一用户同一活动的不同时间记录 +- **时间逻辑错误**: expired_at < created_at,created_at时间差>5分钟 +- **日期属性冲突**: 同一人的生日等单值属性出现多值 +- **先后约束违反**: 存在A→B约束但t(A)>t(B)(如入学>毕业) +- **互斥重叠**: 同一时间出现在不同地点等互斥事件 ### 事实冲突 -事实冲突是指同一实体的属性或关系存在相互矛盾的陈述: +- **属性互斥**: 同一实体的相反属性(喜欢↔不喜欢) +- **关系矛盾**: 同一实体在相同语境下的不同关系描述 +- **身份冲突**: 同一实体被赋予不同类型或角色 +### 混合冲突 +检测所有逻辑不一致或相互矛盾的记录。 +**检测原则**: +- 重点检查相同实体的记录 +- 分析description字段语义冲突 +- 验证时间字段逻辑一致性 +## 2. 隐私审核 (memory_verify=true时) +### 隐私信息类型 +- **身份信息**: 身份证号码、身份证相关描述 +- **联系方式**: 手机号、电话号码 +- **社交账号**: 微信号、QQ号、邮箱地址 +- **金融信息**: 银行卡号、账户信息、支付信息 +- **税务信息**: 税号、纳税信息、发票信息 +- **贷款信息**: 贷款记录、信贷信息 +- **安全信息**: 密码、PIN码、验证码 +### 检测方法 +- 检测description、entity1_name、entity2_name、name等字段 +- 识别数字模式(手机号11位、身份证18位等) +- 识别关键词("身份证"、"银行卡"、"密码"等) +## 3. 质量评估 (quality_assessment=true时) +### 评估标准 +- **数据完整性**: 必要字段完整性、关系描述清晰度、时间字段有效性 +- **重复检测**: 相同或高度相似记录、冗余实体关系、描述重复度 +- **无意义检测**: 空值/无效值、过于简单的描述、格式错误 +- **上下文依赖**: 记录自包含性、实体名称明确性 +### 输出内容 +- **质量分数**: 0-100的整体质量百分比 +- **质量概述**: 简要描述数据质量状况和主要问题 +## 输出规则 +### 核心原则 +1. **conflict=true**: 存在冲突或隐私信息时,将所有相关记录放入data数组 +2. **conflict=false**: 无冲突且无隐私信息时,data为空数组 +3. **独立功能**: 冲突检测、隐私审核、质量评估三者完全独立 +4. **条件输出**: + - quality_assessment=true时输出评估对象,否则为null + - memory_verify=true时输出隐私检测对象,否则为null +5. **不输出conflict_memory字段** +### 处理流程 +1. 冲突检测 → 将冲突记录加入data +2. 隐私审核(如启用) → 将隐私记录加入data +3. 质量评估(如启用) → 独立输出评估结果 +4. 去重data数组中的记录 -1. **属性互斥**:同一实体的相反属性(喜欢↔不喜欢、有↔没有、是↔不是) -2. **关系矛盾**:同一实体在相同语境下的不同关系描述 -3. **身份冲突**:同一实体被赋予不同的类型或角色 - -### 混合冲突检测 -检测所有类型的冲突,包括但不限于时间冲突和事实冲突: -检测任何逻辑上不一致或相互矛盾的记录 -## 记忆审核定义 - -### 隐私信息检测(隐私冲突) -当memory_verify为true时,需要额外检测包含个人隐私信息的记录: - -1. **身份证信息**:包含身份证号码、身份证相关描述 -2. **手机号码**:包含手机号、电话号码等联系方式 -3. **社交账号**:包含微信号、QQ号、邮箱地址等社交平台信息 -4. **银行信息**:包含银行卡号、账户信息、支付信息 -5. **税务信息**:包含税号、纳税信息、发票信息 -6. **贷款信息**:包含贷款记录、信贷信息、借款信息 -7. **其他敏感信息**:包含密码、PIN码、验证码等安全信息 - -### 隐私检测原则 -- 检测description、entity1_name、entity2_name等字段中的隐私信息 -- 识别数字模式(如手机号11位数字、身份证18位等) -- 识别关键词(如"身份证"、"银行卡"、"密码"等) -- 检测敏感实体类型和关系 - -## 冲突检测原则 - -**全面检测**:不区分冲突类型,检测所有可能的冲突 -**完整输出**:如果发现任何冲突或隐私信息,必须将所有相关记录都放入data字段 -**实体关联**:重点检查涉及相同实体(entity1_name, entity2_name)的记录 -**语义分析**:分析description字段的语义相似性和冲突性 -**时间逻辑**:检查时间字段的逻辑一致性 -**隐私检测**:当memory_verify为true时,检测所有包含隐私信息的记录 - -## 不符合冲突检测 - -称呼 -## 重要检测示例 - -### 冲突检测示例 -- 用户与不同时间点的关系(周五 vs 周六,2月10号 vs 2月16号) -- 同一实体的重复定义但描述不同 -- 同一关系的不同表述但含义冲突 -- 任何逻辑上不可能同时为真的记录 - -### 隐私信息检测示例 -- 包含手机号的记录:"用户的手机号是13812345678" -- 包含身份证的记录:"身份证号码为110101199001011234" -- 包含银行卡的记录:"银行卡号6222021234567890" -- 包含社交账号的记录:"微信号是user123456" -- 包含敏感信息的实体名称或描述 - -## 输出要求 - -**关键原则**: -1. 当存在冲突或检测到隐私信息时,conflict才为true,data字段才包含相关记录 -2. 如果发现冲突,必须将所有相关的冲突记录都放入data数组中 -3. 如果memory_verify为true且检测到隐私信息,必须将包含隐私信息的记录也放入data数组中 -4. 既没有冲突也没有隐私信息时,conflict为false,data为空数组 -5. 如果quality_assessment为true,独立分析数据质量并输出评估结果;如果为false,quality_assessment字段输出null -6. 冲突检测、隐私审核和质量评估三个功能完全独立,互不影响 -7. 不输出conflict_memory字段 - -**处理逻辑**: -- 首先进行冲突检测,将冲突记录加入data数组 -- 如果memory_verify为true,再进行隐私信息检测,将包含隐私信息的记录也加入data数组 -- 如果quality_assessment为true,独立进行质量评估,分析所有输入数据的质量并输出评估结果 -- 最终data数组包含所有冲突记录和隐私信息记录(去重) -- quality_assessment字段独立输出,不影响冲突检测和隐私审核结果 -- memory_verify字段独立输出隐私检测结果,包含检测到的隐私信息类型和概述 - -返回数据格式以json方式输出: -- 必须通过json.loads()的格式支持的形式输出,响应必须是与此确切模式匹配的有效JSON对象。不要在JSON之前或之后包含任何文本。 -- 关键的JSON格式要求{"statement":识别出的文本内容} -1.JSON结构仅使用标准ASCII双引号(")-切勿使用中文引号("")或其他Unicode引号 -2.如果提取的语句文本包含引号,请使用反斜杠(\")正确转义它们 -3.确保所有JSON字符串都正确关闭并以逗号分隔 -4.JSON字符串值中不包括换行符 -5.正确转义的例子:"statement":"Zhang Xinhua said:\"我非常喜欢这本书\"" -6.不允许输出```json```相关符号,如```json```、``````、```python```、```javascript```、```html```、```css```、```sql```、```java```、```c```、```c++```、```c#```、```ruby``` - -## 记忆质量评估定义 - -### 质量评估标准 -当quality_assessment为true时,需要对记忆数据进行质量评估: - -1. **数据完整性**: - - 检查必要字段是否完整(entity1_name、entity2_name、description等) - - 检查关系描述是否清晰明确 - - 检查时间字段的有效性 - -2. **重复字段检测**: - - 识别相同或高度相似的记录 - - 检测冗余的实体关系 - - 分析描述内容的重复度 - -3. **无意义字段检测**: - - 识别空值、无效值或占位符内容 - - 检测过于简单或无信息量的描述 - - 识别格式错误或不规范的数据 - -4. **上下文依赖性**: - - 评估记录是否需要额外上下文才能理解 - - 检查实体名称的明确性 - - 分析关系描述的自包含性 - -### 质量评估输出 -- **质量百分比**:基于上述标准计算的整体质量分数(0-100) -- **质量概述**:简要描述数据质量状况,包括主要问题和优点 - -输出是仅输出一个合法 JSON 对象,严格遵循下述结构: +**输出结构**: +```json { - "data": [ - { - "entity1_name": "实体1名称", - "description": "描述信息", - "statement_id": "陈述ID", - "created_at": "创建时间戳", - "expired_at": "过期时间戳", - "relationship_type": "关系类型", - "relationship": "关系对象", - "entity2_name": "实体2名称", - "entity2": "实体2对象" - } - ], - "conflict": true或false, + "data": [记录数组], + "conflict": true/false, "quality_assessment": { - "score": 质量百分比数字, - "summary": "质量概述文本" + "score": 数字, + "summary": "文本" } 或 null, "memory_verify": { - "has_privacy": true或false, - "privacy_types": ["检测到的隐私信息类型列表"], - "summary": "隐私检测结果概述" + "has_privacy": true/false, + "privacy_types": ["类型数组"], + "summary": "概述文本" } 或 null } - -必须遵守: -- 只输出 JSON,不要添加解释或多余文本。 -- 使用标准双引号,必要时对内部引号进行转义。 -- 字段名与结构必须与给定模式一致。 -- data数组中包含冲突记录和隐私信息记录,如果都没有则为空数组。 -- quality_assessment字段:当quality_assessment参数为true时输出评估对象,为false时输出null。 -- memory_verify字段:当memory_verify参数为true时输出隐私检测结果对象,为false时输出null。 - -### memory_verify字段说明 -当memory_verify为true时,需要输出隐私检测结果: -- **has_privacy**: 布尔值,表示是否检测到隐私信息 -- **privacy_types**: 字符串数组,包含检测到的隐私信息类型(如["手机号码", "身份证信息"]) -- **summary**: 字符串,简要描述隐私检测结果 - -当memory_verify为false时,memory_verify字段输出null。 - -### memory_verify字段示例 - -**示例1:检测到隐私信息** -```json -"memory_verify": { - "has_privacy": true, - "privacy_types": ["手机号码", "身份证信息"], - "summary": "检测到2条记录包含隐私信息:1个手机号码,1个身份证号码" -} ``` - -**示例2:未检测到隐私信息** -```json -"memory_verify": { - "has_privacy": false, - "privacy_types": [], - "summary": "未检测到隐私信息" -} -``` - -**示例3:memory_verify为false时** -```json -"memory_verify": null -``` - -模式参考: -{{ json_schema }} \ No newline at end of file +**字段说明**: +- **data**: 包含冲突记录和隐私信息记录,无则为空数组 +- **quality_assessment**: quality_assessment=true时输出评估对象,否则为null +- **memory_verify**: memory_verify=true时输出隐私检测对象,否则为null +模式参考:{{ json_schema }} \ No newline at end of file diff --git a/api/app/core/memory/utils/prompt/prompts/reflexion.jinja2 b/api/app/core/memory/utils/prompt/prompts/reflexion.jinja2 index 43e8e100..5b831c02 100644 --- a/api/app/core/memory/utils/prompt/prompts/reflexion.jinja2 +++ b/api/app/core/memory/utils/prompt/prompts/reflexion.jinja2 @@ -1,160 +1,127 @@ -你将收到一组用户历史记忆原始数据(来源于 Neo4j) -你将收到一条冲突判定对象:{{ data }}。 -需要检测冲突对象:{{ statement_databasets }} -以及需要识别的冲突对象为:{{ baseline }} -记忆审核开关:{{ memory_verify }}(取值为 true / false) +# 记忆冲突解决任务 -角色: -- 你是数据领域中解决数据冲突的专家 +## 输入数据 +- **冲突数据**: {{ data }} +- **原始句子**: {{ statement_databasets }} +- **冲突类型**: {{ baseline }} (TIME/FACT/HYBRID) +- **隐私审核**: {{ memory_verify }} (true/false) -任务:分析冲突产生原因,按冲突类型分组处理,为每种冲突类型生成独立的解决方案。 +## 任务目标 +作为数据冲突解决专家,分析冲突原因,按类型分组处理,为每种冲突生成独立解决方案。 -数据的结构: - statement_databasets里面statement_name是输入的句子,statement_id是连接data里面的statement_id,代表这个句子被拆分成几个实体,需要根据整体的内容, - 需要根据以下内容做处理(冲突检测、记忆审核、记忆的质量评估),data里面的statement_created_at是用户输入的时间 +**数据关系**: statement_databasets中的statement_id对应data中的记录,statement_created_at为用户输入时间。 -**处理模式**: -- 当memory_verify为false时:仅处理数据冲突 -- 当memory_verify为true时:处理数据冲突 + 隐私信息脱敏 +**处理模式**: +- memory_verify=false: 仅处理数据冲突 +- memory_verify=true: 处理数据冲突 + 隐私脱敏 -## 分组处理原则 +## 1. 冲突类型定义 -**冲突类型识别与分组**: -1. **日期冲突**: - 1.1.涉及用户生日的不同日期记录(如2月10号 vs 2月16号), - 1.2.涉及同一活动的不同时间记录(如周五打球 vs 周六打球) -3. **事实属性冲突**: - 3.1. **属性互斥**:同一实体的相反属性(喜欢↔不喜欢、有↔没有、是↔不是) - 3.2. **关系矛盾**:同一实体在相同语境下的不同关系描述 - 3.3. **身份冲突**:同一实体被赋予不同的类型或角色 -4. **其他冲突类型/混合冲突(时间+事实)**:根据具体数据识别 +### 时间冲突 (TIME) +时间维度冲突:两个事件时间重叠,或同一事情在不同时间场景下的变化。 -**分组输出要求**: -- 每种冲突类型生成一个独立的reflexion_result对象 -- 同一类型的多个冲突记录归并到一个结果中 -- 不同类型的冲突分别处理,各自生成独立结果 +### 事实冲突 (FACT) +同一事实对象的陈述内容相互矛盾,真假不能共存的情况。 -## 冲突类型定义 +### 混合冲突 (HYBRID) +检测所有类型冲突,包括时间和事实冲突的任何逻辑不一致记录。 -### 时间冲突(TIME) -时间维度冲突是指两个事件发生时间重叠,或者用户同一件事情和场景等情况下,时间出现了变化。 +## 2. 分组处理原则 -### 事实冲突(FACT) -事实冲突是指同一事实对象(同一个人、同一个时间、同一个状态)但陈述内容相互矛盾,主要为真假不能共存的情况。 -### 混合冲突(HYBRID) -检测所有类型的冲突,包括但不限于时间冲突和事实冲突:检测任何逻辑上不一致或相互矛盾的记录 -{% if memory_verify %} -## 隐私信息处理(memory_verify为true时启用) +### 冲突类型识别 +- **日期冲突**: 用户生日不同日期(2月10号 vs 2月16号)、同一活动不同时间(周五 vs 周六打球) +- **事实属性冲突**: + - 属性互斥(喜欢↔不喜欢) + - 关系矛盾(同一实体不同关系描述) + - 身份冲突(同一实体不同类型/角色) +- **其他/混合冲突**: 根据具体数据识别 -### 隐私信息识别 -需要识别并处理以下类型的隐私信息: +### 分组输出要求 +- 每种冲突类型生成独立的reflexion_result对象 +- 同类型多个冲突归并到一个结果 +- 不同类型分别处理,各自生成独立结果 +## 3. 隐私信息处理 (memory_verify=true时) -1. **身份证信息**:包含身份证号码、身份证相关描述 -2. **手机号码**:包含手机号、电话号码等联系方式 -3. **社交账号**:包含微信号、QQ号、邮箱地址等社交平台信息 -4. **银行信息**:包含银行卡号、账户信息、支付信息 -5. **税务信息**:包含税号、纳税信息、发票信息 -6. **贷款信息**:包含贷款记录、信贷信息、借款信息 -7. **其他敏感信息**:包含密码、PIN码、验证码等安全信息 +### 隐私信息类型 +- **身份信息**: 身份证号码、身份证相关描述 +- **联系方式**: 手机号、电话号码 +- **社交账号**: 微信号、QQ号、邮箱地址 +- **金融信息**: 银行卡号、账户信息、支付信息 +- **税务信息**: 税号、纳税信息、发票信息 +- **贷款信息**: 贷款记录、信贷信息 +- **安全信息**: 密码、PIN码、验证码 -### 隐私数据脱敏规则 -对于检测到的隐私信息,按以下规则进行脱敏处理: +### 脱敏规则 +**数字类**: 保留前三位和后四位,中间用*代替 +- 手机号: 13812345678 → 138****5678 +- 身份证: 110101199001011234 → 110***********1234 +- 银行卡: 6222021234567890 → 622***********7890 -**数字类隐私信息脱敏**: -- 保留前三位和后四位,中间用*代替 -- 示例:手机号13812345678 → 138****5678 -- 示例:身份证110101199001011234 → 110***********1234 -- 示例:银行卡6222021234567890 → 622***********7890 +**文本类**: 保留前三后四位字符,中间用*代替 +- 微信号: user123456 → use****3456 +- 邮箱: zhang.san@example.com → zha****@example.com -**文本类隐私信息脱敏**: -- 社交账号:保留前三后四位字符,中间用*代替 -- 示例:微信号user123456 → use****3456 -- 示例:邮箱zhang.san@example.com → zha****@example.com +**脱敏字段**: name、entity1_name、entity2_name、description -**脱敏处理字段**: -- name字段:如包含隐私信息需脱敏 -- entity1_name字段:如包含隐私信息需脱敏 -- entity2_name字段:如包含隐私信息需脱敏 -- description字段:如包含隐私信息需脱敏 -{% endif %} +## 4. 处理流程 -## 工作步骤 +### 步骤1: 类型匹配验证 +**匹配规则**: +- baseline="TIME": 只处理时间相关冲突(涉及时间表达式、日期、时间点) +- baseline="FACT": 只处理事实相关冲突(属性矛盾、关系冲突、描述不一致) +- baseline="HYBRID": 处理所有类型冲突 -### 第一步:分析冲突类型匹配 -首先判断输入的冲突数据是否符合baseline要求的类型: +**类型识别**: +- 时间冲突: entity2的entity_type包含"TimeExpression"/"TemporalExpression",或entity2_name包含时间词汇 +- 事实冲突: 相同实体的不同属性描述、互斥关系陈述 -**类型匹配规则**: -- 如果baseline是"TIME":只处理时间相关的冲突(涉及时间表达式、日期、时间点的冲突) -- 如果baseline是"FACT":只处理事实相关的冲突(属性矛盾、关系冲突、描述不一致) -- 如果baseline是"HYBRID":处理所有类型的冲突,也可以当作混合冲突类型处理 +**重要**: 类型不匹配时必须输出空结果(resolved为null) -**类型识别**: -- 时间冲突标识:entity2的entity_type包含"TimeExpression"、"TemporalExpression",或entity2_name包含时间词汇(周一到周日、月份日期等) -- 事实冲突标识:相同实体的不同属性描述、互斥的关系陈述 +### 步骤2: 冲突数据分组 +**分组策略**: +- 时间冲突组: 涉及用户时间的记录 +- 活动时间冲突组: 同一活动不同时间的记录 +- 事实冲突组: 同一实体不同属性的记录 +- 其他冲突组: 其他类型冲突记录 -**重要**:如果输入的冲突类型与baseline不匹配,必须输出空结果(resolved为null) +**筛选条件**: 只处理与baseline匹配的冲突类型 -### 第二步:筛选并分组冲突数据 -按冲突类型对数据进行分组: +### 步骤3: 冲突解决策略 +**重要**: 数据被判定为正确时不可修改 -**分组策略**: -1. **时间冲突组**:筛选涉及用户时间的所有记录 -2. **活动时间冲突组**:筛选涉及同一活动不同时间的记录 -3. **事实冲突组**:筛选涉及同一实体不同属性的记录 -4. **其他冲突组**:其他类型的冲突记录 +**智能解决**: +1. 分析冲突数据,结合statement_databasets原文判定正确性 +2. 判断正确答案是否存在于data中 +3. 根据情况选择处理方式{% if memory_verify %} +4. 隐私脱敏处理在冲突解决后进行{% endif %} -**筛选条件**: -- 只处理与baseline匹配的冲突类型 -- 相同entity1_name但entity2_name不同的记录 -- 相同关系但描述矛盾的记录 -- 时间逻辑不一致的记录 +### 处理规则 -### 第三步:冲突解决策略 -** 不可以解决的冲突情况 - 1. 数据被判定为正确的情况下,不可以进行修改 -**仅当冲突类型与baseline匹配时**,对筛选出的冲突数据进行处理: - -**智能解决策略**: -1. **分析冲突数据**:识别哪些记录是正确的,哪些是错误的,需要结合statement_databasets的输入原文来判定 -2. **判断正确答案是否存在**: - - 如果正确答案已存在于data中:只需将错误记录的expired_at设为当前日期(2025-12-16T12:00:00) - - 如果正确答案已存在于data中:错误记录的expired_at已经设为日期,则不需要对正确的数据进行修改 - - 如果正确答案不存在于data中:需要修改现有记录的内容以包含正确信息 - -{% if memory_verify %} -**隐私处理集成**: -- 在处理冲突的同时,需要对涉及的记录进行隐私脱敏 -- 脱敏处理应该在冲突解决之后进行,确保最终输出的记录都已脱敏 -- 在change字段中记录隐私脱敏的变更 -{% endif %} - -**具体处理规则**: - -**情况1:正确答案存在于data中** -- 保留正确的记录不变 -- 基于时间关系的冲突: - 需要只修改错误记录的expired_at为当前时间(2025-12-16T12:00:00) -- 基于事实的关系冲突 +**情况1: 正确答案存在于data中** +- 保留正确记录不变 +- 时间冲突: 修改错误记录的expired_at为当前时间(2025-12-16T12:00:00) +- 事实冲突: 同样处理 - resolved.resolved_memory只包含被设为失效的错误记录 -- change字段只记录expired_at的变更:`[{"expired_at": "2025-12-16T12:00:00"}]`(注意:如果已存在时间,则不需要对其修改,也不需要变更 时间) +- change字段只记录expired_at变更: `[{"expired_at": "2025-12-16T12:00:00"}]` +- 注意: 如果已存在时间则不需要修改 -**情况2:正确答案不存在于data中** -- 选择最合适的记录进行修改 -- 更新该记录的相关字段: - - description字段:添加或修改描述信息{% if memory_verify %}(如包含隐私信息,需脱敏处理){% endif %} - - name字段:修改名称字段{% if memory_verify %}(如需要,包含隐私信息时需脱敏){% endif %} -- resolved.resolved_memory包含修改后的完整记录{% if memory_verify %}(已脱敏){% endif %} -- change字段记录所有被修改的字段{% if memory_verify %},包括脱敏变更{% endif %},例如:`[{"description": "新描述"{% if memory_verify %}, "entity2_name": "138****5678"{% endif %}}]` +**情况2: 正确答案不存在于data中** +- 选择最合适记录进行修改 +- 更新相关字段: + - description字段: 添加或修改描述信息{% if memory_verify %}(含隐私信息需脱敏){% endif %} + - name字段: 修改名称字段{% if memory_verify %}(含隐私信息需脱敏){% endif %} +- resolved.resolved_memory包含修改后的完整记录{% if memory_verify %}(已脱敏){% endif %} +- change字段记录所有被修改字段{% if memory_verify %},包括脱敏变更{% endif %} -**重要原则**: -- **只输出需要修改的记录**:resolved.resolved_memory只包含实际需要修改的数据 -- **优先保留策略**:时间冲突保留最可信的created_at时间的记录,事实冲突选择最新且可信度最高的记录 -- **精确记录变更**:change字段必须包含记录ID、字段名称、新值和旧值 -{% if memory_verify %}- **隐私保护优先**:所有输出的记录必须完成隐私脱敏处理 -- **脱敏变更记录**:隐私脱敏的变更也必须在change字段中详细记录{% endif %} -- **不可修改数据**:数据被判定为正确时,不可以进行修改,如果没有数据可输出空 +**核心原则**: +- 只输出需要修改的记录 +- 优先保留策略: 时间冲突保留最可信created_at时间,事实冲突选择最新且可信度最高记录 +- 精确记录变更: change字段包含记录ID、字段名称、新值和旧值{% if memory_verify %} +- 隐私保护优先: 所有输出记录必须完成隐私脱敏 +- 脱敏变更记录: 隐私脱敏变更也必须在change字段中记录{% endif %} +- 不可修改数据: 数据被判定为正确时不可修改,无数据可输出时为空 -**变更记录格式**: +**变更记录格式**: ```json "change": [ { @@ -166,35 +133,28 @@ ] ``` -**类型不匹配处理**: -- 如果冲突类型与baseline不匹配,resolved必须设为null -- reflexion.reason说明类型不匹配的原因 +**类型不匹配处理**: +- 冲突类型与baseline不匹配时,resolved设为null +- reflexion.reason说明类型不匹配原因 - reflexion.solution说明无需处理 -### 第四步:输出解决方案 +## 5. JSON输出格式 -## 输出要求 -**嵌套字段映射**(系统会自动处理): +**格式要求**: +- 输出有效JSON对象,通过json.loads()解析 +- 使用标准ASCII双引号(") +- 内部引号用反斜杠转义(\") +- 字符串值不包含换行符 +- 不输出```json```等代码块标记 + +**嵌套字段映射**(系统自动处理): - `entity2.name` → 自动映射为 `name` -- `entity1.name` → 自动映射为 `name` +- `entity1.name` → 自动映射为 `name` - `entity1.description` → 自动映射为 `description` - `entity2.description` → 自动映射为 `description` -返回数据格式以json方式输出: -- 必须通过json.loads()的格式支持的形式输出 -- 响应必须是与此确切模式匹配的有效JSON对象 -- 不要在JSON之前或之后包含任何文本 - -JSON格式要求: -1. JSON结构仅使用标准ASCII双引号(") -2. 如果提取的语句文本包含引号,请使用反斜杠(\")正确转义 -3. 确保所有JSON字符串都正确关闭并以逗号分隔 -4. JSON字符串值中不包括换行符 -5. 不允许输出```json```相关符号 - -仅输出一个合法 JSON 对象,严格遵循下述结构: - -**输出格式:按冲突类型分组的列表** +**输出结构**: 按冲突类型分组的列表 +```json { "results": [ { @@ -208,93 +168,25 @@ JSON格式要求: }, "resolved": { "original_memory_id": "被设为失效的记忆id", - "resolved_memory": { - "entity1_name": "实体1名称", - "entity2_name": "实体2名称", - "description": "描述信息", - "statement_id": "陈述ID", - "created_at": "创建时间", - "expired_at": "过期时间", - "relationship_type": "关系类型", - "relationship": {}, - "entity2": {...} - }, - "change": [ - { - "field": [ - {"字段名1": "修改后的值1"}, - {"字段名2": "修改后的值2"} - ] - } - ] + "resolved_memory": {记录对象}, + "change": [变更记录数组] }, "type": "reflexion_result" } ] } +``` -**示例:多种冲突类型的输出** -{ - "results": [ - { - "conflict": { - "data": [生日冲突相关的记录], - "conflict": true - }, - "reflexion": { - "reason": "检测到生日冲突:用户同时关联2月10号和2月16号两个不同日期", - "solution": "保留最新记录(2月16号),将旧记录(2月10号)设为失效" - }, - "resolved": { - "original_memory_id": "df066210883545a08e727ccd8ad4ec77", - "resolved_memory": {...}, - "change": [ - { - "field": [ - {"expired_at": "2025-12-16T12:00:00"} - ] - } - ] - }, - "type": "reflexion_result" - }, - { - "conflict": { - "data": [篮球时间冲突相关的记录], - "conflict": true - }, - "reflexion": { - "reason": "检测到活动时间冲突:用户打篮球时间存在周五和周六的冲突", - "solution": "保留最可信的时间记录,将冲突记录设为失效" - }, - "resolved": { - "original_memory_id": "另一个记录ID", - "resolved_memory": {...}, - "change": [ - { - "field": [ - {"description": "使用系统的个人,指代说话者本人,篮球时间为周六"}, - {"entity2_name": "周六"} - ] - } - ] - }, - "type": "reflexion_result" - } - ] -} +**输出要求**: +- 只输出JSON,不添加解释文本 +- 使用标准双引号,必要时转义 +- 字段名与结构必须与模式一致 +- **results数组格式**: 每个冲突类型作为独立对象 +- **按冲突类型分组**: 相同类型冲突归并到一个result对象 +- **conflict.data**: 只包含该冲突类型相关记录 +- **resolved.resolved_memory**: 只包含需要修改的记录 +- **resolved.change**: 包含详细变更信息 +- 无需修改的冲突类型resolved为null +- 与baseline不匹配的冲突类型不包含在results中 -必须遵守: -- 只输出 JSON,不要添加解释或多余文本 -- 使用标准双引号,必要时对内部引号进行转义 -- 字段名与结构必须与给定模式一致 -- **输出必须是results数组格式**,每个冲突类型作为一个独立的对象 -- **按冲突类型分组**:相同类型的冲突记录归并到一个result对象中 -- **每个result对象的conflict.data**只包含该冲突类型相关的记录 -- **resolved.resolved_memory 只包含需要修改的记录**,不需要修改的记录不要输出 -- **resolved.change 必须包含详细的变更信息**:field数组包含所有被修改的字段及其新值 -- 如果某个冲突类型经分析无需修改任何数据,该类型的resolved 必须为 null -- 如果与baseline不匹配的冲突类型,不要在results中包含该类型 - -模式参考: -{{ json_schema }} \ No newline at end of file +模式参考: {{ json_schema }} \ No newline at end of file diff --git a/api/app/schemas/memory_storage_schema.py b/api/app/schemas/memory_storage_schema.py index ab6b0512..4d8f317a 100644 --- a/api/app/schemas/memory_storage_schema.py +++ b/api/app/schemas/memory_storage_schema.py @@ -31,13 +31,8 @@ class BaseDataSchema(BaseModel): # 保持原有必需字段为可选,以兼容不同数据源 id: Optional[str] = Field(None, description="The unique identifier for the data entry.") statement: Optional[str] = Field(None, description="The statement text.") - group_id: Optional[str] = Field(None, description="The group identifier.") - chunk_id: Optional[str] = Field(None, description="The chunk identifier.") created_at: str = Field(..., description="The creation timestamp in ISO 8601 format.") expired_at: Optional[str] = Field(None, description="The expiration timestamp in ISO 8601 format.") - valid_at: Optional[str] = Field(None, description="The validation timestamp in ISO 8601 format.") - invalid_at: Optional[str] = Field(None, description="The invalidation timestamp in ISO 8601 format.") - entity_ids: List[str] = Field([], description="The list of entity identifiers.") description: Optional[str] = Field(None, description="The description of the data entry.") # 新增字段以匹配实际输入数据