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.
@@ -0,0 +1,142 @@
1
+ # -*- coding: utf-8 -*-
2
+ import json
3
+ from datetime import datetime
4
+ from pathlib import Path
5
+ from typing import Any, Dict, List
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 add_short_term_memory
10
+
11
+
12
+ class SaveMemoryTool:
13
+ """保存记忆工具,用于将信息保存到长短期记忆系统"""
14
+
15
+ name = "save_memory"
16
+ description = """保存信息到长短期记忆系统。
17
+
18
+ 支持的记忆类型:
19
+ - project_long_term: 项目长期记忆(与当前项目相关的信息)
20
+ - global_long_term: 全局长期记忆(通用的信息、用户喜好、知识、方法等)
21
+ - short_term: 短期记忆(当前任务相关的信息)
22
+
23
+ 项目长期记忆存储在当前目录的 .jarvis/memory 下
24
+ 全局长期记忆和短期记忆存储在数据目录的 memory 子目录下
25
+ """
26
+
27
+ parameters = {
28
+ "type": "object",
29
+ "properties": {
30
+ "memory_type": {
31
+ "type": "string",
32
+ "enum": ["project_long_term", "global_long_term", "short_term"],
33
+ "description": "记忆类型",
34
+ },
35
+ "tags": {
36
+ "type": "array",
37
+ "items": {"type": "string"},
38
+ "description": "用于索引记忆的标签列表",
39
+ },
40
+ "content": {"type": "string", "description": "要保存的记忆内容"},
41
+ },
42
+ "required": ["memory_type", "tags", "content"],
43
+ }
44
+
45
+ def __init__(self):
46
+ """初始化保存记忆工具"""
47
+ self.project_memory_dir = Path(".jarvis/memory")
48
+ self.global_memory_dir = Path(get_data_dir()) / "memory"
49
+
50
+ def _get_memory_dir(self, memory_type: str) -> Path:
51
+ """根据记忆类型获取存储目录"""
52
+ if memory_type == "project_long_term":
53
+ return self.project_memory_dir
54
+ elif memory_type in ["global_long_term", "short_term"]:
55
+ return self.global_memory_dir / memory_type
56
+ else:
57
+ raise ValueError(f"未知的记忆类型: {memory_type}")
58
+
59
+ def _generate_memory_id(self) -> str:
60
+ """生成唯一的记忆ID"""
61
+ return datetime.now().strftime("%Y%m%d_%H%M%S_%f")
62
+
63
+ def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
64
+ """执行保存记忆操作"""
65
+ try:
66
+ memory_type = args["memory_type"]
67
+ tags = args.get("tags", [])
68
+ content = args.get("content", "")
69
+
70
+ # 生成记忆ID
71
+ memory_id = self._generate_memory_id()
72
+
73
+ # 创建记忆对象
74
+ memory_data = {
75
+ "id": memory_id,
76
+ "type": memory_type,
77
+ "tags": tags,
78
+ "content": content,
79
+ "created_at": datetime.now().isoformat(),
80
+ "updated_at": datetime.now().isoformat(),
81
+ }
82
+
83
+ if memory_type == "short_term":
84
+ # 短期记忆保存到全局变量
85
+ add_short_term_memory(memory_data)
86
+
87
+ # 打印成功信息
88
+ PrettyOutput.print(
89
+ f"短期记忆已保存\n"
90
+ f"ID: {memory_id}\n"
91
+ f"类型: {memory_type}\n"
92
+ f"标签: {', '.join(tags)}\n"
93
+ f"存储位置: 内存(非持久化)",
94
+ OutputType.SUCCESS,
95
+ )
96
+
97
+ result = {
98
+ "memory_id": memory_id,
99
+ "memory_type": memory_type,
100
+ "tags": tags,
101
+ "storage": "memory",
102
+ "message": f"短期记忆已成功保存到内存,ID: {memory_id}",
103
+ }
104
+ else:
105
+ # 长期记忆保存到文件
106
+ # 获取存储目录并确保存在
107
+ memory_dir = self._get_memory_dir(memory_type)
108
+ memory_dir.mkdir(parents=True, exist_ok=True)
109
+
110
+ # 保存记忆文件
111
+ memory_file = memory_dir / f"{memory_id}.json"
112
+ with open(memory_file, "w", encoding="utf-8") as f:
113
+ json.dump(memory_data, f, ensure_ascii=False, indent=2)
114
+
115
+ # 打印成功信息
116
+ PrettyOutput.print(
117
+ f"记忆已保存\n"
118
+ f"ID: {memory_id}\n"
119
+ f"类型: {memory_type}\n"
120
+ f"标签: {', '.join(tags)}\n"
121
+ f"位置: {memory_file}",
122
+ OutputType.SUCCESS,
123
+ )
124
+
125
+ result = {
126
+ "memory_id": memory_id,
127
+ "memory_type": memory_type,
128
+ "tags": tags,
129
+ "file_path": str(memory_file),
130
+ "message": f"记忆已成功保存,ID: {memory_id}",
131
+ }
132
+
133
+ return {
134
+ "success": True,
135
+ "stdout": json.dumps(result, ensure_ascii=False, indent=2),
136
+ "stderr": "",
137
+ }
138
+
139
+ except Exception as e:
140
+ error_msg = f"保存记忆失败: {str(e)}"
141
+ PrettyOutput.print(error_msg, OutputType.ERROR)
142
+ return {"success": False, "stdout": "", "stderr": error_msg}
@@ -81,10 +81,10 @@ def get_max_token_count(model_group_override: Optional[str] = None) -> int:
81
81
  获取模型允许的最大token数量。
