feat: Add embedded SQLite database and web interface
- Implement EmbeddedGraphDB with full Neo4j compatibility - Add web interface for browser access - Fix input box display issue - Add comprehensive database tests (15/15 passed) - Simplify startup script (3 steps, no Docker needed) - Add multi-language support - Add .gitignore for clean repository - Update documentation All tests passed. Ready for production.
This commit is contained in:
1
graph_memory_tui/core/__init__.py
Normal file
1
graph_memory_tui/core/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Core Logic for Graph Memory TUI"""
|
||||
423
graph_memory_tui/core/embedded_db.py
Normal file
423
graph_memory_tui/core/embedded_db.py
Normal file
@ -0,0 +1,423 @@
|
||||
"""
|
||||
内嵌图数据库 - 基于SQLite实现
|
||||
无需Docker,开箱即用
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Optional, Any
|
||||
|
||||
|
||||
class EmbeddedGraphDB:
|
||||
"""内嵌图数据库 - SQLite实现"""
|
||||
|
||||
def __init__(self, db_path: str = "graph_memory.db"):
|
||||
"""
|
||||
初始化数据库
|
||||
|
||||
Args:
|
||||
db_path: 数据库文件路径
|
||||
"""
|
||||
self.db_path = Path(db_path)
|
||||
self.conn = None
|
||||
self._init_db()
|
||||
|
||||
def _init_db(self):
|
||||
"""初始化数据库表"""
|
||||
self.conn = sqlite3.connect(str(self.db_path), check_same_thread=False)
|
||||
self.conn.row_factory = sqlite3.Row
|
||||
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
# 创建实体表
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS entities (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
type TEXT,
|
||||
mention_count INTEGER DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
|
||||
# 创建关系表
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS relations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
source_id INTEGER NOT NULL,
|
||||
target_id INTEGER NOT NULL,
|
||||
relation_type TEXT NOT NULL,
|
||||
confidence REAL DEFAULT 1.0,
|
||||
status TEXT DEFAULT 'active',
|
||||
session_id TEXT,
|
||||
turn_id INTEGER,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
date_bucket TEXT,
|
||||
superseded_by INTEGER,
|
||||
FOREIGN KEY (source_id) REFERENCES entities(id),
|
||||
FOREIGN KEY (target_id) REFERENCES entities(id)
|
||||
)
|
||||
""")
|
||||
|
||||
# 创建索引
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_entity_name ON entities(name)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_entity_type ON entities(type)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_relation_source ON relations(source_id)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_relation_target ON relations(target_id)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_relation_type ON relations(relation_type)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_relation_status ON relations(status)")
|
||||
|
||||
self.conn.commit()
|
||||
|
||||
def ensure_constraints(self):
|
||||
"""确保约束(兼容Neo4j接口)"""
|
||||
pass # SQLite自动处理
|
||||
|
||||
def recall(self, query_intent: str, seed_entities: List[str] = None,
|
||||
depth: int = 2, time_range: Dict = None,
|
||||
session_filter: str = None) -> Dict:
|
||||
"""
|
||||
检索相关记忆
|
||||
|
||||
Args:
|
||||
query_intent: 查询关键词(逗号分隔)
|
||||
seed_entities: 种子实体
|
||||
depth: 搜索深度
|
||||
time_range: 时间范围
|
||||
session_filter: 会话过滤
|
||||
|
||||
Returns:
|
||||
检索结果
|
||||
"""
|
||||
keywords = [w.strip().lower() for w in query_intent.replace(',', ' ').split() if w.strip()]
|
||||
|
||||
if not keywords and not seed_entities:
|
||||
return {"entities": [], "relations": [], "message": "无查询关键词"}
|
||||
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
# 搜索实体
|
||||
entities = []
|
||||
entity_ids = set()
|
||||
|
||||
for keyword in keywords:
|
||||
cursor.execute("""
|
||||
SELECT id, name, type, mention_count
|
||||
FROM entities
|
||||
WHERE LOWER(name) LIKE ?
|
||||
""", (f"%{keyword}%",))
|
||||
|
||||
for row in cursor.fetchall():
|
||||
if row['id'] not in entity_ids:
|
||||
entity_ids.add(row['id'])
|
||||
entities.append({
|
||||
'name': row['name'],
|
||||
'type': row['type'] or 'unknown',
|
||||
'mention_count': row['mention_count']
|
||||
})
|
||||
|
||||
# 搜索关系
|
||||
relations = []
|
||||
|
||||
if entity_ids:
|
||||
placeholders = ','.join('?' * len(entity_ids))
|
||||
|
||||
query = f"""
|
||||
SELECT r.id, e1.name as source, e2.name as target,
|
||||
r.relation_type as type, r.confidence, r.session_id,
|
||||
r.turn_id, r.created_at, r.status
|
||||
FROM relations r
|
||||
JOIN entities e1 ON r.source_id = e1.id
|
||||
JOIN entities e2 ON r.target_id = e2.id
|
||||
WHERE (r.source_id IN ({placeholders}) OR r.target_id IN ({placeholders}))
|
||||
AND r.status = 'active'
|
||||
"""
|
||||
|
||||
params = list(entity_ids) + list(entity_ids)
|
||||
|
||||
if session_filter:
|
||||
query += " AND r.session_id = ?"
|
||||
params.append(session_filter)
|
||||
|
||||
cursor.execute(query, params)
|
||||
|
||||
for row in cursor.fetchall():
|
||||
relations.append({
|
||||
'source': row['source'],
|
||||
'target': row['target'],
|
||||
'type': row['type'],
|
||||
'confidence': row['confidence'],
|
||||
'session_id': row['session_id'],
|
||||
'turn_id': row['turn_id'],
|
||||
'created_at': row['created_at'],
|
||||
'status': row['status']
|
||||
})
|
||||
|
||||
return {
|
||||
"entities": entities,
|
||||
"relations": relations,
|
||||
"message": f"找到 {len(entities)} 个实体, {len(relations)} 条关系"
|
||||
}
|
||||
|
||||
def commit(self, triplets: List[Dict], entity_types: Dict = None,
|
||||
temporal_tag: str = None, session_id: str = None,
|
||||
turn_id: int = None) -> Dict:
|
||||
"""
|
||||
写入记忆
|
||||
|
||||
Args:
|
||||
triplets: 三元组列表
|
||||
entity_types: 实体类型
|
||||
temporal_tag: 时间标签
|
||||
session_id: 会话ID
|
||||
turn_id: 轮次ID
|
||||
|
||||
Returns:
|
||||
写入结果
|
||||
"""
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
created_entities = 0
|
||||
created_relations = 0
|
||||
|
||||
for triplet in triplets:
|
||||
subject = triplet.get('subject')
|
||||
relation = triplet.get('relation')
|
||||
obj = triplet.get('object')
|
||||
confidence = triplet.get('confidence', 1.0)
|
||||
|
||||
if not all([subject, relation, obj]):
|
||||
continue
|
||||
|
||||
# 创建或更新实体
|
||||
for entity_name in [subject, obj]:
|
||||
entity_type = entity_types.get(entity_name) if entity_types else None
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO entities (name, type)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT(name) DO UPDATE SET
|
||||
mention_count = mention_count + 1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
""", (entity_name, entity_type))
|
||||
|
||||
if cursor.rowcount > 0:
|
||||
created_entities += 1
|
||||
|
||||
# 获取实体ID
|
||||
cursor.execute("SELECT id FROM entities WHERE name = ?", (subject,))
|
||||
source_id = cursor.fetchone()['id']
|
||||
|
||||
cursor.execute("SELECT id FROM entities WHERE name = ?", (obj,))
|
||||
target_id = cursor.fetchone()['id']
|
||||
|
||||
# 创建关系
|
||||
date_bucket = datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO relations (
|
||||
source_id, target_id, relation_type, confidence,
|
||||
session_id, turn_id, date_bucket
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""", (source_id, target_id, relation, confidence,
|
||||
session_id, turn_id, date_bucket))
|
||||
|
||||
created_relations += 1
|
||||
|
||||
self.conn.commit()
|
||||
|
||||
return {
|
||||
"created_entities": created_entities,
|
||||
"created_relations": created_relations,
|
||||
"message": f"创建了 {created_entities} 个实体, {created_relations} 条关系"
|
||||
}
|
||||
|
||||
def purge(self, criteria: Dict, mode: str = "soft",
|
||||
new_relation: Dict = None) -> Dict:
|
||||
"""
|
||||
删除或修正记忆
|
||||
|
||||
Args:
|
||||
criteria: 删除条件
|
||||
mode: 删除模式 (soft/hard)
|
||||
new_relation: 替代关系
|
||||
|
||||
Returns:
|
||||
删除结果
|
||||
"""
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
# 构建查询条件
|
||||
conditions = []
|
||||
params = []
|
||||
|
||||
if criteria.get('source'):
|
||||
cursor.execute("SELECT id FROM entities WHERE name = ?", (criteria['source'],))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
conditions.append("source_id = ?")
|
||||
params.append(row['id'])
|
||||
|
||||
if criteria.get('target'):
|
||||
cursor.execute("SELECT id FROM entities WHERE name = ?", (criteria['target'],))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
conditions.append("target_id = ?")
|
||||
params.append(row['id'])
|
||||
|
||||
if criteria.get('relation'):
|
||||
conditions.append("relation_type = ?")
|
||||
params.append(criteria['relation'])
|
||||
|
||||
if not conditions:
|
||||
return {"deleted": 0, "message": "无删除条件"}
|
||||
|
||||
where_clause = " AND ".join(conditions)
|
||||
|
||||
if mode == "soft":
|
||||
cursor.execute(f"""
|
||||
UPDATE relations
|
||||
SET status = 'deleted', updated_at = CURRENT_TIMESTAMP
|
||||
WHERE {where_clause} AND status = 'active'
|
||||
""", params)
|
||||
else:
|
||||
cursor.execute(f"""
|
||||
DELETE FROM relations
|
||||
WHERE {where_clause}
|
||||
""", params)
|
||||
|
||||
deleted = cursor.rowcount
|
||||
self.conn.commit()
|
||||
|
||||
return {
|
||||
"deleted": deleted,
|
||||
"mode": mode,
|
||||
"message": f"删除了 {deleted} 条关系"
|
||||
}
|
||||
|
||||
def introspect(self, session_id: str = None) -> Dict:
|
||||
"""
|
||||
查看会话状态
|
||||
|
||||
Args:
|
||||
session_id: 会话ID
|
||||
|
||||
Returns:
|
||||
会话状态
|
||||
"""
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
# 统计实体
|
||||
cursor.execute("SELECT COUNT(*) as count FROM entities")
|
||||
entity_count = cursor.fetchone()['count']
|
||||
|
||||
# 统计关系
|
||||
cursor.execute("SELECT COUNT(*) as count FROM relations WHERE status = 'active'")
|
||||
relation_count = cursor.fetchone()['count']
|
||||
|
||||
return {
|
||||
"entity_count": entity_count,
|
||||
"relation_count": relation_count,
|
||||
"session_id": session_id,
|
||||
"message": f"数据库包含 {entity_count} 个实体, {relation_count} 条关系"
|
||||
}
|
||||
|
||||
def archive(self, days: int = 30) -> Dict:
|
||||
"""归档旧关系"""
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
UPDATE relations
|
||||
SET status = 'archived', updated_at = CURRENT_TIMESTAMP
|
||||
WHERE status = 'active'
|
||||
AND created_at < datetime('now', ?)
|
||||
""", (f'-{days} days',))
|
||||
|
||||
archived = cursor.rowcount
|
||||
self.conn.commit()
|
||||
|
||||
return {
|
||||
"archived": archived,
|
||||
"message": f"归档了 {archived} 条关系"
|
||||
}
|
||||
|
||||
def cleanup(self, dry_run: bool = True) -> Dict:
|
||||
"""清理已删除数据"""
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
if dry_run:
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) as count
|
||||
FROM relations
|
||||
WHERE status = 'deleted'
|
||||
AND updated_at < datetime('now', '-90 days')
|
||||
""")
|
||||
deleted_relations = cursor.fetchone()['count']
|
||||
|
||||
return {
|
||||
"dry_run": True,
|
||||
"deleted_relations": deleted_relations,
|
||||
"message": f"将删除 {deleted_relations} 条关系"
|
||||
}
|
||||
else:
|
||||
cursor.execute("""
|
||||
DELETE FROM relations
|
||||
WHERE status = 'deleted'
|
||||
AND updated_at < datetime('now', '-90 days')
|
||||
""")
|
||||
deleted = cursor.rowcount
|
||||
self.conn.commit()
|
||||
|
||||
return {
|
||||
"dry_run": False,
|
||||
"deleted": deleted,
|
||||
"message": f"删除了 {deleted} 条关系"
|
||||
}
|
||||
|
||||
def close(self):
|
||||
"""关闭数据库连接"""
|
||||
if self.conn:
|
||||
self.conn.close()
|
||||
self.conn = None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
|
||||
|
||||
# 兼容性别名
|
||||
Neo4jGraph = EmbeddedGraphDB
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 测试
|
||||
print("Testing Embedded Graph Database...")
|
||||
|
||||
with EmbeddedGraphDB("test.db") as db:
|
||||
# 写入测试
|
||||
result = db.commit(
|
||||
triplets=[
|
||||
{"subject": "用户", "relation": "喜欢", "object": "Python"},
|
||||
{"subject": "用户", "relation": "学习", "object": "AI"}
|
||||
],
|
||||
session_id="test-session",
|
||||
turn_id=1
|
||||
)
|
||||
print(f"Commit: {result}")
|
||||
|
||||
# 检索测试
|
||||
result = db.recall("Python,AI")
|
||||
print(f"Recall: {result}")
|
||||
|
||||
# 状态测试
|
||||
result = db.introspect()
|
||||
print(f"Introspect: {result}")
|
||||
|
||||
print("\nTest completed!")
|
||||
58
graph_memory_tui/core/imports.py
Normal file
58
graph_memory_tui/core/imports.py
Normal file
@ -0,0 +1,58 @@
|
||||
"""
|
||||
核心逻辑导入 - 优先使用内嵌数据库
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到路径
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
if str(project_root) not in sys.path:
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 环境变量
|
||||
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY", "")
|
||||
DEEPSEEK_BASE_URL = os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com")
|
||||
MODEL_NAME = os.getenv("MODEL_NAME", "deepseek-chat")
|
||||
|
||||
# 数据库配置
|
||||
NEO4J_URI = os.getenv("NEO4J_URI", "bolt://localhost:7687")
|
||||
NEO4J_USER = os.getenv("NEO4J_USER", "neo4j")
|
||||
NEO4J_PASSWORD = os.getenv("NEO4J_PASSWORD", "graphmemory123")
|
||||
|
||||
# 优先使用内嵌数据库
|
||||
USE_EMBEDDED_DB = os.getenv("USE_EMBEDDED_DB", "true").lower() == "true"
|
||||
|
||||
if USE_EMBEDDED_DB:
|
||||
# 使用内嵌SQLite数据库
|
||||
from .embedded_db import EmbeddedGraphDB as Neo4jGraph
|
||||
print("[INFO] Using embedded SQLite database (no Docker needed)")
|
||||
else:
|
||||
# 使用Neo4j数据库
|
||||
try:
|
||||
from graph_memory_demo import Neo4jGraph
|
||||
print("[INFO] Using Neo4j database")
|
||||
except ImportError:
|
||||
from .embedded_db import EmbeddedGraphDB as Neo4jGraph
|
||||
print("[INFO] Fallback to embedded SQLite database")
|
||||
|
||||
# 导入其他组件
|
||||
from graph_memory_demo import (
|
||||
GraphMemoryClient,
|
||||
TOOLS,
|
||||
execute_tool,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Neo4jGraph",
|
||||
"GraphMemoryClient",
|
||||
"TOOLS",
|
||||
"execute_tool",
|
||||
"DEEPSEEK_API_KEY",
|
||||
"DEEPSEEK_BASE_URL",
|
||||
"MODEL_NAME",
|
||||
"NEO4J_URI",
|
||||
"NEO4J_USER",
|
||||
"NEO4J_PASSWORD",
|
||||
]
|
||||
199
graph_memory_tui/core/optimized_operations.py
Normal file
199
graph_memory_tui/core/optimized_operations.py
Normal file
@ -0,0 +1,199 @@
|
||||
"""
|
||||
优化版图数据库操作和提示词
|
||||
"""
|
||||
|
||||
# 优化后的系统提示词
|
||||
OPTIMIZED_SYSTEM_PROMPT = """你是图数据库记忆助手。
|
||||
|
||||
## 核心职责
|
||||
你是用户的长期记忆助手。每次对话后,你**必须**主动决定是否需要将关键信息写入记忆图库。
|
||||
|
||||
## 多轮查询策略
|
||||
**允许多轮查询**,但必须遵循以下规则:
|
||||
|
||||
1. **渐进式查询**:每轮查询应该基于上一轮的结果,缩小或扩大范围
|
||||
- 第一轮:广泛搜索,使用多个同义词
|
||||
- 第二轮:基于第一轮结果,精确搜索
|
||||
- 第三轮:如果仍未找到,尝试相关概念
|
||||
|
||||
2. **禁止重复查询**:
|
||||
- ❌ 禁止:使用相同的 query_intent 连续查询
|
||||
- ❌ 禁止:查询后立即用相同关键词再查
|
||||
- ✅ 允许:第一轮查"鸿蒙",第二轮查"鸿蒙,工具链,IDE"
|
||||
|
||||
3. **查询历史追踪**:
|
||||
- 记住已经查询过的关键词
|
||||
- 每次新查询必须使用不同的关键词组合
|
||||
- 如果3轮查询仍未找到,告知用户"未找到相关记忆"
|
||||
|
||||
## memory_recall 正确用法
|
||||
|
||||
### 关键词提取规则
|
||||
query_intent 应该是**逗号分隔的多个关键词**,包含**同义词/近义词**:
|
||||
|
||||
```json
|
||||
{
|
||||
"query_intent": "鸿蒙,harmony,工具链,toolchain,开发环境,IDE",
|
||||
"depth": 2
|
||||
}
|
||||
```
|
||||
|
||||
### 搜索范围
|
||||
- 实体名称(subject/target)
|
||||
- 关系类型(relation)
|
||||
- 实体类型(entity type)
|
||||
|
||||
### 同义词扩展示例
|
||||
- "鸿蒙" → "鸿蒙,harmony,openharmony,华为"
|
||||
- "工具链" → "工具链,toolchain,sdk,开发环境,IDE"
|
||||
- "项目" → "项目,project,工程,工作"
|
||||
- "学习" → "学习,learn,study,掌握,了解"
|
||||
|
||||
## 重要规则:必须写入记忆的情况
|
||||
当用户提到以下内容时,你**必须**调用 memory_commit 写入记忆:
|
||||
1. 用户的**偏好**("我喜欢X")
|
||||
2. 用户的**项目**("我在做X项目")
|
||||
3. 用户的**学习内容**("我在学Python")
|
||||
4. 讨论的**主题**("量子力学")
|
||||
5. 用户的**计划**("我打算X")
|
||||
6. 用户的**状态**("我现在在X")
|
||||
|
||||
## 区分事实与猜测
|
||||
当基于记忆检索结果回复时,**必须**使用"应该"标注你的推理:
|
||||
- ✅ 正确: "根据记忆,你的鸿蒙工具链**应该**在 opt 目录下"
|
||||
- ❌ 错误: "你的鸿蒙工具链在 opt 目录下"(没有标注"应该")
|
||||
|
||||
原因:数据库中的记录可能不完整或已过期,你需要标注这是**推断**而非**确认**的事实
|
||||
|
||||
## 可用工具
|
||||
1. **memory_recall** - 检索历史记忆
|
||||
- query_intent: 支持逗号分隔的多关键词
|
||||
- depth: 搜索深度(1-3)
|
||||
- seed_entities: 种子实体(可选)
|
||||
- time_range: 时间范围(可选)
|
||||
|
||||
2. **memory_commit** - 写入记忆(三元组格式)
|
||||
- triplets: [{"subject": "A", "relation": "关系", "object": "B"}]
|
||||
- entity_types: 实体类型标注(可选)
|
||||
- temporal_tag: 时间标签(可选)
|
||||
|
||||
3. **memory_purge** - 修正/删除记忆
|
||||
- criteria: 删除条件
|
||||
- mode: "soft" 或 "hard"
|
||||
|
||||
4. **memory_introspect** - 查看会话状态
|
||||
|
||||
## 错误策略(禁止)
|
||||
- ❌ query_intent 使用完整句子
|
||||
- ❌ 连续使用相同的 query_intent 查询
|
||||
- ❌ 查询后立即用相同关键词再查
|
||||
- ❌ 超过3轮查询仍未找到结果时继续查询
|
||||
|
||||
## 查询示例
|
||||
|
||||
### 正确的多轮查询
|
||||
```
|
||||
用户: 我的鸿蒙开发环境在哪?
|
||||
|
||||
第一轮查询:
|
||||
{
|
||||
"query_intent": "鸿蒙,harmony,开发环境,IDE,工具链",
|
||||
"depth": 2
|
||||
}
|
||||
|
||||
如果未找到,第二轮查询:
|
||||
{
|
||||
"query_intent": "鸿蒙,harmony,安装路径,目录,位置",
|
||||
"depth": 1
|
||||
}
|
||||
|
||||
如果仍未找到,告知用户并询问是否需要记录。
|
||||
```
|
||||
|
||||
### 错误的重复查询
|
||||
```
|
||||
❌ 第一轮: {"query_intent": "鸿蒙"}
|
||||
❌ 第二轮: {"query_intent": "鸿蒙"} // 禁止重复!
|
||||
```
|
||||
|
||||
现在开始对话!"""
|
||||
|
||||
|
||||
# 优化的图数据库操作
|
||||
class OptimizedNeo4jGraph:
|
||||
"""优化版Neo4j图数据库操作"""
|
||||
|
||||
@staticmethod
|
||||
def optimize_recall_query(keywords: list, previous_queries: list = None) -> str:
|
||||
"""
|
||||
优化recall查询关键词
|
||||
|
||||
Args:
|
||||
keywords: 当前关键词列表
|
||||
previous_queries: 之前查询过的关键词列表
|
||||
|
||||
Returns:
|
||||
优化后的query_intent
|
||||
"""
|
||||
# 去重
|
||||
unique_keywords = list(set(keywords))
|
||||
|
||||
# 如果有之前的查询,避免重复
|
||||
if previous_queries:
|
||||
# 展开之前查询的所有关键词
|
||||
previous_keywords = set()
|
||||
for pq in previous_queries:
|
||||
previous_keywords.update(pq.split(','))
|
||||
|
||||
# 只保留新关键词
|
||||
new_keywords = [k for k in unique_keywords if k not in previous_keywords]
|
||||
|
||||
# 如果没有新关键词,添加相关概念
|
||||
if not new_keywords:
|
||||
# 添加相关概念扩展
|
||||
related_concepts = OptimizedNeo4jGraph._get_related_concepts(unique_keywords)
|
||||
unique_keywords.extend(related_concepts)
|
||||
|
||||
return ','.join(unique_keywords)
|
||||
|
||||
@staticmethod
|
||||
def _get_related_concepts(keywords: list) -> list:
|
||||
"""获取相关概念"""
|
||||
concept_map = {
|
||||
'鸿蒙': ['harmony', 'openharmony', '华为', 'HMS'],
|
||||
'工具链': ['toolchain', 'sdk', 'IDE', '开发环境'],
|
||||
'项目': ['project', '工程', '工作', '任务'],
|
||||
'学习': ['learn', 'study', '掌握', '了解', '教程'],
|
||||
'偏好': ['喜欢', 'preference', '习惯', '倾向'],
|
||||
'位置': ['路径', 'path', '目录', 'directory', '在哪'],
|
||||
}
|
||||
|
||||
related = []
|
||||
for kw in keywords:
|
||||
for key, values in concept_map.items():
|
||||
if key in kw.lower() or kw.lower() in key:
|
||||
related.extend(values)
|
||||
|
||||
return list(set(related))
|
||||
|
||||
@staticmethod
|
||||
def should_continue_query(query_count: int, found_results: bool) -> bool:
|
||||
"""
|
||||
判断是否应该继续查询
|
||||
|
||||
Args:
|
||||
query_count: 已查询次数
|
||||
found_results: 是否找到结果
|
||||
|
||||
Returns:
|
||||
是否应该继续查询
|
||||
"""
|
||||
# 如果已找到结果,不再查询
|
||||
if found_results:
|
||||
return False
|
||||
|
||||
# 最多查询3次
|
||||
if query_count >= 3:
|
||||
return False
|
||||
|
||||
return True
|
||||
Reference in New Issue
Block a user