jarvis-ai-assistant 0.2.4__py3-none-any.whl → 0.2.6__py3-none-any.whl
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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +80 -16
- jarvis/jarvis_agent/edit_file_handler.py +5 -6
- jarvis/jarvis_code_agent/code_agent.py +17 -24
- jarvis/jarvis_data/config_schema.json +2 -19
- jarvis/jarvis_multi_agent/main.py +1 -0
- jarvis/jarvis_stats/cli.py +72 -5
- jarvis/jarvis_stats/stats.py +175 -70
- jarvis/jarvis_stats/storage.py +53 -1
- jarvis/jarvis_stats/visualizer.py +63 -224
- jarvis/jarvis_tools/cli/main.py +7 -9
- jarvis/jarvis_tools/registry.py +2 -5
- jarvis/jarvis_tools/retrieve_memory.py +206 -0
- jarvis/jarvis_tools/save_memory.py +142 -0
- jarvis/jarvis_utils/config.py +6 -8
- jarvis/jarvis_utils/globals.py +120 -1
- jarvis/jarvis_utils/methodology.py +74 -67
- jarvis/jarvis_utils/utils.py +362 -121
- {jarvis_ai_assistant-0.2.4.dist-info → jarvis_ai_assistant-0.2.6.dist-info}/METADATA +11 -2
- {jarvis_ai_assistant-0.2.4.dist-info → jarvis_ai_assistant-0.2.6.dist-info}/RECORD +24 -22
- {jarvis_ai_assistant-0.2.4.dist-info → jarvis_ai_assistant-0.2.6.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.2.4.dist-info → jarvis_ai_assistant-0.2.6.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.2.4.dist-info → jarvis_ai_assistant-0.2.6.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.2.4.dist-info → jarvis_ai_assistant-0.2.6.dist-info}/top_level.txt +0 -0
@@ -5,9 +5,10 @@
|
|
5
5
|
"""
|
6
6
|
|
7
7
|
import os
|
8
|
-
|
8
|
+
import io
|
9
9
|
from typing import Dict, List, Optional, Any
|
10
10
|
from collections import OrderedDict
|
11
|
+
import plotext as plt
|
11
12
|
from rich.console import Console
|
12
13
|
from rich.table import Table
|
13
14
|
from rich.panel import Panel
|
@@ -51,106 +52,50 @@ class StatsVisualizer:
|
|
51
52
|
show_values: bool = True,
|
52
53
|
) -> str:
|
53
54
|
"""
|
54
|
-
绘制折线图
|
55
|
-
|
56
|
-
Args:
|
57
|
-
data: 数据字典,键为时间标签,值为数值
|
58
|
-
title: 图表标题
|
59
|
-
unit: 单位
|
60
|
-
show_values: 是否显示数值
|
61
|
-
|
62
|
-
Returns:
|
63
|
-
图表字符串
|
55
|
+
使用 plotext 绘制折线图
|
64
56
|
"""
|
65
57
|
if not data:
|
66
58
|
return "无数据可显示"
|
67
59
|
|
68
|
-
# 排序数据
|
69
60
|
sorted_data = OrderedDict(sorted(data.items()))
|
70
|
-
|
71
|
-
# 提取值和标签
|
72
61
|
labels = list(sorted_data.keys())
|
73
62
|
values = list(sorted_data.values())
|
74
63
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
# 如果所有值相同,调整范围
|
80
|
-
if min_val == max_val:
|
81
|
-
if min_val == 0:
|
82
|
-
min_val = -1
|
83
|
-
max_val = 1
|
84
|
-
else:
|
85
|
-
min_val = min_val * 0.9
|
86
|
-
max_val = max_val * 1.1
|
87
|
-
|
88
|
-
# 创建图表画布
|
89
|
-
chart_width = self.width - 10 # 留出y轴标签空间
|
90
|
-
chart_height = self.height - 5 # 留出x轴标签空间
|
91
|
-
|
92
|
-
# 初始化画布
|
93
|
-
canvas = [[" " for _ in range(chart_width)] for _ in range(chart_height)]
|
94
|
-
|
95
|
-
# 计算缩放因子
|
96
|
-
x_scale = (chart_width - 1) / max(len(values) - 1, 1)
|
97
|
-
y_scale = (chart_height - 1) / (max_val - min_val)
|
98
|
-
|
99
|
-
# 绘制坐标轴
|
100
|
-
for i in range(chart_height):
|
101
|
-
canvas[i][0] = "│"
|
102
|
-
for j in range(chart_width):
|
103
|
-
canvas[chart_height - 1][j] = "─"
|
104
|
-
canvas[chart_height - 1][0] = "└"
|
105
|
-
|
106
|
-
# 绘制数据点和连线
|
107
|
-
points = []
|
108
|
-
for i, value in enumerate(values):
|
109
|
-
x = int(i * x_scale) + 1
|
110
|
-
y = chart_height - 1 - int((value - min_val) * y_scale)
|
111
|
-
y = max(0, min(chart_height - 1, y)) # 确保在范围内
|
112
|
-
|
113
|
-
if 0 <= x < chart_width and 0 <= y < chart_height:
|
114
|
-
points.append((x, y, value))
|
115
|
-
canvas[y][x] = "●"
|
116
|
-
|
117
|
-
# 连接数据点
|
118
|
-
for i in range(len(points) - 1):
|
119
|
-
x1, y1, _ = points[i]
|
120
|
-
x2, y2, _ = points[i + 1]
|
121
|
-
self._draw_line(canvas, x1, y1, x2, y2)
|
122
|
-
|
123
|
-
# 构建输出
|
124
|
-
output = []
|
125
|
-
|
126
|
-
# 标题
|
64
|
+
plt.clf()
|
65
|
+
plt.plotsize(self.width, self.height)
|
66
|
+
plt.plot(values)
|
67
|
+
plt.xticks(range(len(labels)), labels)
|
127
68
|
if title:
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
# Y轴标签和图表
|
132
|
-
y_labels = self._generate_y_labels(min_val, max_val, chart_height)
|
133
|
-
|
134
|
-
for i, row in enumerate(canvas):
|
135
|
-
label = y_labels.get(i, "")
|
136
|
-
line = f"{label:>8} " + "".join(row)
|
137
|
-
output.append(line)
|
69
|
+
plt.title(title)
|
70
|
+
if unit:
|
71
|
+
plt.ylabel(unit)
|
138
72
|
|
139
|
-
|
140
|
-
x_labels = self._generate_x_labels(labels, chart_width)
|
141
|
-
output.append(" " * 9 + x_labels)
|
73
|
+
chart = plt.build()
|
142
74
|
|
143
|
-
|
144
|
-
|
145
|
-
|
75
|
+
if show_values and values:
|
76
|
+
min_val = min(values)
|
77
|
+
max_val = max(values)
|
78
|
+
avg_val = sum(values) / len(values)
|
79
|
+
stats_info_text = (
|
80
|
+
f"最小值: {min_val:.2f}, 最大值: {max_val:.2f}, 平均值: {avg_val:.2f}"
|
81
|
+
)
|
146
82
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
83
|
+
# 使用StringIO捕获Panel输出
|
84
|
+
string_io = io.StringIO()
|
85
|
+
temp_console = Console(file=string_io, width=self.width)
|
86
|
+
temp_console.print(
|
87
|
+
Panel(
|
88
|
+
stats_info_text,
|
89
|
+
title="[bold]数据统计[/bold]",
|
90
|
+
expand=False,
|
91
|
+
style="dim",
|
92
|
+
border_style="blue",
|
93
|
+
)
|
151
94
|
)
|
95
|
+
stats_panel_str = string_io.getvalue()
|
152
96
|
|
153
|
-
|
97
|
+
return chart + "\n" + stats_panel_str.strip()
|
98
|
+
return chart
|
154
99
|
|
155
100
|
def plot_bar_chart(
|
156
101
|
self,
|
@@ -160,75 +105,27 @@ class StatsVisualizer:
|
|
160
105
|
horizontal: bool = False,
|
161
106
|
) -> str:
|
162
107
|
"""
|
163
|
-
绘制柱状图
|
164
|
-
|
165
|
-
Args:
|
166
|
-
data: 数据字典
|
167
|
-
title: 图表标题
|
168
|
-
unit: 单位
|
169
|
-
horizontal: 是否横向
|
170
|
-
|
171
|
-
Returns:
|
172
|
-
图表字符串
|
108
|
+
使用 plotext 绘制柱状图
|
173
109
|
"""
|
174
110
|
if not data:
|
175
111
|
return "无数据可显示"
|
176
112
|
|
177
|
-
|
178
|
-
|
179
|
-
# 标题
|
180
|
-
if title:
|
181
|
-
output.append(f"\n{title.center(self.width)}")
|
182
|
-
output.append("=" * self.width)
|
113
|
+
labels = list(data.keys())
|
114
|
+
values = list(data.values())
|
183
115
|
|
184
|
-
|
185
|
-
|
116
|
+
plt.clf()
|
117
|
+
plt.plotsize(self.width, self.height)
|
186
118
|
|
187
119
|
if horizontal:
|
188
|
-
|
189
|
-
max_label_len = max(len(str(k)) for k in data.keys())
|
190
|
-
bar_width = self.width - max_label_len - 15
|
191
|
-
|
192
|
-
for label, value in data.items():
|
193
|
-
bar_len = int((value / max_value) * bar_width)
|
194
|
-
bar = "█" * bar_len
|
195
|
-
output.append(f"{str(label):>{max_label_len}} │{bar} {value:.2f}")
|
120
|
+
plt.bar(labels, values, orientation="horizontal")
|
196
121
|
else:
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
values = list(data.values())
|
201
|
-
|
202
|
-
# 创建画布
|
203
|
-
canvas = [[" " for _ in range(len(labels) * 4)] for _ in range(bar_height)]
|
204
|
-
|
205
|
-
# 绘制柱子
|
206
|
-
for i, (label, value) in enumerate(zip(labels, values)):
|
207
|
-
height = int((value / max_value) * bar_height)
|
208
|
-
x = i * 4 + 1
|
209
|
-
|
210
|
-
for y in range(bar_height - height, bar_height):
|
211
|
-
if y >= 0 and x < len(canvas[0]):
|
212
|
-
canvas[y][x] = "█"
|
213
|
-
if x + 1 < len(canvas[0]):
|
214
|
-
canvas[y][x + 1] = "█"
|
215
|
-
|
216
|
-
# 输出画布
|
217
|
-
for row in canvas:
|
218
|
-
output.append("".join(row))
|
219
|
-
|
220
|
-
# X轴标签
|
221
|
-
label_line = ""
|
222
|
-
for i, label in enumerate(labels):
|
223
|
-
x = i * 4
|
224
|
-
label_line += f"{label[:3]:>4}"
|
225
|
-
output.append(label_line)
|
226
|
-
|
227
|
-
# 单位
|
122
|
+
plt.bar(labels, values)
|
123
|
+
if title:
|
124
|
+
plt.title(title)
|
228
125
|
if unit:
|
229
|
-
|
126
|
+
plt.ylabel(unit)
|
230
127
|
|
231
|
-
return
|
128
|
+
return plt.build()
|
232
129
|
|
233
130
|
def show_summary(
|
234
131
|
self,
|
@@ -346,98 +243,40 @@ class StatsVisualizer:
|
|
346
243
|
if unit:
|
347
244
|
info_items.append(f"单位: {unit}")
|
348
245
|
if start_time and end_time:
|
349
|
-
info_items.append(f"时间范围: {start_time} ~ {end_time}")
|
246
|
+
info_items.append(f"时间范围: [cyan]{start_time}[/] ~ [cyan]{end_time}[/]")
|
350
247
|
if tags_filter:
|
351
248
|
filter_str = ", ".join([f"{k}={v}" for k, v in tags_filter.items()])
|
352
249
|
info_items.append(f"过滤条件: {filter_str}")
|
353
250
|
|
354
251
|
if info_items:
|
355
|
-
self.console.print(
|
252
|
+
self.console.print(
|
253
|
+
Panel(
|
254
|
+
" | ".join(info_items),
|
255
|
+
title="[bold]查询详情[/bold]",
|
256
|
+
expand=False,
|
257
|
+
style="dim",
|
258
|
+
border_style="green",
|
259
|
+
)
|
260
|
+
)
|
356
261
|
|
357
262
|
# 统计信息
|
358
263
|
if len(records) > 0:
|
359
264
|
values = [r["value"] for r in records]
|
360
|
-
|
265
|
+
stats_info_text = (
|
361
266
|
f"总记录数: {len(records)} | "
|
362
267
|
f"显示: {len(display_records)} | "
|
363
268
|
f"最小值: {min(values):.2f} | "
|
364
269
|
f"最大值: {max(values):.2f} | "
|
365
270
|
f"平均值: {sum(values)/len(values):.2f}"
|
366
271
|
)
|
367
|
-
self.console.print(
|
272
|
+
self.console.print(
|
273
|
+
Panel(
|
274
|
+
stats_info_text,
|
275
|
+
title="[bold]数据统计[/bold]",
|
276
|
+
expand=False,
|
277
|
+
style="dim",
|
278
|
+
border_style="blue",
|
279
|
+
)
|
280
|
+
)
|
368
281
|
|
369
282
|
return ""
|
370
|
-
|
371
|
-
def _draw_line(self, canvas: List[List[str]], x1: int, y1: int, x2: int, y2: int):
|
372
|
-
"""在画布上绘制线条"""
|
373
|
-
# 使用Bresenham算法绘制线条
|
374
|
-
dx = abs(x2 - x1)
|
375
|
-
dy = abs(y2 - y1)
|
376
|
-
sx = 1 if x1 < x2 else -1
|
377
|
-
sy = 1 if y1 < y2 else -1
|
378
|
-
err = dx - dy
|
379
|
-
|
380
|
-
x, y = x1, y1
|
381
|
-
|
382
|
-
while True:
|
383
|
-
if 0 <= x < len(canvas[0]) and 0 <= y < len(canvas):
|
384
|
-
if canvas[y][x] == " ":
|
385
|
-
if dx > dy:
|
386
|
-
canvas[y][x] = "─"
|
387
|
-
else:
|
388
|
-
canvas[y][x] = "│"
|
389
|
-
|
390
|
-
if x == x2 and y == y2:
|
391
|
-
break
|
392
|
-
|
393
|
-
e2 = 2 * err
|
394
|
-
if e2 > -dy:
|
395
|
-
err -= dy
|
396
|
-
x += sx
|
397
|
-
if e2 < dx:
|
398
|
-
err += dx
|
399
|
-
y += sy
|
400
|
-
|
401
|
-
def _generate_y_labels(
|
402
|
-
self, min_val: float, max_val: float, height: int
|
403
|
-
) -> Dict[int, str]:
|
404
|
-
"""生成Y轴标签"""
|
405
|
-
labels = {}
|
406
|
-
|
407
|
-
# 在顶部、中部和底部放置标签
|
408
|
-
positions = [0, height // 2, height - 1]
|
409
|
-
values = [max_val, (max_val + min_val) / 2, min_val]
|
410
|
-
|
411
|
-
for pos, val in zip(positions, values):
|
412
|
-
labels[pos] = f"{val:.1f}"
|
413
|
-
|
414
|
-
return labels
|
415
|
-
|
416
|
-
def _generate_x_labels(self, labels: List[str], width: int) -> str:
|
417
|
-
"""生成X轴标签"""
|
418
|
-
if not labels:
|
419
|
-
return ""
|
420
|
-
|
421
|
-
# 简化标签(如果是时间格式)
|
422
|
-
simplified_labels = []
|
423
|
-
for label in labels:
|
424
|
-
if len(label) > 10:
|
425
|
-
# 尝试提取时间部分
|
426
|
-
parts = label.split()
|
427
|
-
if len(parts) >= 2:
|
428
|
-
simplified_labels.append(parts[1][:5]) # 只取时间
|
429
|
-
else:
|
430
|
-
simplified_labels.append(label[:8])
|
431
|
-
else:
|
432
|
-
simplified_labels.append(label)
|
433
|
-
|
434
|
-
# 根据空间决定显示多少标签
|
435
|
-
max_labels = width // 10
|
436
|
-
step = max(1, len(simplified_labels) // max_labels)
|
437
|
-
|
438
|
-
label_line = ""
|
439
|
-
for i in range(0, len(simplified_labels), step):
|
440
|
-
if i < len(simplified_labels):
|
441
|
-
label_line += f"{simplified_labels[i]:>10}"
|
442
|
-
|
443
|
-
return label_line
|
jarvis/jarvis_tools/cli/main.py
CHANGED
@@ -48,8 +48,6 @@ def stat_tools(
|
|
48
48
|
"""显示工具调用统计信息"""
|
49
49
|
from jarvis.jarvis_stats.stats import StatsManager
|
50
50
|
|
51
|
-
stats_manager = StatsManager()
|
52
|
-
|
53
51
|
if format == "table":
|
54
52
|
registry = ToolRegistry()
|
55
53
|
stats = registry._get_tool_stats()
|
@@ -78,13 +76,13 @@ def stat_tools(
|
|
78
76
|
# 使用 stats 系统的高级功能
|
79
77
|
PrettyOutput.section("工具组统计", OutputType.SYSTEM)
|
80
78
|
# 显示所有标记为 tool 组的指标
|
81
|
-
metrics =
|
79
|
+
metrics = StatsManager.list_metrics()
|
82
80
|
tool_metrics = []
|
83
81
|
|
84
82
|
for metric in metrics:
|
85
83
|
# 检查是否是工具组的指标
|
86
84
|
if last_days:
|
87
|
-
stats_data =
|
85
|
+
stats_data = StatsManager.get_stats(
|
88
86
|
metric_name=metric,
|
89
87
|
last_days=last_days,
|
90
88
|
tags={"group": "tool"}
|
@@ -92,7 +90,7 @@ def stat_tools(
|
|
92
90
|
else:
|
93
91
|
# 获取所有历史数据
|
94
92
|
from datetime import datetime
|
95
|
-
stats_data =
|
93
|
+
stats_data = StatsManager.get_stats(
|
96
94
|
metric_name=metric,
|
97
95
|
start_time=datetime(2000, 1, 1),
|
98
96
|
end_time=datetime.now(),
|
@@ -105,10 +103,10 @@ def stat_tools(
|
|
105
103
|
for metric in tool_metrics:
|
106
104
|
if format == "chart":
|
107
105
|
if last_days:
|
108
|
-
|
106
|
+
StatsManager.plot(metric, last_days=last_days, tags={"group": "tool"})
|
109
107
|
else:
|
110
108
|
from datetime import datetime
|
111
|
-
|
109
|
+
StatsManager.plot(
|
112
110
|
metric,
|
113
111
|
start_time=datetime(2000, 1, 1),
|
114
112
|
end_time=datetime.now(),
|
@@ -116,10 +114,10 @@ def stat_tools(
|
|
116
114
|
)
|
117
115
|
elif format == "summary":
|
118
116
|
if last_days:
|
119
|
-
|
117
|
+
StatsManager.show(metric, last_days=last_days, format="summary", tags={"group": "tool"})
|
120
118
|
else:
|
121
119
|
from datetime import datetime
|
122
|
-
|
120
|
+
StatsManager.show(
|
123
121
|
metric,
|
124
122
|
start_time=datetime(2000, 1, 1),
|
125
123
|
end_time=datetime.now(),
|
jarvis/jarvis_tools/registry.py
CHANGED
@@ -203,8 +203,6 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
203
203
|
from jarvis.jarvis_stats.stats import StatsManager
|
204
204
|
from datetime import datetime, timedelta
|
205
205
|
|
206
|
-
stats_manager = StatsManager()
|
207
|
-
|
208
206
|
# 获取所有工具的统计数据
|
209
207
|
tool_stats = {}
|
210
208
|
tools = self.get_all_tools()
|
@@ -216,7 +214,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
216
214
|
for tool in tools:
|
217
215
|
tool_name = tool["name"]
|
218
216
|
# 获取该工具的统计数据
|
219
|
-
stats_data =
|
217
|
+
stats_data = StatsManager.get_stats(
|
220
218
|
metric_name=tool_name,
|
221
219
|
start_time=start_time,
|
222
220
|
end_time=end_time,
|
@@ -236,8 +234,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
236
234
|
"""更新工具调用统计"""
|
237
235
|
from jarvis.jarvis_stats.stats import StatsManager
|
238
236
|
|
239
|
-
|
240
|
-
stats_manager.increment(name, group="tool")
|
237
|
+
StatsManager.increment(name, group="tool")
|
241
238
|
|
242
239
|
def use_tools(self, name: List[str]) -> None:
|
243
240
|
"""使用指定工具
|
@@ -0,0 +1,206 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
import json
|
3
|
+
import random
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Any, Dict, List, Optional
|
6
|
+
|
7
|
+
from jarvis.jarvis_utils.config import get_data_dir
|
8
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
9
|
+
from jarvis.jarvis_utils.globals import get_short_term_memories
|
10
|
+
|
11
|
+
|
12
|
+
class RetrieveMemoryTool:
|
13
|
+
"""检索记忆工具,用于从长短期记忆系统中检索信息"""
|
14
|
+
|
15
|
+
name = "retrieve_memory"
|
16
|
+
description = """从长短期记忆系统中检索信息。
|
17
|
+
|
18
|
+
支持的记忆类型:
|
19
|
+
- project_long_term: 项目长期记忆(与当前项目相关的信息)
|
20
|
+
- global_long_term: 全局长期记忆(通用的信息、用户喜好、知识、方法等)
|
21
|
+
- short_term: 短期记忆(当前任务相关的信息)
|
22
|
+
- all: 从所有类型中检索
|
23
|
+
|
24
|
+
可以通过标签过滤检索结果,支持多个标签(满足任一标签即可)
|
25
|
+
"""
|
26
|
+
|
27
|
+
parameters = {
|
28
|
+
"type": "object",
|
29
|
+
"properties": {
|
30
|
+
"memory_types": {
|
31
|
+
"type": "array",
|
32
|
+
"items": {
|
33
|
+
"type": "string",
|
34
|
+
"enum": [
|
35
|
+
"project_long_term",
|
36
|
+
"global_long_term",
|
37
|
+
"short_term",
|
38
|
+
"all",
|
39
|
+
],
|
40
|
+
},
|
41
|
+
"description": "要检索的记忆类型列表,如果包含'all'则检索所有类型",
|
42
|
+
},
|
43
|
+
"tags": {
|
44
|
+
"type": "array",
|
45
|
+
"items": {"type": "string"},
|
46
|
+
"description": "用于过滤的标签列表(可选)",
|
47
|
+
},
|
48
|
+
"limit": {
|
49
|
+
"type": "integer",
|
50
|
+
"description": "返回结果的最大数量(可选,默认返回所有)",
|
51
|
+
"minimum": 1,
|
52
|
+
},
|
53
|
+
},
|
54
|
+
"required": ["memory_types"],
|
55
|
+
}
|
56
|
+
|
57
|
+
def __init__(self):
|
58
|
+
"""初始化检索记忆工具"""
|
59
|
+
self.project_memory_dir = Path(".jarvis/memory")
|
60
|
+
self.global_memory_dir = Path(get_data_dir()) / "memory"
|
61
|
+
|
62
|
+
def _get_memory_dir(self, memory_type: str) -> Path:
|
63
|
+
"""根据记忆类型获取存储目录"""
|
64
|
+
if memory_type == "project_long_term":
|
65
|
+
return self.project_memory_dir
|
66
|
+
elif memory_type in ["global_long_term", "short_term"]:
|
67
|
+
return self.global_memory_dir / memory_type
|
68
|
+
else:
|
69
|
+
raise ValueError(f"未知的记忆类型: {memory_type}")
|
70
|
+
|
71
|
+
def _retrieve_from_type(
|
72
|
+
self, memory_type: str, tags: Optional[List[str]] = None
|
73
|
+
) -> List[Dict[str, Any]]:
|
74
|
+
"""从指定类型中检索记忆"""
|
75
|
+
memories: List[Dict[str, Any]] = []
|
76
|
+
|
77
|
+
if memory_type == "short_term":
|
78
|
+
# 从全局变量获取短期记忆
|
79
|
+
memories = get_short_term_memories(tags)
|
80
|
+
else:
|
81
|
+
# 从文件系统获取长期记忆
|
82
|
+
memory_dir = self._get_memory_dir(memory_type)
|
83
|
+
|
84
|
+
if not memory_dir.exists():
|
85
|
+
return memories
|
86
|
+
|
87
|
+
# 遍历记忆文件
|
88
|
+
for memory_file in memory_dir.glob("*.json"):
|
89
|
+
try:
|
90
|
+
with open(memory_file, "r", encoding="utf-8") as f:
|
91
|
+
memory_data = json.load(f)
|
92
|
+
|
93
|
+
# 如果指定了标签,检查是否匹配
|
94
|
+
if tags:
|
95
|
+
memory_tags = memory_data.get("tags", [])
|
96
|
+
if not any(tag in memory_tags for tag in tags):
|
97
|
+
continue
|
98
|
+
|
99
|
+
memories.append(memory_data)
|
100
|
+
except Exception as e:
|
101
|
+
PrettyOutput.print(
|
102
|
+
f"读取记忆文件 {memory_file} 失败: {str(e)}", OutputType.WARNING
|
103
|
+
)
|
104
|
+
|
105
|
+
return memories
|
106
|
+
|
107
|
+
def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
108
|
+
"""执行检索记忆操作"""
|
109
|
+
try:
|
110
|
+
memory_types = args.get("memory_types", [])
|
111
|
+
tags = args.get("tags", [])
|
112
|
+
limit = args.get("limit", None)
|
113
|
+
|
114
|
+
# 确定要检索的记忆类型
|
115
|
+
if "all" in memory_types:
|
116
|
+
types_to_search = [
|
117
|
+
"project_long_term",
|
118
|
+
"global_long_term",
|
119
|
+
"short_term",
|
120
|
+
]
|
121
|
+
else:
|
122
|
+
types_to_search = memory_types
|
123
|
+
|
124
|
+
# 从各个类型中检索记忆
|
125
|
+
all_memories = []
|
126
|
+
for memory_type in types_to_search:
|
127
|
+
memories = self._retrieve_from_type(memory_type, tags)
|
128
|
+
all_memories.extend(memories)
|
129
|
+
|
130
|
+
# 按创建时间排序(最新的在前)
|
131
|
+
all_memories.sort(key=lambda x: x.get("created_at", ""), reverse=True)
|
132
|
+
|
133
|
+
# 限制最多返回50条记忆,随机选取
|
134
|
+
if len(all_memories) > 50:
|
135
|
+
all_memories = random.sample(all_memories, 50)
|
136
|
+
# 重新排序,保持时间顺序
|
137
|
+
all_memories.sort(key=lambda x: x.get("created_at", ""), reverse=True)
|
138
|
+
|
139
|
+
# 如果指定了限制,只返回前N个
|
140
|
+
if limit:
|
141
|
+
all_memories = all_memories[:limit]
|
142
|
+
|
143
|
+
# 打印结果摘要
|
144
|
+
PrettyOutput.print(f"检索到 {len(all_memories)} 条记忆", OutputType.INFO)
|
145
|
+
|
146
|
+
if tags:
|
147
|
+
PrettyOutput.print(f"使用标签过滤: {', '.join(tags)}", OutputType.INFO)
|
148
|
+
|
149
|
+
# 格式化为Markdown输出
|
150
|
+
markdown_output = f"# 记忆检索结果\n\n"
|
151
|
+
markdown_output += f"**检索到 {len(all_memories)} 条记忆**\n\n"
|
152
|
+
|
153
|
+
if tags:
|
154
|
+
markdown_output += f"**使用标签过滤**: {', '.join(tags)}\n\n"
|
155
|
+
|
156
|
+
markdown_output += f"**记忆类型**: {', '.join(types_to_search)}\n\n"
|
157
|
+
|
158
|
+
markdown_output += "---\n\n"
|
159
|
+
|
160
|
+
# 输出所有记忆
|
161
|
+
for i, memory in enumerate(all_memories):
|
162
|
+
markdown_output += f"## {i+1}. {memory.get('id', '未知ID')}\n\n"
|
163
|
+
markdown_output += f"**类型**: {memory.get('type', '未知类型')}\n\n"
|
164
|
+
markdown_output += f"**标签**: {', '.join(memory.get('tags', []))}\n\n"
|
165
|
+
markdown_output += f"**创建时间**: {memory.get('created_at', '未知时间')}\n\n"
|
166
|
+
|
167
|
+
# 内容部分
|
168
|
+
content = memory.get('content', '')
|
169
|
+
if content:
|
170
|
+
markdown_output += f"**内容**:\n\n{content}\n\n"
|
171
|
+
|
172
|
+
# 如果有额外的元数据
|
173
|
+
metadata = {k: v for k, v in memory.items()
|
174
|
+
if k not in ['id', 'type', 'tags', 'created_at', 'content']}
|
175
|
+
if metadata:
|
176
|
+
markdown_output += f"**其他信息**:\n"
|
177
|
+
for key, value in metadata.items():
|
178
|
+
markdown_output += f"- {key}: {value}\n"
|
179
|
+
markdown_output += "\n"
|
180
|
+
|
181
|
+
markdown_output += "---\n\n"
|
182
|
+
|
183
|
+
# 如果记忆较多,在终端显示摘要
|
184
|
+
if len(all_memories) > 5:
|
185
|
+
PrettyOutput.print(f"记忆较多,仅显示前5条摘要:", OutputType.INFO)
|
186
|
+
for i, memory in enumerate(all_memories[:5]):
|
187
|
+
content_preview = memory.get("content", "")[:100]
|
188
|
+
if len(memory.get("content", "")) > 100:
|
189
|
+
content_preview += "..."
|
190
|
+
PrettyOutput.print(
|
191
|
+
f"{i+1}. [{memory.get('type')}] {memory.get('id')}\n"
|
192
|
+
f" 标签: {', '.join(memory.get('tags', []))}\n"
|
193
|
+
f" 内容: {content_preview}",
|
194
|
+
OutputType.INFO,
|
195
|
+
)
|
196
|
+
|
197
|
+
return {
|
198
|
+
"success": True,
|
199
|
+
"stdout": markdown_output,
|
200
|
+
"stderr": "",
|
201
|
+
}
|
202
|
+
|
203
|
+
except Exception as e:
|
204
|
+
error_msg = f"检索记忆失败: {str(e)}"
|
205
|
+
PrettyOutput.print(error_msg, OutputType.ERROR)
|
206
|
+
return {"success": False, "stdout": "", "stderr": error_msg}
|