82
82
 
83
83
  返回:
84
- int: 模型能处理的最大token数量。
84
+ int: 模型能处理的最大token数量,为最大输入token数量的100倍。
85
85
  """
86
- config = _get_resolved_model_config(model_group_override)
87
- return int(config.get("JARVIS_MAX_TOKEN_COUNT", "960000"))
86
+ max_input_tokens = get_max_input_token_count(model_group_override)
87
+ return max_input_tokens * 100
88
88
 
89
89
 
90
90
  def get_max_input_token_count(model_group_override: Optional[str] = None) -> int:
@@ -151,9 +151,7 @@ def _get_resolved_model_config(
151
151
  "JARVIS_MODEL",
152
152
  "JARVIS_THINKING_PLATFORM",
153
153
  "JARVIS_THINKING_MODEL",
154
- "JARVIS_MAX_TOKEN_COUNT",
155
154
  "JARVIS_MAX_INPUT_TOKEN_COUNT",
156
- "JARVIS_MAX_BIG_CONTENT_SIZE",
157
155
  ]:
158
156
  if key in GLOBAL_CONFIG_DATA:
159
157
  resolved_config[key] = GLOBAL_CONFIG_DATA[key]
@@ -249,10 +247,10 @@ def get_max_big_content_size(model_group_override: Optional[str] = None) -> int:
249
247
  获取最大大内容大小。
250
248
 
251
249
  返回:
252
- int: 最大大内容大小
250
+ int: 最大大内容大小,为最大输入token数量的5倍
253
251
  """
254
- config = _get_resolved_model_config(model_group_override)
255
- return int(config.get("JARVIS_MAX_BIG_CONTENT_SIZE", "160000"))
252
+ max_input_tokens = get_max_input_token_count(model_group_override)
253
+ return max_input_tokens * 5
256
254
 
257
255
 
258
256
  def get_pretty_output() -> bool:
@@ -10,11 +10,16 @@
10
10
  import os
11
11
 
12
12
  # 全局变量:保存消息历史
13
- from typing import Any, Dict, Set, List
13
+ from typing import Any, Dict, Set, List, Optional
14
+ from datetime import datetime
14
15
 
15
16
  message_history: List[str] = []
16
17
  MAX_HISTORY_SIZE = 50
17
18
 
19
+ # 短期记忆存储
20
+ short_term_memories: List[Dict[str, Any]] = []
21
+ MAX_SHORT_TERM_MEMORIES = 100
22
+
18
23
  import colorama
19
24
  from rich.console import Console
