myagent-ai 1.10.7 → 1.10.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/__pycache__/main_agent.cpython-312.pyc +0 -0
- package/agents/__pycache__/memory_agent.cpython-312.pyc +0 -0
- package/agents/main_agent.py +17 -17
- package/agents/memory_agent.py +2 -2
- package/config.py +0 -5
- package/core/__pycache__/context_builder.cpython-312.pyc +0 -0
- package/memory/__pycache__/manager.cpython-312.pyc +0 -0
- package/memory/manager.py +22 -129
- package/package.json +1 -1
- package/web/__pycache__/api_server.cpython-312.pyc +0 -0
- package/web/api_server.py +27 -26
- package/web/ui/chat/chat_main.js +1 -1
- package/web/ui/chat/middle_chat.html +1 -1
- package/web/ui/index.html +18 -22
|
Binary file
|
|
Binary file
|
package/agents/main_agent.py
CHANGED
|
@@ -453,9 +453,9 @@ class MainAgent(BaseAgent):
|
|
|
453
453
|
except Exception as e:
|
|
454
454
|
logger.warning(f"[{task_id}] 加载历史对话失败: {e}")
|
|
455
455
|
|
|
456
|
-
#
|
|
456
|
+
# 保存用户消息到会话记忆
|
|
457
457
|
if self.memory:
|
|
458
|
-
self.memory.
|
|
458
|
+
self.memory.add_session(
|
|
459
459
|
session_id=context.session_id,
|
|
460
460
|
role="user",
|
|
461
461
|
content=context.user_message,
|
|
@@ -551,9 +551,9 @@ class MainAgent(BaseAgent):
|
|
|
551
551
|
llm_raw = response.content
|
|
552
552
|
logger.debug(f"[{task_id}] LLM 输出 (前500字): {llm_raw[:500]}")
|
|
553
553
|
|
|
554
|
-
# 保存 LLM
|
|
554
|
+
# 保存 LLM 原始输出到会话记忆(用于回溯和审计)
|
|
555
555
|
if self.memory:
|
|
556
|
-
self.memory.
|
|
556
|
+
self.memory.add_session(
|
|
557
557
|
session_id=context.session_id,
|
|
558
558
|
role="assistant",
|
|
559
559
|
content=llm_raw,
|
|
@@ -588,7 +588,7 @@ class MainAgent(BaseAgent):
|
|
|
588
588
|
context.working_memory["final_response"] = final_text
|
|
589
589
|
await self._emit_v2_event("v2_reasoning", {"content": final_text}, stream_callback)
|
|
590
590
|
if self.memory:
|
|
591
|
-
self.memory.
|
|
591
|
+
self.memory.add_session(
|
|
592
592
|
session_id=context.session_id,
|
|
593
593
|
role="assistant",
|
|
594
594
|
content=final_text,
|
|
@@ -601,7 +601,7 @@ class MainAgent(BaseAgent):
|
|
|
601
601
|
context.working_memory["final_response"] = final_text
|
|
602
602
|
await self._emit_v2_event("v2_reasoning", {"content": final_text}, stream_callback)
|
|
603
603
|
if self.memory:
|
|
604
|
-
self.memory.
|
|
604
|
+
self.memory.add_session(
|
|
605
605
|
session_id=context.session_id,
|
|
606
606
|
role="assistant",
|
|
607
607
|
content=final_text,
|
|
@@ -741,7 +741,7 @@ class MainAgent(BaseAgent):
|
|
|
741
741
|
stream_callback,
|
|
742
742
|
)
|
|
743
743
|
if self.memory:
|
|
744
|
-
self.memory.
|
|
744
|
+
self.memory.add_session(
|
|
745
745
|
session_id=context.session_id,
|
|
746
746
|
role="assistant",
|
|
747
747
|
content=parsed.ask_user,
|
|
@@ -758,7 +758,7 @@ class MainAgent(BaseAgent):
|
|
|
758
758
|
context.working_memory["final_response"] = final_text
|
|
759
759
|
await self._emit_v2_event("v2_reasoning", {"content": truncate_str(final_text, 3000)}, stream_callback)
|
|
760
760
|
if self.memory:
|
|
761
|
-
self.memory.
|
|
761
|
+
self.memory.add_session(
|
|
762
762
|
session_id=context.session_id,
|
|
763
763
|
role="assistant",
|
|
764
764
|
content=final_text,
|
|
@@ -770,7 +770,7 @@ class MainAgent(BaseAgent):
|
|
|
770
770
|
context.working_memory["final_response"] = final_text
|
|
771
771
|
await self._emit_v2_event("v2_reasoning", {"content": truncate_str(final_text, 3000)}, stream_callback)
|
|
772
772
|
if self.memory:
|
|
773
|
-
self.memory.
|
|
773
|
+
self.memory.add_session(
|
|
774
774
|
session_id=context.session_id,
|
|
775
775
|
role="assistant",
|
|
776
776
|
content=final_text,
|
|
@@ -1006,16 +1006,16 @@ class MainAgent(BaseAgent):
|
|
|
1006
1006
|
content=f"[工具 {tool_name} 执行完成] {'成功' if tool_result.get('success') else '失败'}",
|
|
1007
1007
|
))
|
|
1008
1008
|
|
|
1009
|
-
#
|
|
1009
|
+
# 保存工具调用到会话记忆
|
|
1010
1010
|
if self.memory:
|
|
1011
|
-
self.memory.
|
|
1011
|
+
self.memory.add_session(
|
|
1012
1012
|
session_id=context.session_id,
|
|
1013
1013
|
role="assistant",
|
|
1014
1014
|
content=f"调用工具: {tool_name}\n参数: {truncate_str(parms, 1000)}",
|
|
1015
1015
|
key="tool_call",
|
|
1016
1016
|
importance=0.4,
|
|
1017
1017
|
)
|
|
1018
|
-
self.memory.
|
|
1018
|
+
self.memory.add_session(
|
|
1019
1019
|
session_id=context.session_id,
|
|
1020
1020
|
role="tool",
|
|
1021
1021
|
content=f"[{tool_name}] {'成功' if tool_result.get('success') else '失败'}\n{truncate_str(output_str, 5000)}",
|
|
@@ -1048,7 +1048,7 @@ class MainAgent(BaseAgent):
|
|
|
1048
1048
|
context.working_memory["final_response"] = final_text
|
|
1049
1049
|
await self._emit_v2_event("v2_reasoning", {"content": truncate_str(final_text, 3000)}, stream_callback)
|
|
1050
1050
|
if self.memory:
|
|
1051
|
-
self.memory.
|
|
1051
|
+
self.memory.add_session(
|
|
1052
1052
|
session_id=context.session_id,
|
|
1053
1053
|
role="assistant",
|
|
1054
1054
|
content=final_text,
|
|
@@ -1075,7 +1075,7 @@ class MainAgent(BaseAgent):
|
|
|
1075
1075
|
context.working_memory["final_response"] = final_text
|
|
1076
1076
|
await self._emit_v2_event("v2_reasoning", {"content": truncate_str(final_text, 3000)}, stream_callback)
|
|
1077
1077
|
if self.memory:
|
|
1078
|
-
self.memory.
|
|
1078
|
+
self.memory.add_session(
|
|
1079
1079
|
session_id=context.session_id,
|
|
1080
1080
|
role="assistant",
|
|
1081
1081
|
content=final_text,
|
|
@@ -1084,14 +1084,14 @@ class MainAgent(BaseAgent):
|
|
|
1084
1084
|
|
|
1085
1085
|
logger.info(f"[{task_id}] finish=false 且 need_callback=true,回调 LLM...")
|
|
1086
1086
|
|
|
1087
|
-
# 回调前,保存当前轮次的 LLM
|
|
1087
|
+
# 回调前,保存当前轮次的 LLM 输出到会话记忆
|
|
1088
1088
|
# 这样每轮工具调用都有对应的 assistant 消息记录
|
|
1089
1089
|
if self.memory:
|
|
1090
1090
|
_round_items = _v2_reasoning_collected[_reasoning_len_before_round:]
|
|
1091
1091
|
if _round_items:
|
|
1092
1092
|
_round_output = "\n".join(_round_items)
|
|
1093
1093
|
if _round_output.strip():
|
|
1094
|
-
self.memory.
|
|
1094
|
+
self.memory.add_session(
|
|
1095
1095
|
session_id=context.session_id,
|
|
1096
1096
|
role="assistant",
|
|
1097
1097
|
content=_round_output,
|
|
@@ -1102,7 +1102,7 @@ class MainAgent(BaseAgent):
|
|
|
1102
1102
|
if self.memory and _v2_reasoning_collected:
|
|
1103
1103
|
_fallback_text = "\n".join(_v2_reasoning_collected)
|
|
1104
1104
|
if _fallback_text.strip():
|
|
1105
|
-
self.memory.
|
|
1105
|
+
self.memory.add_session(
|
|
1106
1106
|
session_id=context.session_id,
|
|
1107
1107
|
role="assistant",
|
|
1108
1108
|
content=_fallback_text,
|
package/agents/memory_agent.py
CHANGED
|
@@ -116,7 +116,7 @@ class MemoryAgent(BaseAgent):
|
|
|
116
116
|
)
|
|
117
117
|
|
|
118
118
|
# 修剪旧消息
|
|
119
|
-
max_msgs = self.config.memory.
|
|
119
|
+
max_msgs = self.config.memory.max_session
|
|
120
120
|
self.memory.prune_conversation(session_id, max_msgs)
|
|
121
121
|
|
|
122
122
|
logger.debug(f"对话已保存到会话记忆 (session={session_id})")
|
|
@@ -162,7 +162,7 @@ class MemoryAgent(BaseAgent):
|
|
|
162
162
|
if not self.memory:
|
|
163
163
|
return
|
|
164
164
|
|
|
165
|
-
entries = self.memory.
|
|
165
|
+
entries = self.memory._query(session_id=session_id, category="session", limit=100)
|
|
166
166
|
context.working_memory["task_progress"] = [
|
|
167
167
|
{"key": e.key, "content": e.content, "metadata": e.metadata}
|
|
168
168
|
for e in entries
|
package/config.py
CHANGED
|
Binary file
|
|
Binary file
|
package/memory/manager.py
CHANGED
|
@@ -6,10 +6,6 @@ memory/manager.py - 记忆管理器
|
|
|
6
6
|
- 会话记忆 (session): 当前会话的对话上下文和任务进度,仅本 session 可检索
|
|
7
7
|
|
|
8
8
|
所有记忆通过 session_id 隔离,全局记忆可跨会话检索。
|
|
9
|
-
|
|
10
|
-
向后兼容:
|
|
11
|
-
旧 API (add_short_term, add_working, add_long_term) 仍然可用,
|
|
12
|
-
内部自动映射到新的 session / global 分类。
|
|
13
9
|
"""
|
|
14
10
|
from __future__ import annotations
|
|
15
11
|
|
|
@@ -95,23 +91,13 @@ class MemoryManager:
|
|
|
95
91
|
- session (会话记忆): 对话上下文 + 任务进度,按 session_id 隔离
|
|
96
92
|
- global (全局记忆): 跨会话持久知识,所有 session 可检索
|
|
97
93
|
|
|
98
|
-
向后兼容:
|
|
99
|
-
旧方法 add_short_term / add_working / add_long_term 仍可正常使用,
|
|
100
|
-
内部自动映射到 session / global 分类。
|
|
101
|
-
旧分类值 short_term、working、long_term 在查询时自动包含。
|
|
102
|
-
|
|
103
94
|
使用示例:
|
|
104
95
|
mm = MemoryManager(db_path="~/.myagent/data/memory.db")
|
|
105
96
|
mm.initialize()
|
|
106
97
|
|
|
107
|
-
# 新 API
|
|
108
98
|
mm.add_session("s1", role="user", content="帮我创建一个Python项目")
|
|
109
99
|
mm.add_global(content="偏好使用Python, TypeScript")
|
|
110
100
|
|
|
111
|
-
# 旧 API (仍然可用)
|
|
112
|
-
mm.add_short_term("s1", "user", "帮我创建一个Python项目")
|
|
113
|
-
mm.add_long_term("user_prefs", "coding_style", "偏好使用Python, TypeScript")
|
|
114
|
-
|
|
115
101
|
# 查询对话历史
|
|
116
102
|
history = mm.get_conversation("s1")
|
|
117
103
|
|
|
@@ -183,29 +169,6 @@ class MemoryManager:
|
|
|
183
169
|
self._local.conn.close()
|
|
184
170
|
self._local.conn = None
|
|
185
171
|
|
|
186
|
-
# ==========================================================================
|
|
187
|
-
# 分类映射 (向后兼容)
|
|
188
|
-
# ==========================================================================
|
|
189
|
-
|
|
190
|
-
def _normalize_category(self, category: str) -> str:
|
|
191
|
-
"""Map old category values to new ones:
|
|
192
|
-
short_term, working → session
|
|
193
|
-
long_term → global
|
|
194
|
-
"""
|
|
195
|
-
_MAP = {"short_term": "session", "working": "session", "long_term": "global"}
|
|
196
|
-
return _MAP.get(category, category)
|
|
197
|
-
|
|
198
|
-
def _expand_category(self, category: str) -> list:
|
|
199
|
-
"""Expand category for SQL IN clause (backward compat):
|
|
200
|
-
"session" → ["session", "short_term", "working"]
|
|
201
|
-
"global" → ["global", "long_term"]
|
|
202
|
-
"""
|
|
203
|
-
if category == "session":
|
|
204
|
-
return ["session", "short_term", "working"]
|
|
205
|
-
elif category == "global":
|
|
206
|
-
return ["global", "long_term"]
|
|
207
|
-
return [category]
|
|
208
|
-
|
|
209
172
|
# ==========================================================================
|
|
210
173
|
# 基础 CRUD
|
|
211
174
|
# ==========================================================================
|
|
@@ -252,10 +215,8 @@ class MemoryManager:
|
|
|
252
215
|
conditions.append("session_id = ?")
|
|
253
216
|
params.append(session_id)
|
|
254
217
|
if category:
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
conditions.append(f"category IN ({placeholders})")
|
|
258
|
-
params.extend(expanded)
|
|
218
|
+
conditions.append("category = ?")
|
|
219
|
+
params.append(category)
|
|
259
220
|
if key:
|
|
260
221
|
conditions.append("key = ?")
|
|
261
222
|
params.append(key)
|
|
@@ -323,58 +284,13 @@ class MemoryManager:
|
|
|
323
284
|
)
|
|
324
285
|
return self._insert(entry)
|
|
325
286
|
|
|
326
|
-
# --- 兼容别名 ---
|
|
327
|
-
def add_short_term(self, session_id, role, content, key="", importance=0.5) -> str:
|
|
328
|
-
"""[兼容] → add_session()"""
|
|
329
|
-
return self.add_session(session_id=session_id, role=role, content=content, key=key, importance=importance)
|
|
330
|
-
|
|
331
|
-
def add_working(self, session_id, key, content, metadata=None) -> str:
|
|
332
|
-
"""[兼容] → add_session()"""
|
|
333
|
-
return self.add_session(session_id=session_id, key=key, content=content, importance=0.7, metadata=metadata or {})
|
|
334
|
-
|
|
335
|
-
def get_working(self, session_id, key="") -> List[MemoryEntry]:
|
|
336
|
-
"""[兼容] 获取会话记忆中 key 非空的条目"""
|
|
337
|
-
conn = self._get_conn()
|
|
338
|
-
cats = self._expand_category("session")
|
|
339
|
-
placeholders = ",".join("?" for _ in cats)
|
|
340
|
-
conditions = [f"session_id = ?", f"category IN ({placeholders})", "key != ''"]
|
|
341
|
-
params = [session_id] + cats
|
|
342
|
-
if key:
|
|
343
|
-
conditions.append("key = ?")
|
|
344
|
-
params.append(key)
|
|
345
|
-
where = " AND ".join(conditions)
|
|
346
|
-
sql = f"SELECT * FROM memories WHERE {where} ORDER BY created_at DESC LIMIT 100"
|
|
347
|
-
rows = conn.execute(sql, params).fetchall()
|
|
348
|
-
return [MemoryEntry.from_row(row) for row in rows]
|
|
349
|
-
|
|
350
|
-
def update_working(self, memory_id, content, **updates):
|
|
351
|
-
"""[兼容] → _update_content()"""
|
|
352
|
-
self._update_content(memory_id, content, **updates)
|
|
353
|
-
|
|
354
|
-
def clear_working(self, session_id) -> int:
|
|
355
|
-
"""[兼容] 清空会话记忆中 key 非空的条目"""
|
|
356
|
-
conn = self._get_conn()
|
|
357
|
-
cats = self._expand_category("session")
|
|
358
|
-
placeholders = ",".join("?" for _ in cats)
|
|
359
|
-
params = [session_id] + cats
|
|
360
|
-
cursor = conn.execute(
|
|
361
|
-
f"DELETE FROM memories WHERE session_id = ? AND category IN ({placeholders}) AND key != ''",
|
|
362
|
-
params,
|
|
363
|
-
)
|
|
364
|
-
conn.commit()
|
|
365
|
-
return cursor.rowcount
|
|
366
|
-
|
|
367
287
|
def get_conversation(self, session_id, limit=500, include_roles=None) -> List[MemoryEntry]:
|
|
368
|
-
"""
|
|
288
|
+
"""获取对话历史(session 分类中 role 非空的条目)"""
|
|
369
289
|
conn = self._get_conn()
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
where = " AND ".join(conditions)
|
|
375
|
-
sql = f"SELECT * FROM memories WHERE {where} ORDER BY created_at ASC LIMIT ?"
|
|
376
|
-
params.append(limit)
|
|
377
|
-
rows = conn.execute(sql, params).fetchall()
|
|
290
|
+
sql = """SELECT * FROM memories
|
|
291
|
+
WHERE session_id = ? AND category = 'session' AND role != ''
|
|
292
|
+
ORDER BY created_at ASC LIMIT ?"""
|
|
293
|
+
rows = conn.execute(sql, (session_id, limit)).fetchall()
|
|
378
294
|
entries = [MemoryEntry.from_row(row) for row in rows]
|
|
379
295
|
if include_roles:
|
|
380
296
|
entries = [e for e in entries if e.role in include_roles]
|
|
@@ -403,14 +319,7 @@ class MemoryManager:
|
|
|
403
319
|
|
|
404
320
|
def clear_conversation(self, session_id) -> int:
|
|
405
321
|
"""清空会话对话历史"""
|
|
406
|
-
|
|
407
|
-
# 兼容旧数据
|
|
408
|
-
for old_cat in ("short_term", "working"):
|
|
409
|
-
try:
|
|
410
|
-
count += self._delete(session_id, category=old_cat)
|
|
411
|
-
except Exception:
|
|
412
|
-
pass
|
|
413
|
-
return count
|
|
322
|
+
return self._delete(session_id, category="session")
|
|
414
323
|
|
|
415
324
|
def delete_session(self, session_id: str) -> int:
|
|
416
325
|
"""删除会话的所有记忆(对话、工作记忆、长期记忆等),彻底删除该会话"""
|
|
@@ -504,11 +413,6 @@ class MemoryManager:
|
|
|
504
413
|
)
|
|
505
414
|
return self._insert(entry)
|
|
506
415
|
|
|
507
|
-
# --- 兼容别名 ---
|
|
508
|
-
def add_long_term(self, session_id="global", key="", content="", summary="", importance=0.7, metadata=None) -> str:
|
|
509
|
-
"""[兼容] → add_global()"""
|
|
510
|
-
return self.add_global(session_id=session_id, key=key, content=content, summary=summary, importance=importance, metadata=metadata)
|
|
511
|
-
|
|
512
416
|
def find_duplicate_memory(
|
|
513
417
|
self,
|
|
514
418
|
content: str,
|
|
@@ -535,10 +439,8 @@ class MemoryManager:
|
|
|
535
439
|
return None # 空内容不查重
|
|
536
440
|
|
|
537
441
|
conn = self._get_conn()
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
conditions = [f"category IN ({placeholders})"]
|
|
541
|
-
params: list = list(cats)
|
|
442
|
+
conditions = ["category = 'global'"]
|
|
443
|
+
params: list = []
|
|
542
444
|
|
|
543
445
|
if session_id:
|
|
544
446
|
conditions.append("session_id = ?")
|
|
@@ -633,13 +535,11 @@ class MemoryManager:
|
|
|
633
535
|
return True
|
|
634
536
|
return False
|
|
635
537
|
|
|
636
|
-
def
|
|
538
|
+
def get_global_memories(self, session_id="global", key="", limit=50) -> List[MemoryEntry]:
|
|
637
539
|
"""获取全局记忆"""
|
|
638
540
|
conn = self._get_conn()
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
conditions = ["category IN (%s)" % placeholders]
|
|
642
|
-
params = list(cats)
|
|
541
|
+
conditions = ["category = 'global'"]
|
|
542
|
+
params: list = []
|
|
643
543
|
if session_id:
|
|
644
544
|
conditions.insert(0, "session_id = ?")
|
|
645
545
|
params.insert(0, session_id)
|
|
@@ -654,15 +554,15 @@ class MemoryManager:
|
|
|
654
554
|
|
|
655
555
|
def get_preferences(self, session_id: str = "global") -> List[MemoryEntry]:
|
|
656
556
|
"""获取用户偏好"""
|
|
657
|
-
return self.
|
|
557
|
+
return self.get_global_memories(session_id, key="user_pref")
|
|
658
558
|
|
|
659
559
|
def get_experience(self, session_id: str = "global") -> List[MemoryEntry]:
|
|
660
560
|
"""获取技能经验"""
|
|
661
|
-
return self.
|
|
561
|
+
return self.get_global_memories(session_id, key="skill_experience")
|
|
662
562
|
|
|
663
563
|
def get_task_summaries(self, session_id: str = "global") -> List[MemoryEntry]:
|
|
664
564
|
"""获取历史任务总结"""
|
|
665
|
-
return self.
|
|
565
|
+
return self.get_global_memories(session_id, key="task_summary")
|
|
666
566
|
|
|
667
567
|
# ==========================================================================
|
|
668
568
|
# 记忆搜索
|
|
@@ -812,10 +712,8 @@ class MemoryManager:
|
|
|
812
712
|
conditions.append("session_id = ?")
|
|
813
713
|
params.append(session_id)
|
|
814
714
|
if category:
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
conditions.append(f"category IN ({placeholders})")
|
|
818
|
-
params.extend(expanded)
|
|
715
|
+
conditions.append("category = ?")
|
|
716
|
+
params.append(category)
|
|
819
717
|
|
|
820
718
|
# 过滤过期
|
|
821
719
|
conditions.append("(expires_at = '' OR expires_at > ?)")
|
|
@@ -959,7 +857,7 @@ class MemoryManager:
|
|
|
959
857
|
original_count: int = 0,
|
|
960
858
|
) -> str:
|
|
961
859
|
"""保存对话总结为全局记忆"""
|
|
962
|
-
return self.
|
|
860
|
+
return self.add_global(
|
|
963
861
|
session_id=session_id,
|
|
964
862
|
key="conversation_summary",
|
|
965
863
|
content=summary,
|
|
@@ -985,7 +883,7 @@ class MemoryManager:
|
|
|
985
883
|
session_id: str = "global",
|
|
986
884
|
):
|
|
987
885
|
"""记录错误模式及修复方案"""
|
|
988
|
-
self.
|
|
886
|
+
self.add_global(
|
|
989
887
|
session_id=session_id,
|
|
990
888
|
key="error_pattern",
|
|
991
889
|
content=f"错误: {error}\n修复: {fix}",
|
|
@@ -1002,22 +900,17 @@ class MemoryManager:
|
|
|
1002
900
|
conn = self._get_conn()
|
|
1003
901
|
stats = {}
|
|
1004
902
|
for cat in ("session", "global"):
|
|
1005
|
-
cats = self._expand_category(cat)
|
|
1006
|
-
placeholders = ",".join("?" for _ in cats)
|
|
1007
903
|
row = conn.execute(
|
|
1008
|
-
|
|
1009
|
-
|
|
904
|
+
"SELECT COUNT(*) as cnt FROM memories WHERE category = ?",
|
|
905
|
+
(cat,),
|
|
1010
906
|
).fetchone()
|
|
1011
907
|
stats[f"{cat}_count"] = row["cnt"]
|
|
1012
|
-
|
|
1013
908
|
total = conn.execute("SELECT COUNT(*) as cnt FROM memories").fetchone()
|
|
1014
909
|
stats["total_count"] = total["cnt"]
|
|
1015
|
-
|
|
1016
910
|
sessions = conn.execute(
|
|
1017
911
|
"SELECT COUNT(DISTINCT session_id) as cnt FROM memories"
|
|
1018
912
|
).fetchone()
|
|
1019
913
|
stats["distinct_sessions"] = sessions["cnt"]
|
|
1020
|
-
|
|
1021
914
|
return stats
|
|
1022
915
|
|
|
1023
916
|
def cleanup_expired(self) -> int:
|
package/package.json
CHANGED
|
Binary file
|
package/web/api_server.py
CHANGED
|
@@ -278,8 +278,8 @@ class ApiServer:
|
|
|
278
278
|
r.add_get("/api/session/status", self.handle_session_status_query) # query param version
|
|
279
279
|
r.add_get("/api/memory/stats", self.handle_memory_stats)
|
|
280
280
|
r.add_get("/api/memory/search", self.handle_memory_search)
|
|
281
|
-
r.add_get("/api/memory/
|
|
282
|
-
r.add_delete("/api/memory/
|
|
281
|
+
r.add_get("/api/memory/list", self.handle_memory_list)
|
|
282
|
+
r.add_delete("/api/memory/{mid}", self.handle_delete_memory)
|
|
283
283
|
r.add_post("/api/memory/cleanup", self.handle_memory_cleanup)
|
|
284
284
|
# 权限管理
|
|
285
285
|
r.add_get("/api/permissions", self.handle_get_permissions)
|
|
@@ -551,7 +551,7 @@ class ApiServer:
|
|
|
551
551
|
response = await self.core.process_message(clean_message, session_id)
|
|
552
552
|
|
|
553
553
|
# 记忆保存已由 MainAgent._process_inner() 完成,此处不再重复保存
|
|
554
|
-
# (MainAgent 内部会保存 user + assistant
|
|
554
|
+
# (MainAgent 内部会保存 user + assistant 消息到会话记忆)
|
|
555
555
|
|
|
556
556
|
# ── 收集执行事件(供前端展示命令执行过程) ──
|
|
557
557
|
exec_events = []
|
|
@@ -795,11 +795,11 @@ class ApiServer:
|
|
|
795
795
|
# 使用已累积的结果(如果有)
|
|
796
796
|
saved = result_store.get("full_response", "")
|
|
797
797
|
if saved:
|
|
798
|
-
self.core.memory.
|
|
798
|
+
self.core.memory.add_session(
|
|
799
799
|
session_id=session_id, role="assistant", content=saved,
|
|
800
800
|
)
|
|
801
801
|
else:
|
|
802
|
-
self.core.memory.
|
|
802
|
+
self.core.memory.add_session(
|
|
803
803
|
session_id=session_id, role="assistant",
|
|
804
804
|
content=f"⚠️ [执行异常] {str(e)[:200]}",
|
|
805
805
|
)
|
|
@@ -1647,7 +1647,7 @@ class ApiServer:
|
|
|
1647
1647
|
if self.core.memory:
|
|
1648
1648
|
rows = self.core.memory._get_conn().execute(
|
|
1649
1649
|
"SELECT session_id, COUNT(*) as cnt FROM memories "
|
|
1650
|
-
"WHERE category
|
|
1650
|
+
"WHERE category = 'session' GROUP BY session_id").fetchall()
|
|
1651
1651
|
session_counts = {}
|
|
1652
1652
|
for r in rows:
|
|
1653
1653
|
sid = r["session_id"]
|
|
@@ -1667,7 +1667,7 @@ class ApiServer:
|
|
|
1667
1667
|
if self.core.memory:
|
|
1668
1668
|
rows = self.core.memory._get_conn().execute(
|
|
1669
1669
|
"SELECT session_id, COUNT(*) as cnt FROM memories "
|
|
1670
|
-
"WHERE category
|
|
1670
|
+
"WHERE category = 'session' GROUP BY session_id").fetchall()
|
|
1671
1671
|
session_counts = {}
|
|
1672
1672
|
for r in rows:
|
|
1673
1673
|
sid = r["session_id"]
|
|
@@ -2301,12 +2301,12 @@ class ApiServer:
|
|
|
2301
2301
|
prefix = f"{agent}_"
|
|
2302
2302
|
rows = self.core.memory._get_conn().execute(
|
|
2303
2303
|
"SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last FROM memories "
|
|
2304
|
-
"WHERE category
|
|
2304
|
+
"WHERE category = 'session' AND session_id LIKE ? GROUP BY session_id ORDER BY last DESC LIMIT 100",
|
|
2305
2305
|
(prefix + "%",)).fetchall()
|
|
2306
2306
|
else:
|
|
2307
2307
|
rows = self.core.memory._get_conn().execute(
|
|
2308
2308
|
"SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last FROM memories "
|
|
2309
|
-
"WHERE category
|
|
2309
|
+
"WHERE category = 'session' GROUP BY session_id ORDER BY last DESC LIMIT 100").fetchall()
|
|
2310
2310
|
sessions = [{"id": r["session_id"], "messages": r["cnt"], "last": r["last"]} for r in rows]
|
|
2311
2311
|
# 批量获取自定义会话名称
|
|
2312
2312
|
sids = [s["id"] for s in sessions]
|
|
@@ -2319,9 +2319,9 @@ class ApiServer:
|
|
|
2319
2319
|
preview_rows = conn.execute(
|
|
2320
2320
|
f"SELECT m1.session_id, m1.content FROM memories m1 "
|
|
2321
2321
|
f"INNER JOIN (SELECT session_id, MAX(created_at) as max_t FROM memories "
|
|
2322
|
-
f"WHERE session_id IN ({placeholders}) AND role='user' AND category
|
|
2322
|
+
f"WHERE session_id IN ({placeholders}) AND role='user' AND category = 'session' "
|
|
2323
2323
|
f"GROUP BY session_id) m2 ON m1.session_id = m2.session_id AND m1.created_at = m2.max_t "
|
|
2324
|
-
f"WHERE m1.role='user' AND m1.category
|
|
2324
|
+
f"WHERE m1.role='user' AND m1.category = 'session'",
|
|
2325
2325
|
sids
|
|
2326
2326
|
).fetchall()
|
|
2327
2327
|
for pr in preview_rows:
|
|
@@ -2339,7 +2339,7 @@ class ApiServer:
|
|
|
2339
2339
|
prefix = f"{name}_"
|
|
2340
2340
|
rows = self.core.memory._get_conn().execute(
|
|
2341
2341
|
"SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last FROM memories "
|
|
2342
|
-
"WHERE category
|
|
2342
|
+
"WHERE category = 'session' AND session_id LIKE ? GROUP BY session_id ORDER BY last DESC LIMIT 100",
|
|
2343
2343
|
(prefix + "%",)).fetchall()
|
|
2344
2344
|
sessions = [{"id": r["session_id"], "messages": r["cnt"], "last": r["last"]} for r in rows]
|
|
2345
2345
|
# 批量获取自定义会话名称
|
|
@@ -2473,11 +2473,12 @@ class ApiServer:
|
|
|
2473
2473
|
"role": e.role, "session_id": e.session_id, "created_at": e.created_at,
|
|
2474
2474
|
} for e in results])
|
|
2475
2475
|
|
|
2476
|
-
async def
|
|
2476
|
+
async def handle_memory_list(self, request):
|
|
2477
2477
|
if not self.core.memory: return web.json_response([])
|
|
2478
2478
|
cat = request.query.get("category", "global")
|
|
2479
2479
|
limit = int(request.query.get("limit", "50"))
|
|
2480
|
-
|
|
2480
|
+
session_id = request.query.get("session_id", "")
|
|
2481
|
+
entries = self.core.memory._query(category=cat, session_id=session_id, limit=limit, order_by="created_at DESC")
|
|
2481
2482
|
return web.json_response([{
|
|
2482
2483
|
"id": e.id, "key": e.key, "content": e.content[:500],
|
|
2483
2484
|
"summary": e.summary, "importance": e.importance,
|
|
@@ -2485,7 +2486,7 @@ class ApiServer:
|
|
|
2485
2486
|
"session_id": e.session_id, "created_at": e.created_at,
|
|
2486
2487
|
} for e in entries])
|
|
2487
2488
|
|
|
2488
|
-
async def
|
|
2489
|
+
async def handle_delete_memory(self, request):
|
|
2489
2490
|
if self.core.memory:
|
|
2490
2491
|
self.core.memory._get_conn().execute("DELETE FROM memories WHERE id=?", (request.match_info["mid"],))
|
|
2491
2492
|
self.core.memory._get_conn().commit()
|
|
@@ -3256,7 +3257,7 @@ class ApiServer:
|
|
|
3256
3257
|
"""使用流式LLM调用处理消息,支持完整的agent循环(工具调用/操作执行)+ 实时流式输出
|
|
3257
3258
|
|
|
3258
3259
|
核心改进:
|
|
3259
|
-
-
|
|
3260
|
+
- 每轮迭代结束后增量保存已流式输出的文本到会话记忆(即使客户端断开也不丢失)
|
|
3260
3261
|
- 使用 frequency_penalty 减少大模型重复输出
|
|
3261
3262
|
- 最终保存时使用累积文本而非 final_response,确保完整内容不丢失
|
|
3262
3263
|
|
|
@@ -3390,11 +3391,11 @@ class ApiServer:
|
|
|
3390
3391
|
recent = agent.memory.get_conversation(session_id, limit=3)
|
|
3391
3392
|
_has_assistant = any(e.role == "assistant" for e in recent)
|
|
3392
3393
|
if not _has_assistant and final_response and final_response.strip():
|
|
3393
|
-
agent.memory.
|
|
3394
|
+
agent.memory.add_session(
|
|
3394
3395
|
session_id=session_id, role="assistant", content=final_response,
|
|
3395
3396
|
)
|
|
3396
3397
|
|
|
3397
|
-
# ──
|
|
3398
|
+
# ── 保存工具调用过程到会话记忆(V2 路由) ──
|
|
3398
3399
|
if agent.memory and agent._execution_events:
|
|
3399
3400
|
try:
|
|
3400
3401
|
from core.utils import truncate_str
|
|
@@ -3424,7 +3425,7 @@ class ApiServer:
|
|
|
3424
3425
|
pass # code_result is duplicate of code_exec with final status
|
|
3425
3426
|
if tool_summary_parts:
|
|
3426
3427
|
exec_log = "\n".join(tool_summary_parts)
|
|
3427
|
-
agent.memory.
|
|
3428
|
+
agent.memory.add_session(
|
|
3428
3429
|
session_id=session_id,
|
|
3429
3430
|
role="tool",
|
|
3430
3431
|
content=exec_log,
|
|
@@ -3782,7 +3783,7 @@ class ApiServer:
|
|
|
3782
3783
|
if accumulated_text and len(accumulated_text) > _saved_text_length:
|
|
3783
3784
|
new_save_text = accumulated_text[_saved_text_length:]
|
|
3784
3785
|
if new_save_text.strip():
|
|
3785
|
-
agent.memory.
|
|
3786
|
+
agent.memory.add_session(
|
|
3786
3787
|
session_id=session_id, role="assistant", content=new_save_text,
|
|
3787
3788
|
)
|
|
3788
3789
|
_saved_text_length = len(accumulated_text)
|
|
@@ -3932,7 +3933,7 @@ class ApiServer:
|
|
|
3932
3933
|
if accumulated_text and len(accumulated_text) > _saved_text_length:
|
|
3933
3934
|
new_save_text = accumulated_text[_saved_text_length:]
|
|
3934
3935
|
if new_save_text.strip():
|
|
3935
|
-
agent.memory.
|
|
3936
|
+
agent.memory.add_session(
|
|
3936
3937
|
session_id=session_id, role="assistant", content=new_save_text,
|
|
3937
3938
|
)
|
|
3938
3939
|
_saved_text_length = len(accumulated_text)
|
|
@@ -3956,7 +3957,7 @@ class ApiServer:
|
|
|
3956
3957
|
if agent.memory and content and len(content) > _saved_text_length:
|
|
3957
3958
|
new_save_text = content[_saved_text_length:]
|
|
3958
3959
|
if new_save_text.strip():
|
|
3959
|
-
agent.memory.
|
|
3960
|
+
agent.memory.add_session(
|
|
3960
3961
|
session_id=session_id, role="assistant", content=new_save_text,
|
|
3961
3962
|
)
|
|
3962
3963
|
_saved_text_length = len(content)
|
|
@@ -3976,14 +3977,14 @@ class ApiServer:
|
|
|
3976
3977
|
if agent.memory and saved_response and len(saved_response) > _saved_text_length:
|
|
3977
3978
|
new_text = saved_response[_saved_text_length:]
|
|
3978
3979
|
if new_text.strip():
|
|
3979
|
-
agent.memory.
|
|
3980
|
+
agent.memory.add_session(
|
|
3980
3981
|
session_id=session_id, role="assistant", content=new_text,
|
|
3981
3982
|
)
|
|
3982
3983
|
_saved_text_length = len(saved_response)
|
|
3983
3984
|
elif agent.memory and not saved_response:
|
|
3984
|
-
agent.memory.
|
|
3985
|
+
agent.memory.add_session(session_id=session_id, role="assistant", content="(执行完成,无文本回复)")
|
|
3985
3986
|
|
|
3986
|
-
# ──
|
|
3987
|
+
# ── 保存工具调用过程到会话记忆(供历史记录查看) ──
|
|
3987
3988
|
if agent.memory and agent._execution_events:
|
|
3988
3989
|
try:
|
|
3989
3990
|
from core.utils import truncate_str
|
|
@@ -4013,7 +4014,7 @@ class ApiServer:
|
|
|
4013
4014
|
pass # code_result is duplicate of code_exec with final status
|
|
4014
4015
|
if tool_summary_parts:
|
|
4015
4016
|
exec_log = "\n".join(tool_summary_parts)
|
|
4016
|
-
agent.memory.
|
|
4017
|
+
agent.memory.add_session(
|
|
4017
4018
|
session_id=session_id,
|
|
4018
4019
|
role="tool",
|
|
4019
4020
|
content=exec_log,
|
package/web/ui/chat/chat_main.js
CHANGED
|
@@ -2047,7 +2047,7 @@ function _renderMessagesInner() {
|
|
|
2047
2047
|
<div class="capability"><span class="cap-icon">💻</span> 代码编写与执行</div>
|
|
2048
2048
|
<div class="capability"><span class="cap-icon">🔍</span> 网络搜索与阅读</div>
|
|
2049
2049
|
<div class="capability"><span class="cap-icon">📁</span> 文件管理与操作</div>
|
|
2050
|
-
<div class="capability"><span class="cap-icon">🧠</span>
|
|
2050
|
+
<div class="capability"><span class="cap-icon">🧠</span> 全局记忆系统</div>
|
|
2051
2051
|
<div class="capability"><span class="cap-icon">🌐</span> 多平台接入</div>
|
|
2052
2052
|
<div class="capability"><span class="cap-icon">🛠️</span> 系统命令执行</div>
|
|
2053
2053
|
</div>
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
<div class="capability"><span class="cap-icon">💻</span> 代码编写与执行</div>
|
|
45
45
|
<div class="capability"><span class="cap-icon">🔍</span> 网络搜索与阅读</div>
|
|
46
46
|
<div class="capability"><span class="cap-icon">📁</span> 文件管理与操作</div>
|
|
47
|
-
<div class="capability"><span class="cap-icon">🧠</span>
|
|
47
|
+
<div class="capability"><span class="cap-icon">🧠</span> 全局记忆系统</div>
|
|
48
48
|
<div class="capability"><span class="cap-icon">🌐</span> 多平台接入</div>
|
|
49
49
|
<div class="capability"><span class="cap-icon">🛠️</span> 系统命令执行</div>
|
|
50
50
|
</div>
|
package/web/ui/index.html
CHANGED
|
@@ -346,9 +346,8 @@ async function renderDashboard(){
|
|
|
346
346
|
<div class="stat"><div class="label">已注册技能</div><div class="value">${s.skills||0}</div></div>
|
|
347
347
|
<div class="stat"><div class="label">会话数</div><div class="value">${m.session_count||0}</div></div>
|
|
348
348
|
<div class="stat"><div class="label">任务队列</div><div class="value">${s.queue?.total_submitted||0} 提交</div></div>
|
|
349
|
-
<div class="stat"><div class="label"
|
|
350
|
-
<div class="stat"><div class="label"
|
|
351
|
-
<div class="stat"><div class="label">长期记忆</div><div class="value">${m.long_term_count||0}</div></div>
|
|
349
|
+
<div class="stat"><div class="label">会话记忆</div><div class="value">${m.session_count||0}</div></div>
|
|
350
|
+
<div class="stat"><div class="label">全局记忆</div><div class="value">${m.global_count||0}</div></div>
|
|
352
351
|
</div>`;
|
|
353
352
|
}
|
|
354
353
|
|
|
@@ -913,13 +912,13 @@ function enterSession(sid,agentName){
|
|
|
913
912
|
}
|
|
914
913
|
|
|
915
914
|
// ========== Memory ==========
|
|
916
|
-
let _memCategory='
|
|
915
|
+
let _memCategory='global';
|
|
917
916
|
async function renderMemory(){
|
|
918
917
|
let stats={},lt=[];
|
|
919
918
|
try{stats=await api('/api/memory/stats')}catch(e){stats={error:e.message}}
|
|
920
|
-
try{lt=await api('/api/memory/
|
|
919
|
+
try{lt=await api('/api/memory/list?category='+encodeURIComponent(_memCategory))}catch(e){lt=[]}
|
|
921
920
|
if(stats.error){$('content').innerHTML='<div class="empty" style="color:var(--danger)">记忆系统异常: '+escHtml(stats.error)+'</div>';return}
|
|
922
|
-
const cats=[{k:'
|
|
921
|
+
const cats=[{k:'global',l:'全局记忆'},{k:'session',l:'会话记忆'}];
|
|
923
922
|
let tabHtml='<div class="flex gap-8 mb-8">';
|
|
924
923
|
for(const c of cats){
|
|
925
924
|
const active=c.k===_memCategory?'btn-primary':'btn-ghost';
|
|
@@ -927,22 +926,19 @@ async function renderMemory(){
|
|
|
927
926
|
tabHtml+=`<button class="btn ${active}" onclick="_memCategory='${c.k}';renderMemory()">${c.l} (${count})</button>`;
|
|
928
927
|
}
|
|
929
928
|
tabHtml+='</div>';
|
|
930
|
-
let html=`<div class="grid" style="grid-template-columns:repeat(
|
|
929
|
+
let html=`<div class="grid" style="grid-template-columns:repeat(3,1fr);margin-bottom:16px">
|
|
931
930
|
<div class="stat"><div class="label">总计</div><div class="value">${stats.total_count||0}</div></div>
|
|
932
|
-
<div class="stat"><div class="label"
|
|
933
|
-
<div class="stat"><div class="label"
|
|
934
|
-
<div class="stat"><div class="label">长期</div><div class="value">${stats.long_term_count||0}</div></div></div>`;
|
|
931
|
+
<div class="stat"><div class="label">全局记忆</div><div class="value">${stats.global_count||0}</div></div>
|
|
932
|
+
<div class="stat"><div class="label">会话记忆</div><div class="value">${stats.session_count||0}</div></div></div>`;
|
|
935
933
|
html+=tabHtml;
|
|
936
934
|
html+='<div class="flex gap-8 mb-16"><input id="memSearch" placeholder="搜索记忆..." onkeydown="if(event.key===\'Enter\')searchMemory()" style="max-width:400px"><button class="btn btn-primary" onclick="searchMemory()">搜索</button><button class="btn btn-ghost" onclick="cleanupMemory()">清理过期</button></div>';
|
|
937
935
|
if(lt&<.length){
|
|
938
|
-
const
|
|
939
|
-
const showSession=isShortTerm;
|
|
940
|
-
const showRole=isShortTerm;
|
|
936
|
+
const isSession=_memCategory==='session';
|
|
941
937
|
let thHtml='<tr>';
|
|
942
938
|
thHtml+='<th>Key</th><th>内容</th>';
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
if(!
|
|
939
|
+
thHtml+='<th>角色</th>';
|
|
940
|
+
thHtml+='<th>会话</th>';
|
|
941
|
+
if(!isSession)thHtml+='<th>重要性</th>';
|
|
946
942
|
thHtml+='<th></th></tr>';
|
|
947
943
|
html+='<table>'+thHtml;
|
|
948
944
|
for(const e of lt){
|
|
@@ -952,14 +948,14 @@ async function renderMemory(){
|
|
|
952
948
|
if(content.length>300)contentPreview+=`<span style="color:var(--text3)">... (${content.length}字)</span>`;
|
|
953
949
|
html+='<tr><td style="white-space:nowrap;max-width:120px;overflow:hidden;text-overflow:ellipsis">'+escHtml(e.key||e.role||'-')+'</td>';
|
|
954
950
|
html+='<td style="max-width:600px;word-break:break-word;font-size:13px">'+contentPreview+'</td>';
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
if(!
|
|
951
|
+
html+='<td style="white-space:nowrap">'+escHtml(e.role||'')+'</td>';
|
|
952
|
+
html+='<td style="white-space:nowrap;max-width:140px;overflow:hidden;text-overflow:ellipsis;font-size:12px;color:var(--text3)" title="'+escHtml(e.session_id||'')+'">'+escHtml((e.session_id||'').split('_web_')[0])+'</td>';
|
|
953
|
+
if(!isSession)html+='<td>'+(e.importance!=null?e.importance.toFixed(2):'')+'</td>';
|
|
958
954
|
html+='<td><button class="btn btn-sm btn-danger" onclick="deleteMemory(\''+e.id+'\')">删除</button></td></tr>';
|
|
959
955
|
}
|
|
960
956
|
html+='</table>';
|
|
961
957
|
}else{
|
|
962
|
-
html+='<div class="empty">暂无'+(_memCategory==='
|
|
958
|
+
html+='<div class="empty">暂无'+(_memCategory==='session'?'会话':'全局')+'记忆</div>';
|
|
963
959
|
}
|
|
964
960
|
$('content').innerHTML=html;
|
|
965
961
|
}
|
|
@@ -979,7 +975,7 @@ async function searchMemory(){
|
|
|
979
975
|
}
|
|
980
976
|
html+='<button class="btn btn-ghost mt-8" onclick="renderMemory()">返回</button>';$('content').innerHTML=html;
|
|
981
977
|
}
|
|
982
|
-
async function deleteMemory(id){await api(`/api/memory
|
|
978
|
+
async function deleteMemory(id){await api(`/api/memory/${id}`,{method:'DELETE'});renderMemory()}
|
|
983
979
|
async function cleanupMemory(){const r=await api('/api/memory/cleanup',{method:'POST'});showToast('清理了 '+(r.cleaned||0)+' 条','success');renderMemory()}
|
|
984
980
|
|
|
985
981
|
// ========== 权限管理 ==========
|
|
@@ -1985,7 +1981,7 @@ async function sysLoadPreview(){
|
|
|
1985
1981
|
lines.push('<span class="key">执行引擎:</span> '+escHtml(c.executor.execution_mode||'local')+' | Timeout: '+(c.executor.timeout||300)+'s');
|
|
1986
1982
|
}
|
|
1987
1983
|
if(c.memory){
|
|
1988
|
-
lines.push('<span class="key">记忆:</span>
|
|
1984
|
+
lines.push('<span class="key">记忆:</span> 会话 '+(c.memory.max_session||50)+' 轮 | 自动总结: '+(c.memory.auto_summarize?'开':'关'));
|
|
1989
1985
|
}
|
|
1990
1986
|
if(c.agent){
|
|
1991
1987
|
lines.push('<span class="key">Agent:</span> 最大迭代 '+(c.agent.max_iterations||30)+' | 并行 '+(c.agent.max_parallel||3));
|