From 17978cc71f3bd14e0cb90a74302b92690282294d Mon Sep 17 00:00:00 2001 From: root Date: Thu, 30 Apr 2026 09:01:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20memory=5Fquery=5Fa?= =?UTF-8?q?rchived=20=E5=B7=A5=E5=85=B7=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=8C=89=E6=97=B6=E9=97=B4/=E5=85=B3=E9=94=AE=E8=AF=8D?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E5=BD=92=E6=A1=A3=E8=AE=B0=E5=BF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/embedded_db.py | 68 ++++++++++++++++++++++++++++++++++++++ core/graph_client.py | 43 ++++++++++++++++++++++++ core/tool_executor.py | 7 ++++ core/tools/memory_tools.py | 30 +++++++++++++++++ 4 files changed, 148 insertions(+) diff --git a/core/embedded_db.py b/core/embedded_db.py index 0600a47..f17637e 100644 --- a/core/embedded_db.py +++ b/core/embedded_db.py @@ -621,6 +621,74 @@ class EmbeddedGraphDB: "message": f"归档了 {archived} 条关系" } + def query_archived(self, days: int = None, keyword: str = "") -> Dict: + """ + 查询已归档的记忆 + + Args: + days: 可选,最近N天内的归档记录 + keyword: 可选,过滤包含指定关键词的实体名或关系 + + Returns: + 归档记录列表 + """ + cursor = self.conn.cursor() + + # 基础 SQL:查询已归档的关系及其关联实体 + conditions = ["r.status = 'archived'"] + params = [] + + # 时间范围过滤(最近N天) + if days is not None and days > 0: + conditions.append("r.updated_at >= datetime('now', ?)") + params.append(f'-{days} days') + + # 关键词过滤(匹配源实体名、目标实体名、关系类型任一) + if keyword: + # 先找到匹配的实体ID + cursor.execute("SELECT id FROM entities WHERE name LIKE ?", (f'%{keyword}%',)) + matched_ids = [str(row['id']) for row in cursor.fetchall()] + + if matched_ids: + id_list = ','.join(matched_ids) + conditions.append(f"(r.source_id IN ({id_list}) OR r.target_id IN ({id_list}) OR r.relation_type LIKE ?)") + params.append(f'%{keyword}%') + else: + conditions.append("r.relation_type LIKE ?") + params.append(f'%{keyword}%') + + where_clause = " AND ".join(conditions) + + cursor.execute(f""" + SELECT r.id, r.relation_type, r.created_at, r.updated_at, + e.name AS source_name, t.name AS target_name + FROM relations r + JOIN entities e ON r.source_id = e.id + JOIN entities t ON r.target_id = t.id + WHERE {where_clause} + ORDER BY r.updated_at DESC + LIMIT 200 + """, params) + + rows = cursor.fetchall() + + results = [] + for row in rows: + results.append({ + "id": row['id'], + "source": row['source_name'], + "relation": row['relation_type'], + "target": row['target_name'], + "archived_at": row['updated_at'], + "created_at": row['created_at'] + }) + + return { + "archived_relations": results, + "total_relations": len(results), + "message": f"找到 {len(results)} 条归档关系" + } + def cleanup(self, dry_run: bool = True) -> Dict: """清理已删除数据""" cursor = self.conn.cursor() diff --git a/core/graph_client.py b/core/graph_client.py index 1df0b71..046f424 100644 --- a/core/graph_client.py +++ b/core/graph_client.py @@ -282,6 +282,49 @@ class Neo4jGraph: return {"archived_count": result.single()["archived"], "days": days} + def query_archived(self, days: int = None, keyword: str = "") -> dict: + """查询归档记忆""" + with self.driver.session() as session: + filters = [] + params = {} + + filters.append("r.status = 'archived'") + + if days is not None and days > 0: + filters.append("r.archived_at >= datetime() - duration('P' + $days + 'D')") + params["days"] = str(days) + + if keyword: + filters.append("(e.name CONTAINS $keyword OR t.name CONTAINS $keyword OR r.type CONTAINS $keyword)") + params["keyword"] = keyword + + where = " AND ".join(filters) + + result = session.run(f""" + MATCH (e:Entity)-[r:RELATES]->(t:Entity) + WHERE {where} + RETURN e.name as source, r.type as relation, t.name as target, + r.archived_at as archived_at, r.created_at as created_at + ORDER BY r.archived_at DESC + LIMIT 200 + """, params) + + records = [] + for row in result: + records.append({ + "source": row["source"], + "relation": row["relation"], + "target": row["target"], + "archived_at": str(row["archived_at"]) if row.get("archived_at") else "", + "created_at": str(row["created_at"]) if row.get("created_at") else "" + }) + + return { + "archived_relations": records, + "total_relations": len(records), + "message": f"找到 {len(records)} 条归档关系" + } + def cleanup(self, dry_run: bool = True) -> dict: """清理无效数据""" with self.driver.session() as session: diff --git a/core/tool_executor.py b/core/tool_executor.py index aaef4fe..33c77f2 100644 --- a/core/tool_executor.py +++ b/core/tool_executor.py @@ -69,6 +69,13 @@ def execute_tool(graph: Any, tool_name: str, arguments: dict) -> str: result = graph.cleanup(dry_run=arguments.get("dry_run", True)) return json.dumps(result, ensure_ascii=False, default=str) + elif tool_name == "memory_query_archived": + days = arguments.get("days") + keyword = arguments.get("keyword", "") + recorder.record("query", tool_name, f"days={days}, keyword={keyword}") + result = graph.query_archived(days=days, keyword=keyword) + return json.dumps(result, ensure_ascii=False, default=str) + elif tool_name == "context_rewrite": result = execute_context_rewrite(graph, arguments) return json.dumps(result, ensure_ascii=False, default=str) diff --git a/core/tools/memory_tools.py b/core/tools/memory_tools.py index fb7da6b..9b1d1f3 100644 --- a/core/tools/memory_tools.py +++ b/core/tools/memory_tools.py @@ -244,6 +244,36 @@ MEMORY_TOOLS = [ } } }, + { + "type": "function", + "function": { + "name": "memory_query_archived", + "description": """查询已归档的记忆。 + +【使用场景】 +- 想了解之前归档过哪些记忆 +- 按关键词搜索归档内容 +- 按时间范围查看最近归档的历史 + +【注意】 +- days 和 keyword 可以单独使用,也可以组合使用 +- 不加任何参数时返回最近的所有归档记录 +""", + "parameters": { + "type": "object", + "properties": { + "days": { + "type": "integer", + "description": "最近N天内的归档记录,不指定则不限时间" + }, + "keyword": { + "type": "string", + "description": "关键词,匹配实体名或关系类型" + } + } + } + } + }, { "type": "function", "function": {