20
25
  from rich.theme import Theme
@@ -207,3 +212,117 @@ def get_message_history() -> List[str]:
207
212
  """
208
213
  global message_history
209
214
  return message_history
215
+
216
+
217
+ def add_short_term_memory(memory_data: Dict[str, Any]) -> None:
218
+ """
219
+ 添加短期记忆到全局存储。
220
+
221
+ 参数:
222
+ memory_data: 包含记忆信息的字典
223
+ """
224
+ global short_term_memories
225
+ short_term_memories.append(memory_data)
226
+ # 如果超过最大数量,删除最旧的记忆
227
+ if len(short_term_memories) > MAX_SHORT_TERM_MEMORIES:
228
+ short_term_memories.pop(0)
229
+
230
+
231
+ def get_short_term_memories(tags: Optional[List[str]] = None) -> List[Dict[str, Any]]:
232
+ """
233
+ 获取短期记忆,可选择按标签过滤。
234
+
235
+ 参数:
236
+ tags: 用于过滤的标签列表(可选)
237
+
238
+ 返回:
239
+ List[Dict[str, Any]]: 符合条件的短期记忆列表
240
+ """
241
+ global short_term_memories
242
+ if not tags:
243
+ return short_term_memories.copy()
244
+
245
+ # 按标签过滤
246
+ filtered_memories = []
247
+ for memory in short_term_memories:
248
+ memory_tags = memory.get("tags", [])
249
+ if any(tag in memory_tags for tag in tags):
250
+ filtered_memories.append(memory)
251
+
252
+ return filtered_memories
253
+
254
+
255
+ def clear_short_term_memories() -> None:
256
+ """
257
+ 清空所有短期记忆。
258
+ """
259
+ global short_term_memories
260
+ short_term_memories.clear()
261
+
262
+
263
+ def get_all_memory_tags() -> Dict[str, List[str]]:
264
+ """
265
+ 获取所有记忆类型中的标签集合。
266
+ 每个类型最多返回200个标签,超过时随机提取。
267
+
268
+ 返回:
269
+ Dict[str, List[str]]: 按记忆类型分组的标签列表
270
+ """
271
+ from pathlib import Path
272
+ import json
273
+ import random
274
+ from jarvis.jarvis_utils.config import get_data_dir
275
+
276
+ tags_by_type = {
277
+ "short_term": [],
278
+ "project_long_term": [],
279
+ "global_long_term": []
280
+ }
281
+
282
+ MAX_TAGS_PER_TYPE = 200
283
+
284
+ # 获取短期记忆标签
285
+ short_term_tags = set()
286
+ for memory in short_term_memories:
287
+ short_term_tags.update(memory.get("tags", []))
288
+ short_term_tags_list = sorted(list(short_term_tags))
289
+ if len(short_term_tags_list) > MAX_TAGS_PER_TYPE:
290
+ tags_by_type["short_term"] = sorted(random.sample(short_term_tags_list, MAX_TAGS_PER_TYPE))
291
+ else:
292
+ tags_by_type["short_term"] = short_term_tags_list
293
+
294
+ # 获取项目长期记忆标签
295
+ project_memory_dir = Path(".jarvis/memory")
296
+ if project_memory_dir.exists():
297
+ project_tags = set()
298
+ for memory_file in project_memory_dir.glob("*.json"):
299
+ try:
300
+ with open(memory_file, "r", encoding="utf-8") as f:
301
+ memory_data = json.load(f)
302
+ project_tags.update(memory_data.get("tags", []))
303
+ except Exception:
304
+ pass
305
+ project_tags_list = sorted(list(project_tags))
306
+ if len(project_tags_list) > MAX_TAGS_PER_TYPE:
307
+ tags_by_type["project_long_term"] = sorted(random.sample(project_tags_list, MAX_TAGS_PER_TYPE))
308
+ else:
309
+ tags_by_type["project_long_term"] = project_tags_list
310
+
311
+ # 获取全局长期记忆标签
312
+ global_memory_dir = Path(get_data_dir()) / "memory" / "global_long_term"
313
+ if global_memory_dir.exists():
314
+ global_tags = set()
315
+ for memory_file in global_memory_dir.glob("*.json"):
316
+ try:
317
+ with open(memory_file, "r", encoding="utf-8") as f:
318
+ memory_data = json.load(f)
319
+ global_tags.update(memory_data.get("tags", []))
320
+ except Exception:
321
+ pass
322
+ global_tags_list = sorted(list(global_tags))
323
+ if len(global_tags_list) > MAX_TAGS_PER_TYPE:
324
+ tags_by_type["global_long_term"] = sorted(random.sample(global_tags_list, MAX_TAGS_PER_TYPE))
325
+ else:
326
+ tags_by_type["global_long_term"] = global_tags_list
327
+
328
+ return tags_by_type
@@ -149,6 +149,7 @@ def load_methodology(user_input: str, tool_registery: Optional[Any] = None) -> s
149
149
 
150
150
  参数:
151
151
  user_input: 用户输入文本,用于提示大模型
152
+ tool_registery: 工具注册表,用于获取工具列表
152
153
 
153
154
  返回:
154
155
  str: 相关的方法论提示,如果未找到方法论则返回空字符串
@@ -170,70 +171,87 @@ def load_methodology(user_input: str, tool_registery: Optional[Any] = None) -> s
170
171
  return ""
171
172
  print(f"✅ 加载方法论文件完成 (共 {len(methodologies)} 个)")
172
173
 
173
- # 获取当前平台
174
- agent = get_agent(current_agent_name)
175
- if agent:
176
- platform = agent.model
177
- model_group = agent.model.model_group
178
- else:
179
- platform = PlatformRegistry().get_normal_platform()
180
- model_group = None
181
- platform.set_suppress_output(False)
182
- if not platform:
183
- return ""
174
+ platform = PlatformRegistry().get_normal_platform()
175
+ platform.set_suppress_output(True)
176
+
177
+ # 步骤1:获取所有方法论的标题
178
+ methodology_titles = list(methodologies.keys())
184
179
 
185
- # 构建基础提示信息
186
- base_prompt = f"""以下是所有可用的方法论内容:
180
+ # 步骤2:让大模型选择相关性高的方法论
181
+ selection_prompt = f"""以下是所有可用的方法论标题:
187
182
 
188
183
  """
