mirror of
https://gitcode.com/JianFeeeee/TrulyMEM-TrueHumanMEM.git
synced 2026-10-04 00:14:26 +00:00
feat: 添加聊天记录持久化功能 - 启动时从数据库加载历史,消息处理后自动保存
This commit is contained in:
@ -71,6 +71,19 @@ class EmbeddedGraphDB:
|
|||||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_relation_type ON relations(relation_type)")
|
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)")
|
cursor.execute("CREATE INDEX IF NOT EXISTS idx_relation_status ON relations(status)")
|
||||||
|
|
||||||
|
# 创建聊天记录表
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS chat_records (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
role TEXT NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# 创建聊天记录索引
|
||||||
|
cursor.execute("CREATE INDEX IF NOT EXISTS idx_chat_created ON chat_records(created_at)")
|
||||||
|
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
|
||||||
def ensure_constraints(self):
|
def ensure_constraints(self):
|
||||||
@ -394,6 +407,44 @@ class EmbeddedGraphDB:
|
|||||||
"message": f"删除了 {deleted} 条关系"
|
"message": f"删除了 {deleted} 条关系"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def save_chat_records(self, messages: list) -> Dict:
|
||||||
|
"""保存聊天记录到数据库"""
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
|
||||||
|
saved = 0
|
||||||
|
for msg in messages:
|
||||||
|
role = msg.get("role")
|
||||||
|
content = msg.get("content")
|
||||||
|
if role and content:
|
||||||
|
cursor.execute(
|
||||||
|
"INSERT INTO chat_records (role, content) VALUES (?, ?)",
|
||||||
|
(role, content)
|
||||||
|
)
|
||||||
|
saved += 1
|
||||||
|
|
||||||
|
self.conn.commit()
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
DELETE FROM chat_records
|
||||||
|
WHERE id NOT IN (
|
||||||
|
SELECT id FROM chat_records
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT 500
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
self.conn.commit()
|
||||||
|
|
||||||
|
return {"saved": saved}
|
||||||
|
|
||||||
|
def get_chat_records(self, limit: int = 500) -> list:
|
||||||
|
"""从数据库获取聊天记录"""
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT role, content FROM chat_records
|
||||||
|
ORDER BY id ASC LIMIT ?
|
||||||
|
""", (limit,))
|
||||||
|
return [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""关闭数据库连接"""
|
"""关闭数据库连接"""
|
||||||
if self.conn:
|
if self.conn:
|
||||||
|
|||||||
@ -15,10 +15,8 @@ class PacketType(Enum):
|
|||||||
PROCESS_MESSAGE = "process_message"
|
PROCESS_MESSAGE = "process_message"
|
||||||
EXECUTE_TOOL = "execute_tool"
|
EXECUTE_TOOL = "execute_tool"
|
||||||
GET_STATUS = "get_status"
|
GET_STATUS = "get_status"
|
||||||
GET_CONFIG = "get_config"
|
GET_SETTINGS = "get_settings" # 合并:获取 api_config + tool_limits
|
||||||
SET_CONFIG = "set_config"
|
SET_SETTINGS = "set_settings" # 合并:设置 api_config + tool_limits
|
||||||
GET_TOOL_LIMITS = "get_tool_limits"
|
|
||||||
SET_TOOL_LIMITS = "set_tool_limits"
|
|
||||||
GET_HISTORY = "get_history"
|
GET_HISTORY = "get_history"
|
||||||
SAVE_HISTORY = "save_history"
|
SAVE_HISTORY = "save_history"
|
||||||
SHUTDOWN = "shutdown"
|
SHUTDOWN = "shutdown"
|
||||||
@ -160,14 +158,10 @@ class BackendServer:
|
|||||||
response_body = self._handle_execute_tool(packet.body)
|
response_body = self._handle_execute_tool(packet.body)
|
||||||
elif packet.type == PacketType.GET_STATUS:
|
elif packet.type == PacketType.GET_STATUS:
|
||||||
response_body = self._handle_get_status()
|
response_body = self._handle_get_status()
|
||||||
elif packet.type == PacketType.GET_CONFIG:
|
elif packet.type == PacketType.GET_SETTINGS:
|
||||||
response_body = self._handle_get_config()
|
response_body = self._handle_get_settings()
|
||||||
elif packet.type == PacketType.SET_CONFIG:
|
elif packet.type == PacketType.SET_SETTINGS:
|
||||||
response_body = self._handle_set_config(packet.body)
|
response_body = self._handle_set_settings(packet.body)
|
||||||
elif packet.type == PacketType.GET_TOOL_LIMITS:
|
|
||||||
response_body = self._handle_get_tool_limits()
|
|
||||||
elif packet.type == PacketType.SET_TOOL_LIMITS:
|
|
||||||
response_body = self._handle_set_tool_limits(packet.body)
|
|
||||||
elif packet.type == PacketType.GET_HISTORY:
|
elif packet.type == PacketType.GET_HISTORY:
|
||||||
response_body = self._handle_get_history()
|
response_body = self._handle_get_history()
|
||||||
elif packet.type == PacketType.SAVE_HISTORY:
|
elif packet.type == PacketType.SAVE_HISTORY:
|
||||||
@ -197,6 +191,8 @@ class BackendServer:
|
|||||||
if not self._client:
|
if not self._client:
|
||||||
return {"success": False, "error": "API Key 未配置", "content": "请先配置 API Key"}
|
return {"success": False, "error": "API Key 未配置", "content": "请先配置 API Key"}
|
||||||
|
|
||||||
|
self._graph.save_chat_records([{"role": "user", "content": user_input}])
|
||||||
|
|
||||||
self._tool_limiter.reset()
|
self._tool_limiter.reset()
|
||||||
|
|
||||||
messages_history = [{"role": "user", "content": user_input}]
|
messages_history = [{"role": "user", "content": user_input}]
|
||||||
@ -281,6 +277,8 @@ class BackendServer:
|
|||||||
content += f"\n\n部分工具调用被限制:\n{rejected_info}"
|
content += f"\n\n部分工具调用被限制:\n{rejected_info}"
|
||||||
content += f"\n\n工具调用统计:\n{self._tool_limiter.get_summary()}"
|
content += f"\n\n工具调用统计:\n{self._tool_limiter.get_summary()}"
|
||||||
|
|
||||||
|
self._graph.save_chat_records([{"role": "assistant", "content": content}])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"content": content,
|
"content": content,
|
||||||
@ -309,45 +307,45 @@ class BackendServer:
|
|||||||
"client_initialized": self._client is not None
|
"client_initialized": self._client is not None
|
||||||
}
|
}
|
||||||
|
|
||||||
def _handle_get_config(self) -> Dict:
|
def _handle_get_settings(self) -> Dict:
|
||||||
return self._config.copy()
|
return {
|
||||||
|
"api_config": self._config.copy(),
|
||||||
|
"tool_limits": self._tool_limits.copy()
|
||||||
|
}
|
||||||
|
|
||||||
def _handle_set_config(self, body: Dict) -> Dict:
|
def _handle_set_settings(self, body: Dict) -> Dict:
|
||||||
api_key = body.get("api_key", "")
|
api_config = body.get("api_config", {})
|
||||||
base_url = body.get("base_url", "https://api.deepseek.com")
|
tool_limits = body.get("tool_limits", {})
|
||||||
model = body.get("model", "deepseek-chat")
|
|
||||||
|
api_key = api_config.get("api_key", "")
|
||||||
|
base_url = api_config.get("base_url", "https://api.deepseek.com")
|
||||||
|
model = api_config.get("model", "deepseek-chat")
|
||||||
|
|
||||||
self.update_config(api_key, base_url, model)
|
self.update_config(api_key, base_url, model)
|
||||||
self._save_config()
|
|
||||||
return {"status": "config_updated"}
|
|
||||||
|
|
||||||
def _handle_get_tool_limits(self) -> Dict:
|
|
||||||
return self._tool_limits.copy()
|
|
||||||
|
|
||||||
def _handle_set_tool_limits(self, body: Dict) -> Dict:
|
|
||||||
limits_keys = [
|
limits_keys = [
|
||||||
"persona_query_max", "persona_update_max",
|
"persona_query_max", "persona_update_max",
|
||||||
"task_query_max", "task_update_max",
|
"task_query_max", "task_update_max",
|
||||||
"memory_query_max", "memory_update_max"
|
"memory_query_max", "memory_update_max"
|
||||||
]
|
]
|
||||||
for key in limits_keys:
|
for key in limits_keys:
|
||||||
if key in body:
|
if key in tool_limits:
|
||||||
value = int(body[key])
|
value = int(tool_limits[key])
|
||||||
if value < 1:
|
if value < 1:
|
||||||
return {"success": False, "error": f"{key} must be >= 1, got {value}"}
|
return {"success": False, "error": f"{key} must be >= 1, got {value}"}
|
||||||
self._tool_limits[key] = value
|
self._tool_limits[key] = value
|
||||||
|
|
||||||
self._tool_limiter = self._create_tool_limiter()
|
self._tool_limiter = self._create_tool_limiter()
|
||||||
|
|
||||||
self._save_config()
|
self._save_config()
|
||||||
return {"status": "tool_limits_updated", "limits": self._tool_limits.copy()}
|
return {"status": "settings_updated"}
|
||||||
|
|
||||||
def _handle_get_history(self) -> Dict:
|
def _handle_get_history(self) -> Dict:
|
||||||
return {"history": self._message_history}
|
history = self._graph.get_chat_records(limit=500)
|
||||||
|
return {"history": history}
|
||||||
|
|
||||||
def _handle_save_history(self, body: Dict) -> Dict:
|
def _handle_save_history(self, body: Dict) -> Dict:
|
||||||
messages = body.get("messages", [])
|
messages = body.get("messages", [])
|
||||||
self._message_history = messages
|
result = self._graph.save_chat_records(messages)
|
||||||
return {"status": "history_saved"}
|
return {"status": "history_saved"}
|
||||||
|
|
||||||
def _send_response(self, request_id: str, response: PacketResponse) -> None:
|
def _send_response(self, request_id: str, response: PacketResponse) -> None:
|
||||||
|
|||||||
158
docs/en/api.md
158
docs/en/api.md
@ -25,10 +25,8 @@ class PacketType(Enum):
|
|||||||
PROCESS_MESSAGE = "process_message" # Process message
|
PROCESS_MESSAGE = "process_message" # Process message
|
||||||
EXECUTE_TOOL = "execute_tool" # Execute tool
|
EXECUTE_TOOL = "execute_tool" # Execute tool
|
||||||
GET_STATUS = "get_status" # Get status
|
GET_STATUS = "get_status" # Get status
|
||||||
GET_CONFIG = "get_config" # Get config
|
GET_SETTINGS = "get_settings" # Get all settings (api_config + tool_limits)
|
||||||
SET_CONFIG = "set_config" # Set config
|
SET_SETTINGS = "set_settings" # Set all settings (api_config + tool_limits)
|
||||||
GET_TOOL_LIMITS = "get_tool_limits" # Get tool limits
|
|
||||||
SET_TOOL_LIMITS = "set_tool_limits" # Set tool limits
|
|
||||||
GET_HISTORY = "get_history" # Get history
|
GET_HISTORY = "get_history" # Get history
|
||||||
SAVE_HISTORY = "save_history" # Save history
|
SAVE_HISTORY = "save_history" # Save history
|
||||||
SHUTDOWN = "shutdown" # Shutdown service
|
SHUTDOWN = "shutdown" # Shutdown service
|
||||||
@ -160,9 +158,9 @@ body = {} # No parameters
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4. GET_CONFIG - Get Config
|
### 4. GET_SETTINGS - Get All Settings
|
||||||
|
|
||||||
Get current API configuration.
|
Get current API config and tool limits (all at once).
|
||||||
|
|
||||||
**Request parameters:**
|
**Request parameters:**
|
||||||
```python
|
```python
|
||||||
@ -172,103 +170,82 @@ body = {} # No parameters
|
|||||||
**Response data:**
|
**Response data:**
|
||||||
```python
|
```python
|
||||||
{
|
{
|
||||||
"api_key": str, # API Key
|
"api_config": {
|
||||||
"base_url": str, # API Base URL
|
"api_key": str, # API Key
|
||||||
"model": str # Model name
|
"base_url": str, # API Base URL
|
||||||
|
"model": str # Model name
|
||||||
|
},
|
||||||
|
"tool_limits": {
|
||||||
|
"persona_query_max": int, # Persona graph query limit
|
||||||
|
"persona_update_max": int, # Persona graph update limit
|
||||||
|
"task_query_max": int, # Working memory query limit
|
||||||
|
"task_update_max": int, # Working memory update limit
|
||||||
|
"memory_query_max": int, # General memory query limit
|
||||||
|
"memory_update_max": int # General memory update limit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```python
|
||||||
|
result = client.get_settings()
|
||||||
|
api_config = result["data"]["api_config"]
|
||||||
|
tool_limits = result["data"]["tool_limits"]
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 5. SET_CONFIG - Set Config
|
### 5. SET_SETTINGS - Set All Settings
|
||||||
|
|
||||||
Update API configuration (API Key and Base URL).
|
Update API config and tool limits (all at once).
|
||||||
|
|
||||||
**Request parameters:**
|
**Request parameters:**
|
||||||
```python
|
```python
|
||||||
body = {
|
body = {
|
||||||
"api_key": str, # API Key
|
"api_config": {
|
||||||
"base_url": str, # API Base URL (default: https://api.deepseek.com)
|
"api_key": str, # API Key
|
||||||
"model": str # Model name (default: deepseek-chat)
|
"base_url": str, # API Base URL (default: https://api.deepseek.com)
|
||||||
|
"model": str # Model name (default: deepseek-chat)
|
||||||
|
},
|
||||||
|
"tool_limits": {
|
||||||
|
"persona_query_max": int, # Persona query limit (≥1)
|
||||||
|
"persona_update_max": int, # Persona update limit (≥1)
|
||||||
|
"task_query_max": int, # Working memory query limit (≥1)
|
||||||
|
"task_update_max": int, # Working memory update limit (≥1)
|
||||||
|
"memory_query_max": int, # General memory query limit (≥1)
|
||||||
|
"memory_update_max": int # General memory update limit (≥1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Response data:**
|
**Response data:**
|
||||||
```python
|
```python
|
||||||
{
|
{
|
||||||
"status": "config_updated"
|
"status": "settings_updated"
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. GET_TOOL_LIMITS - Get Tool Limits
|
|
||||||
|
|
||||||
Get current AI reasoning tool call limits.
|
|
||||||
|
|
||||||
**Request Parameters:**
|
|
||||||
```python
|
|
||||||
body = {} # No parameters
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response Data:**
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"persona_query_max": int, # Persona graph query limit
|
|
||||||
"persona_update_max": int, # Persona graph update limit
|
|
||||||
"task_query_max": int, # Working memory query limit
|
|
||||||
"task_update_max": int, # Working memory update limit
|
|
||||||
"memory_query_max": int, # General memory query limit
|
|
||||||
"memory_update_max": int # General memory update limit
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```python
|
```python
|
||||||
result = client.get_tool_limits()
|
result = client.update_settings(
|
||||||
print(result["data"]["persona_query_max"]) # 1
|
api_config={
|
||||||
```
|
"api_key": "sk-xxxxx",
|
||||||
|
"base_url": "https://api.deepseek.com",
|
||||||
---
|
"model": "deepseek-chat"
|
||||||
|
},
|
||||||
### 7. SET_TOOL_LIMITS - Set Tool Limits
|
tool_limits={
|
||||||
|
"persona_query_max": 2,
|
||||||
Set AI reasoning tool call limits.
|
"task_query_max": 5,
|
||||||
|
"memory_query_max": 30
|
||||||
**Request Parameters:**
|
}
|
||||||
```python
|
|
||||||
body = {
|
|
||||||
"persona_query_max": int, # Persona query limit (≥1)
|
|
||||||
"persona_update_max": int, # Persona update limit (≥1)
|
|
||||||
"task_query_max": int, # Working memory query limit (≥1)
|
|
||||||
"task_update_max": int, # Working memory update limit (≥1)
|
|
||||||
"memory_query_max": int, # General memory query limit (≥1)
|
|
||||||
"memory_update_max": int # General memory update limit (≥1)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response Data:**
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"status": "tool_limits_updated",
|
|
||||||
"limits": {...} # Updated limits
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```python
|
|
||||||
result = client.update_tool_limits(
|
|
||||||
persona_query_max=2,
|
|
||||||
task_query_max=5,
|
|
||||||
memory_query_max=30
|
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 8. GET_HISTORY - Get Message History
|
### 6. GET_HISTORY - Get Message History
|
||||||
|
|
||||||
Get saved message history.
|
Get saved message history (from database, for UI display only, not used in model inference).
|
||||||
|
|
||||||
**Request parameters:**
|
**Request parameters:**
|
||||||
```python
|
```python
|
||||||
@ -278,20 +255,25 @@ body = {} # No parameters
|
|||||||
**Response data:**
|
**Response data:**
|
||||||
```python
|
```python
|
||||||
{
|
{
|
||||||
"history": list # Message history list
|
"history": list # Message history list [{"role": "user/assistant", "content": "..."}]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Message history is stored in database `chat_records` table
|
||||||
|
- Returns up to 500 most recent records
|
||||||
|
- History messages are only for UI display, not used in model inference
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 7. SAVE_HISTORY - Save Message History
|
### 7. SAVE_HISTORY - Save Message History
|
||||||
|
|
||||||
Save message history to memory.
|
Save message history to database (automatically saved after each message processing, user message and AI response saved separately).
|
||||||
|
|
||||||
**Request parameters:**
|
**Request parameters:**
|
||||||
```python
|
```python
|
||||||
body = {
|
body = {
|
||||||
"messages": list # Message list
|
"messages": list # Message list [{"role": "...", "content": "..."}]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -302,6 +284,12 @@ body = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Messages are automatically saved to database `chat_records` table
|
||||||
|
- System automatically keeps only 500 most recent records, older records are deleted
|
||||||
|
- Each call to `PROCESS_MESSAGE` will automatically save user message and AI response
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 8. SHUTDOWN - Shutdown Service
|
### 8. SHUTDOWN - Shutdown Service
|
||||||
@ -403,7 +391,10 @@ def send_message():
|
|||||||
@app.route("/config", methods=["POST"])
|
@app.route("/config", methods=["POST"])
|
||||||
def update_config():
|
def update_config():
|
||||||
data = request.json
|
data = request.json
|
||||||
result = client.update_config(data["api_key"], data.get("base_url"))
|
result = client.update_settings(
|
||||||
|
api_config=data.get("api_config", {}),
|
||||||
|
tool_limits=data.get("tool_limits", {})
|
||||||
|
)
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
||||||
@app.route("/status", methods=["GET"])
|
@app.route("/status", methods=["GET"])
|
||||||
@ -434,8 +425,11 @@ async def handler(websocket):
|
|||||||
|
|
||||||
if msg_type == "message":
|
if msg_type == "message":
|
||||||
result = client.process_message(data["content"])
|
result = client.process_message(data["content"])
|
||||||
elif msg_type == "config":
|
elif msg_type == "settings":
|
||||||
result = client.update_config(data["api_key"], data.get("base_url"))
|
result = client.update_settings(
|
||||||
|
api_config=data.get("api_config", {}),
|
||||||
|
tool_limits=data.get("tool_limits", {})
|
||||||
|
)
|
||||||
elif msg_type == "status":
|
elif msg_type == "status":
|
||||||
result = client.get_status()
|
result = client.get_status()
|
||||||
else:
|
else:
|
||||||
|
|||||||
164
docs/zh/api.md
164
docs/zh/api.md
@ -25,10 +25,8 @@ class PacketType(Enum):
|
|||||||
PROCESS_MESSAGE = "process_message" # 处理消息
|
PROCESS_MESSAGE = "process_message" # 处理消息
|
||||||
EXECUTE_TOOL = "execute_tool" # 执行工具
|
EXECUTE_TOOL = "execute_tool" # 执行工具
|
||||||
GET_STATUS = "get_status" # 获取状态
|
GET_STATUS = "get_status" # 获取状态
|
||||||
GET_CONFIG = "get_config" # 获取配置
|
GET_SETTINGS = "get_settings" # 获取完整配置(api_config + tool_limits)
|
||||||
SET_CONFIG = "set_config" # 设置配置
|
SET_SETTINGS = "set_settings" # 设置完整配置(api_config + tool_limits)
|
||||||
GET_TOOL_LIMITS = "get_tool_limits" # 获取工具限制
|
|
||||||
SET_TOOL_LIMITS = "set_tool_limits" # 设置工具限制
|
|
||||||
GET_HISTORY = "get_history" # 获取历史
|
GET_HISTORY = "get_history" # 获取历史
|
||||||
SAVE_HISTORY = "save_history" # 保存历史
|
SAVE_HISTORY = "save_history" # 保存历史
|
||||||
SHUTDOWN = "shutdown" # 关闭服务
|
SHUTDOWN = "shutdown" # 关闭服务
|
||||||
@ -166,9 +164,9 @@ print(status["data"]["running"]) # True
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4. GET_CONFIG - 获取配置
|
### 4. GET_SETTINGS - 获取完整配置
|
||||||
|
|
||||||
获取当前 API 配置。
|
获取当前 API 配置和工具限制(一次获取全部)。
|
||||||
|
|
||||||
**请求参数:**
|
**请求参数:**
|
||||||
```python
|
```python
|
||||||
@ -178,48 +176,82 @@ body = {} # 无参数
|
|||||||
**响应数据:**
|
**响应数据:**
|
||||||
```python
|
```python
|
||||||
{
|
{
|
||||||
"api_key": str, # API Key
|
"api_config": {
|
||||||
"base_url": str, # API Base URL
|
"api_key": str, # API Key
|
||||||
"model": str # 模型名称
|
"base_url": str, # API Base URL
|
||||||
|
"model": str # 模型名称
|
||||||
|
},
|
||||||
|
"tool_limits": {
|
||||||
|
"persona_query_max": int, # 人设图查询上限
|
||||||
|
"persona_update_max": int, # 人设图修改上限
|
||||||
|
"task_query_max": int, # 工作记忆查询上限
|
||||||
|
"task_update_max": int, # 工作记忆修改上限
|
||||||
|
"memory_query_max": int, # 一般记忆查询上限
|
||||||
|
"memory_update_max": int # 一般记忆修改上限
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
```python
|
||||||
|
result = client.get_settings()
|
||||||
|
api_config = result["data"]["api_config"]
|
||||||
|
tool_limits = result["data"]["tool_limits"]
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 5. SET_CONFIG - 设置配置
|
### 5. SET_SETTINGS - 设置完整配置
|
||||||
|
|
||||||
更新 API 配置(API Key 和 Base URL)。
|
更新 API 配置和工具限制(一次设置全部)。
|
||||||
|
|
||||||
**请求参数:**
|
**请求参数:**
|
||||||
```python
|
```python
|
||||||
body = {
|
body = {
|
||||||
"api_key": str, # API Key
|
"api_config": {
|
||||||
"base_url": str, # API Base URL (默认: https://api.deepseek.com)
|
"api_key": str, # API Key
|
||||||
"model": str # 模型名称 (默认: deepseek-chat)
|
"base_url": str, # API Base URL (默认: https://api.deepseek.com)
|
||||||
|
"model": str # 模型名称 (默认: deepseek-chat)
|
||||||
|
},
|
||||||
|
"tool_limits": {
|
||||||
|
"persona_query_max": int, # 人设图查询上限 (≥1)
|
||||||
|
"persona_update_max": int, # 人设图修改上限 (≥1)
|
||||||
|
"task_query_max": int, # 工作记忆查询上限 (≥1)
|
||||||
|
"task_update_max": int, # 工作记忆修改上限 (≥1)
|
||||||
|
"memory_query_max": int, # 一般记忆查询上限 (≥1)
|
||||||
|
"memory_update_max": int # 一般记忆修改上限 (≥1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**响应数据:**
|
**响应数据:**
|
||||||
```python
|
```python
|
||||||
{
|
{
|
||||||
"status": "config_updated"
|
"status": "settings_updated"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
```python
|
```python
|
||||||
result = client.update_config(
|
result = client.update_settings(
|
||||||
api_key="sk-xxxxx",
|
api_config={
|
||||||
base_url="https://api.deepseek.com",
|
"api_key": "sk-xxxxx",
|
||||||
model="deepseek-chat"
|
"base_url": "https://api.deepseek.com",
|
||||||
|
"model": "deepseek-chat"
|
||||||
|
},
|
||||||
|
tool_limits={
|
||||||
|
"persona_query_max": 2,
|
||||||
|
"task_query_max": 5,
|
||||||
|
"memory_query_max": 30
|
||||||
|
}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 6. GET_TOOL_LIMITS - 获取工具限制
|
### 6. GET_HISTORY - 获取消息历史
|
||||||
|
|
||||||
获取当前 AI 推理时的工具调用限制配置。
|
获取保存的消息历史(从数据库读取,用于UI显示,不参与模型推理)。
|
||||||
|
|
||||||
**请求参数:**
|
**请求参数:**
|
||||||
```python
|
```python
|
||||||
@ -229,84 +261,25 @@ body = {} # 无参数
|
|||||||
**响应数据:**
|
**响应数据:**
|
||||||
```python
|
```python
|
||||||
{
|
{
|
||||||
"persona_query_max": int, # 人设图查询上限
|
"history": list # 消息历史列表 [{"role": "user/assistant", "content": "..."}]
|
||||||
"persona_update_max": int, # 人设图修改上限
|
|
||||||
"task_query_max": int, # 工作记忆查询上限
|
|
||||||
"task_update_max": int, # 工作记忆修改上限
|
|
||||||
"memory_query_max": int, # 一般记忆查询上限
|
|
||||||
"memory_update_max": int # 一般记忆修改上限
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**示例:**
|
**说明:**
|
||||||
```python
|
- 消息历史存储在数据库 `chat_records` 表中
|
||||||
result = client.get_tool_limits()
|
- 最多返回最近 500 条记录
|
||||||
print(result["data"]["persona_query_max"]) # 1
|
- 历史消息仅用于 UI 显示,不参与模型推理
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 7. SET_TOOL_LIMITS - 设置工具限制
|
|
||||||
|
|
||||||
设置 AI 推理时的工具调用限制。
|
|
||||||
|
|
||||||
**请求参数:**
|
|
||||||
```python
|
|
||||||
body = {
|
|
||||||
"persona_query_max": int, # 人设图查询上限 (≥1)
|
|
||||||
"persona_update_max": int, # 人设图修改上限 (≥1)
|
|
||||||
"task_query_max": int, # 工作记忆查询上限 (≥1)
|
|
||||||
"task_update_max": int, # 工作记忆修改上限 (≥1)
|
|
||||||
"memory_query_max": int, # 一般记忆查询上限 (≥1)
|
|
||||||
"memory_update_max": int # 一般记忆修改上限 (≥1)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应数据:**
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"status": "tool_limits_updated",
|
|
||||||
"limits": {...} # 更新后的限制
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
|
||||||
result = client.update_tool_limits(
|
|
||||||
persona_query_max=2,
|
|
||||||
task_query_max=5,
|
|
||||||
memory_query_max=30
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 8. GET_HISTORY - 获取消息历史
|
|
||||||
|
|
||||||
获取保存的消息历史。
|
|
||||||
|
|
||||||
**请求参数:**
|
|
||||||
```python
|
|
||||||
body = {} # 无参数
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应数据:**
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"history": list # 消息历史列表
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 7. SAVE_HISTORY - 保存消息历史
|
### 7. SAVE_HISTORY - 保存消息历史
|
||||||
|
|
||||||
保存消息历史到内存。
|
保存消息历史到数据库(每次处理消息后自动保存,用户消息和AI回复分别保存)。
|
||||||
|
|
||||||
**请求参数:**
|
**请求参数:**
|
||||||
```python
|
```python
|
||||||
body = {
|
body = {
|
||||||
"messages": list # 消息列表
|
"messages": list # 消息列表 [{"role": "...", "content": "..."}]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -317,6 +290,11 @@ body = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**说明:**
|
||||||
|
- 消息自动保存到数据库 `chat_records` 表
|
||||||
|
- 系统自动限制最多保留 500 条记录,超出后自动删除旧记录
|
||||||
|
- 每次调用 `PROCESS_MESSAGE` 时,会自动保存用户消息和AI回复
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 8. SHUTDOWN - 关闭服务
|
### 8. SHUTDOWN - 关闭服务
|
||||||
@ -418,7 +396,10 @@ def send_message():
|
|||||||
@app.route("/config", methods=["POST"])
|
@app.route("/config", methods=["POST"])
|
||||||
def update_config():
|
def update_config():
|
||||||
data = request.json
|
data = request.json
|
||||||
result = client.update_config(data["api_key"], data.get("base_url"))
|
result = client.update_settings(
|
||||||
|
api_config=data.get("api_config", {}),
|
||||||
|
tool_limits=data.get("tool_limits", {})
|
||||||
|
)
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
||||||
@app.route("/status", methods=["GET"])
|
@app.route("/status", methods=["GET"])
|
||||||
@ -449,8 +430,11 @@ async def handler(websocket):
|
|||||||
|
|
||||||
if msg_type == "message":
|
if msg_type == "message":
|
||||||
result = client.process_message(data["content"])
|
result = client.process_message(data["content"])
|
||||||
elif msg_type == "config":
|
elif msg_type == "settings":
|
||||||
result = client.update_config(data["api_key"], data.get("base_url"))
|
result = client.update_settings(
|
||||||
|
api_config=data.get("api_config", {}),
|
||||||
|
tool_limits=data.get("tool_limits", {})
|
||||||
|
)
|
||||||
elif msg_type == "status":
|
elif msg_type == "status":
|
||||||
result = client.get_status()
|
result = client.get_status()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -160,3 +160,41 @@ def test_close_and_context_manager():
|
|||||||
finally:
|
finally:
|
||||||
if os.path.exists(db_path):
|
if os.path.exists(db_path):
|
||||||
os.unlink(db_path)
|
os.unlink(db_path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_and_get_chat_records(db):
|
||||||
|
"""测试聊天记录保存和读取"""
|
||||||
|
messages = [
|
||||||
|
{"role": "user", "content": "你好"},
|
||||||
|
{"role": "assistant", "content": "你好,有什么可以帮你?"}
|
||||||
|
]
|
||||||
|
|
||||||
|
result = db.save_chat_records(messages)
|
||||||
|
assert result["saved"] == 2
|
||||||
|
|
||||||
|
history = db.get_chat_records()
|
||||||
|
assert len(history) == 2
|
||||||
|
assert history[0]["role"] == "user"
|
||||||
|
assert history[0]["content"] == "你好"
|
||||||
|
assert history[1]["role"] == "assistant"
|
||||||
|
|
||||||
|
|
||||||
|
def test_chat_records_limit_500(db):
|
||||||
|
"""测试聊天记录限制500条"""
|
||||||
|
for i in range(600):
|
||||||
|
db.save_chat_records([{"role": "user", "content": f"消息{i}"}])
|
||||||
|
|
||||||
|
history = db.get_chat_records()
|
||||||
|
assert len(history) == 500
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_chat_records_default_limit(db):
|
||||||
|
"""测试默认limit参数"""
|
||||||
|
for i in range(100):
|
||||||
|
db.save_chat_records([{"role": "user", "content": f"msg{i}"}])
|
||||||
|
|
||||||
|
history_50 = db.get_chat_records(limit=50)
|
||||||
|
assert len(history_50) == 50
|
||||||
|
|
||||||
|
history_default = db.get_chat_records()
|
||||||
|
assert len(history_default) == 100
|
||||||
|
|||||||
120
ui/app.py
120
ui/app.py
@ -37,20 +37,21 @@ class GraphMemoryApp(App):
|
|||||||
initial_config = AppConfig()
|
initial_config = AppConfig()
|
||||||
|
|
||||||
if self._backend_client:
|
if self._backend_client:
|
||||||
config_result = self._backend_client.get_config()
|
settings_result = self._backend_client.get_settings()
|
||||||
config_data = config_result.get("data", {})
|
settings_data = settings_result.get("data", {})
|
||||||
initial_config.api_key = config_data.get("api_key", "")
|
|
||||||
initial_config.base_url = config_data.get("base_url", "https://api.deepseek.com")
|
|
||||||
initial_config.model = config_data.get("model", "deepseek-chat")
|
|
||||||
|
|
||||||
limits_result = self._backend_client.get_tool_limits()
|
api_config = settings_data.get("api_config", {})
|
||||||
limits_data = limits_result.get("data", {})
|
initial_config.api_key = api_config.get("api_key", "")
|
||||||
initial_config.persona_query_max = limits_data.get("persona_query_max", 1)
|
initial_config.base_url = api_config.get("base_url", "https://api.deepseek.com")
|
||||||
initial_config.persona_update_max = limits_data.get("persona_update_max", 1)
|
initial_config.model = api_config.get("model", "deepseek-chat")
|
||||||
initial_config.task_query_max = limits_data.get("task_query_max", 4)
|
|
||||||
initial_config.task_update_max = limits_data.get("task_update_max", 2)
|
tool_limits = settings_data.get("tool_limits", {})
|
||||||
initial_config.memory_query_max = limits_data.get("memory_query_max", 20)
|
initial_config.persona_query_max = tool_limits.get("persona_query_max", 1)
|
||||||
initial_config.memory_update_max = limits_data.get("memory_update_max", 10)
|
initial_config.persona_update_max = tool_limits.get("persona_update_max", 1)
|
||||||
|
initial_config.task_query_max = tool_limits.get("task_query_max", 4)
|
||||||
|
initial_config.task_update_max = tool_limits.get("task_update_max", 2)
|
||||||
|
initial_config.memory_query_max = tool_limits.get("memory_query_max", 20)
|
||||||
|
initial_config.memory_update_max = tool_limits.get("memory_update_max", 10)
|
||||||
|
|
||||||
yield LeftPanel()
|
yield LeftPanel()
|
||||||
yield RightPanel(config=initial_config)
|
yield RightPanel(config=initial_config)
|
||||||
@ -73,7 +74,16 @@ class GraphMemoryApp(App):
|
|||||||
self._api_configured = data.get("config", {}).get("api_key", "") != ""
|
self._api_configured = data.get("config", {}).get("api_key", "") != ""
|
||||||
status_bar.set_api_status(self._api_configured)
|
status_bar.set_api_status(self._api_configured)
|
||||||
|
|
||||||
from .widgets.message_history import MessageHistory
|
if self._api_configured:
|
||||||
|
result = self._backend_client.get_history()
|
||||||
|
if result.get("success"):
|
||||||
|
history_data = result.get("data", {})
|
||||||
|
chat_history = history_data.get("history", [])
|
||||||
|
history = self.query_one(MessageHistory)
|
||||||
|
for msg in chat_history:
|
||||||
|
message = Message(role=msg["role"], content=msg["content"])
|
||||||
|
history.add_message(message)
|
||||||
|
|
||||||
history = self.query_one(MessageHistory)
|
history = self.query_one(MessageHistory)
|
||||||
welcome = Message(
|
welcome = Message(
|
||||||
role="assistant",
|
role="assistant",
|
||||||
@ -184,48 +194,62 @@ class GraphMemoryApp(App):
|
|||||||
self.notify("后端未初始化,无法保存配置", title="错误", severity="error")
|
self.notify("后端未初始化,无法保存配置", title="错误", severity="error")
|
||||||
return
|
return
|
||||||
|
|
||||||
config = event.config
|
asyncio.create_task(self._update_settings_async(event.config))
|
||||||
|
|
||||||
if event.is_tool_limits:
|
|
||||||
asyncio.create_task(self._update_tool_limits_async(config))
|
|
||||||
else:
|
|
||||||
asyncio.create_task(self._update_config_async(config))
|
|
||||||
|
|
||||||
async def _update_config_async(self, config) -> None:
|
async def _update_settings_async(self, config) -> None:
|
||||||
"""异步更新配置"""
|
|
||||||
from .widgets.status_bar import StatusBar
|
from .widgets.status_bar import StatusBar
|
||||||
from .widgets.config_section import ConfigSection
|
from .widgets.config_section import ConfigSection
|
||||||
|
|
||||||
status_bar = self.query_one(StatusBar)
|
status_bar = self.query_one(StatusBar)
|
||||||
|
|
||||||
api_key = config.api_key
|
api_config = {
|
||||||
base_url = config.base_url
|
"api_key": config.api_key,
|
||||||
model = getattr(config, 'model', 'deepseek-chat')
|
"base_url": config.base_url,
|
||||||
|
"model": getattr(config, 'model', 'deepseek-chat')
|
||||||
|
}
|
||||||
|
|
||||||
|
tool_limits = {
|
||||||
|
"persona_query_max": config.persona_query_max,
|
||||||
|
"persona_update_max": config.persona_update_max,
|
||||||
|
"task_query_max": config.task_query_max,
|
||||||
|
"task_update_max": config.task_update_max,
|
||||||
|
"memory_query_max": config.memory_query_max,
|
||||||
|
"memory_update_max": config.memory_update_max,
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = await asyncio.get_event_loop().run_in_executor(
|
result = await asyncio.get_event_loop().run_in_executor(
|
||||||
None,
|
None,
|
||||||
lambda: self._backend_client.update_config(api_key=api_key, base_url=base_url, model=model)
|
lambda: self._backend_client.update_settings(
|
||||||
|
api_config=api_config,
|
||||||
|
tool_limits=tool_limits
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.get("success"):
|
if result.get("success"):
|
||||||
self._api_configured = bool(api_key)
|
self._api_configured = bool(config.api_key)
|
||||||
status_bar.set_api_status(self._api_configured)
|
status_bar.set_api_status(self._api_configured)
|
||||||
|
|
||||||
# 从后端重新获取配置并刷新 UI
|
settings_result = await asyncio.get_event_loop().run_in_executor(
|
||||||
config_result = await asyncio.get_event_loop().run_in_executor(
|
|
||||||
None,
|
None,
|
||||||
lambda: self._backend_client.get_config()
|
lambda: self._backend_client.get_settings()
|
||||||
)
|
)
|
||||||
config_data = config_result.get("data", {})
|
settings_data = settings_result.get("data", {})
|
||||||
|
|
||||||
# 刷新输入框
|
|
||||||
try:
|
try:
|
||||||
config_section = self.query_one(ConfigSection)
|
config_section = self.query_one(ConfigSection)
|
||||||
|
api_cfg = settings_data.get("api_config", {})
|
||||||
|
tool_lmts = settings_data.get("tool_limits", {})
|
||||||
config_section.set_config(AppConfig(
|
config_section.set_config(AppConfig(
|
||||||
api_key=config_data.get("api_key", ""),
|
api_key=api_cfg.get("api_key", ""),
|
||||||
base_url=config_data.get("base_url", "https://api.deepseek.com"),
|
base_url=api_cfg.get("base_url", "https://api.deepseek.com"),
|
||||||
model=config_data.get("model", "deepseek-chat")
|
model=api_cfg.get("model", "deepseek-chat"),
|
||||||
|
persona_query_max=tool_lmts.get("persona_query_max", 1),
|
||||||
|
persona_update_max=tool_lmts.get("persona_update_max", 1),
|
||||||
|
task_query_max=tool_lmts.get("task_query_max", 4),
|
||||||
|
task_update_max=tool_lmts.get("task_update_max", 2),
|
||||||
|
memory_query_max=tool_lmts.get("memory_query_max", 20),
|
||||||
|
memory_update_max=tool_lmts.get("memory_update_max", 10),
|
||||||
))
|
))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@ -234,31 +258,5 @@ class GraphMemoryApp(App):
|
|||||||
else:
|
else:
|
||||||
error = result.get("error", "未知错误")
|
error = result.get("error", "未知错误")
|
||||||
self.notify(f"❌ 配置失败: {error}", title="配置失败", severity="error")
|
self.notify(f"❌ 配置失败: {error}", title="配置失败", severity="error")
|
||||||
except Exception as e:
|
|
||||||
self.notify(f"❌ 配置异常: {str(e)}", title="配置失败", severity="error")
|
|
||||||
|
|
||||||
async def _update_tool_limits_async(self, config) -> None:
|
|
||||||
from .widgets.status_bar import StatusBar
|
|
||||||
|
|
||||||
status_bar = self.query_one(StatusBar)
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = await asyncio.get_event_loop().run_in_executor(
|
|
||||||
None,
|
|
||||||
lambda: self._backend_client.update_tool_limits(
|
|
||||||
persona_query_max=config.persona_query_max,
|
|
||||||
persona_update_max=config.persona_update_max,
|
|
||||||
task_query_max=config.task_query_max,
|
|
||||||
task_update_max=config.task_update_max,
|
|
||||||
memory_query_max=config.memory_query_max,
|
|
||||||
memory_update_max=config.memory_update_max,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.get("success"):
|
|
||||||
self.notify("✅ 工具限制已保存", title="配置成功", severity="information")
|
|
||||||
else:
|
|
||||||
error = result.get("error", "未知错误")
|
|
||||||
self.notify(f"❌ 配置失败: {error}", title="配置失败", severity="error")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.notify(f"❌ 配置异常: {str(e)}", title="配置失败", severity="error")
|
self.notify(f"❌ 配置异常: {str(e)}", title="配置失败", severity="error")
|
||||||
Reference in New Issue
Block a user