189
- # 构建完整内容
190
- full_content = base_prompt
191
- for problem_type, content in methodologies.items():
192
- full_content += f"## {problem_type}\n\n{content}\n\n---\n\n"
184
+ for i, title in enumerate(methodology_titles, 1):
185
+ selection_prompt += f"{i}. {title}\n"
193
186
 
194
- full_content += f"以下是所有可用的工具内容:\n\n"
195
- full_content += prompt
187
+ selection_prompt += f"""
188
+ 以下是可用的工具列表:
189
+ {prompt}
196
190
 
197
- # 添加用户输入和输出要求
198
- full_content += f"""
199
- 请根据以上方法论和可调用的工具内容,规划/总结出以下用户需求的执行步骤: {user_input}
191
+ 用户需求:{user_input}
200
192
 
201
- 请按以下格式回复:
202
- ### 与该任务/需求相关的方法论
203
- 1. [方法论名字]
204
- 2. [方法论名字]
205
- ### 根据以上方法论,规划/总结出执行步骤
206
- 1. [步骤1]
207
- 2. [步骤2]
208
- 3. [步骤3]
193
+ 请分析用户需求,从上述方法论中选择出与需求相关性较高的方法论(可以选择多个)。
209
194
 
210
- 如果没有匹配的方法论,请输出:没有历史方法论可参考
211
- 除以上要求外,不要输出任何内容
195
+ 请严格按照以下格式返回序号:
196
+ <NUM>序号1,序号2,序号3</NUM>
197
+
198
+ 例如:<NUM>1,3,5</NUM>
199
+
200
+ 如果没有相关的方法论,请返回:<NUM>none</NUM>
201
+
202
+ 注意:只返回<NUM>标签内的内容,不要有其他任何输出。
212
203
  """
213
204
 
214
- # 检查内容是否过大
215
- is_large_content = is_context_overflow(full_content, model_group)
216
- temp_file_path = None
205
+ # 获取大模型选择的方法论序号
206
+ response = platform.chat_until_success(selection_prompt).strip()
207
+
208
+ # 重置平台,恢复输出
209
+ platform.reset()
210
+ platform.set_suppress_output(False)
211
+
212
+ # 从响应中提取<NUM>标签内的内容
213
+ import re
214
+ num_match = re.search(r'<NUM>(.*?)</NUM>', response, re.DOTALL)
215
+
216
+ if not num_match:
217
+ # 如果没有找到<NUM>标签,尝试直接解析响应
218
+ selected_indices_str = response
219
+ else:
220
+ selected_indices_str = num_match.group(1).strip()
221
+
222
+ if selected_indices_str.lower() == "none":
223
+ return "没有历史方法论可参考"
217
224
 
225
+ # 解析选择的序号
226
+ selected_methodologies = {}
218
227
  try:
219
- if is_large_content:
220
- # 创建临时文件
221
- print(f"📝 创建方法论临时文件...")
222
- temp_file_path = _create_methodology_temp_file(methodologies)
223
- if not temp_file_path:
224
- print(f"❌ 创建方法论临时文件失败")
225
- return ""
226
- print(f"✅ 创建方法论临时文件完成")
227
-
228
- # 尝试上传文件
229
- upload_success = platform.upload_files([temp_file_path])
230
-
231
- if upload_success:
232
- # 使用上传的文件生成摘要
233
- return platform.chat_until_success(
234
- base_prompt
235
- + f"""
236
- 请根据已上传的方法论和可调用的工具文件内容,规划/总结出以下用户需求的执行步骤: {user_input}
228
+ if selected_indices_str:
229
+ indices = [int(idx.strip()) for idx in selected_indices_str.split(",") if idx.strip().isdigit()]
230
+ for idx in indices:
231
+ if 1 <= idx <= len(methodology_titles):
232
+ title = methodology_titles[idx - 1]
233
+ selected_methodologies[title] = methodologies[title]
234
+ except Exception:
235
+ # 如果解析失败,返回空结果
236
+ return "没有历史方法论可参考"
237
+
238
+ if not selected_methodologies:
239
+ return "没有历史方法论可参考"
240
+
241
+ # 步骤3:将选择出来的方法论内容提供给大模型生成步骤
242
+ final_prompt = f"""以下是与用户需求相关的方法论内容:
243
+
244
+ """
245
+ for problem_type, content in selected_methodologies.items():
246
+ final_prompt += f"## {problem_type}\n\n{content}\n\n---\n\n"
247
+
248
+ final_prompt += f"""以下是所有可用的工具内容:
249
+
250
+ {prompt}
251
+
252
+ 用户需求:{user_input}
253
+
254
+ 请根据以上方法论和可调用的工具内容,规划/总结出执行步骤。
237
255
 
238
256
  请按以下格式回复:
239
257
  ### 与该任务/需求相关的方法论
@@ -244,22 +262,11 @@ def load_methodology(user_input: str, tool_registery: Optional[Any] = None) -> s
244
262
  2. [步骤2]
245
263
  3. [步骤3]
246
264
 
247
- 如果没有匹配的方法论,请输出:没有历史方法论可参考
248
265
  除以上要求外,不要输出任何内容
249
266
  """
250
- )
251
- else:
252
- return "没有历史方法论可参考"
253
- # 如果内容不大或上传失败,直接使用chat_until_success
254
- return platform.chat_until_success(full_content)
255
-
256
- finally:
257
- # 清理临时文件
258
- if temp_file_path and os.path.exists(temp_file_path):
259
- try:
260
- os.remove(temp_file_path)
261
- except Exception:
262
- pass
267
+
268
+ # 如果内容不大,直接使用chat_until_success
269
+ return platform.chat_until_success(final_prompt)
263
270
 
264
271
  except Exception as e:
265
272
  PrettyOutput.print(f"加载方法论失败: {str(e)}", OutputType.ERROR)