jarvis-ai-assistant 0.2.7__py3-none-any.whl → 0.3.0__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.
Files changed (38) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +267 -240
  3. jarvis/jarvis_agent/agent_manager.py +85 -0
  4. jarvis/jarvis_agent/config_editor.py +53 -0
  5. jarvis/jarvis_agent/file_methodology_manager.py +105 -0
  6. jarvis/jarvis_agent/jarvis.py +37 -398
  7. jarvis/jarvis_agent/memory_manager.py +133 -0
  8. jarvis/jarvis_agent/methodology_share_manager.py +174 -0
  9. jarvis/jarvis_agent/prompts.py +18 -3
  10. jarvis/jarvis_agent/share_manager.py +176 -0
  11. jarvis/jarvis_agent/task_analyzer.py +126 -0
  12. jarvis/jarvis_agent/task_manager.py +111 -0
  13. jarvis/jarvis_agent/tool_share_manager.py +139 -0
  14. jarvis/jarvis_code_agent/code_agent.py +26 -20
  15. jarvis/jarvis_data/config_schema.json +37 -0
  16. jarvis/jarvis_platform/ai8.py +13 -1
  17. jarvis/jarvis_platform/base.py +20 -5
  18. jarvis/jarvis_platform/human.py +11 -1
  19. jarvis/jarvis_platform/kimi.py +10 -0
  20. jarvis/jarvis_platform/openai.py +20 -0
  21. jarvis/jarvis_platform/tongyi.py +14 -9
  22. jarvis/jarvis_platform/yuanbao.py +10 -0
  23. jarvis/jarvis_platform_manager/main.py +12 -12
  24. jarvis/jarvis_tools/registry.py +79 -20
  25. jarvis/jarvis_tools/retrieve_memory.py +36 -8
  26. jarvis/jarvis_utils/clipboard.py +90 -0
  27. jarvis/jarvis_utils/config.py +64 -0
  28. jarvis/jarvis_utils/git_utils.py +17 -7
  29. jarvis/jarvis_utils/globals.py +18 -12
  30. jarvis/jarvis_utils/input.py +118 -16
  31. jarvis/jarvis_utils/methodology.py +48 -5
  32. jarvis/jarvis_utils/utils.py +196 -106
  33. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/METADATA +1 -1
  34. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/RECORD +38 -28
  35. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/WHEEL +0 -0
  36. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/entry_points.txt +0 -0
  37. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/licenses/LICENSE +0 -0
  38. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/top_level.txt +0 -0
@@ -1,405 +1,19 @@
1
1
  # -*- coding: utf-8 -*-
2
- import os
3
- import shutil
4
- import subprocess
5
- import sys
6
- from pathlib import Path
7
- from typing import Dict, Optional, List
2
+ """Jarvis AI 助手主入口模块"""
3
+ from typing import Optional
8
4
 
9
5
  import typer
10
- import yaml # type: ignore
11
- from prompt_toolkit import prompt # type: ignore
12
6
 
13
- from jarvis.jarvis_agent import (
14
- Agent,
15
- OutputType,
16
- PrettyOutput,
17
- get_multiline_input,
18
- origin_agent_system_prompt,
19
- user_confirm,
20
- )
21
- from jarvis.jarvis_agent.builtin_input_handler import builtin_input_handler
22
- from jarvis.jarvis_agent.shell_input_handler import shell_input_handler
23
- from jarvis.jarvis_tools.registry import ToolRegistry
24
- from jarvis.jarvis_utils.config import get_data_dir
7
+ from jarvis.jarvis_agent import OutputType, PrettyOutput
8
+ from jarvis.jarvis_agent.agent_manager import AgentManager
9
+ from jarvis.jarvis_agent.config_editor import ConfigEditor
10
+ from jarvis.jarvis_agent.methodology_share_manager import MethodologyShareManager
11
+ from jarvis.jarvis_agent.tool_share_manager import ToolShareManager
25
12
  from jarvis.jarvis_utils.utils import init_env
26
13
 
27
14
  app = typer.Typer(help="Jarvis AI 助手")
28
15
 
29
16
 
30
- def _load_tasks() -> Dict[str, str]:
31
- """Load tasks from .jarvis files in user home and current directory."""
32
- tasks: Dict[str, str] = {}
33
-
34
- # Check pre-command in data directory
35
- data_dir = get_data_dir()
36
- pre_command_path = os.path.join(data_dir, "pre-command")
37
- if os.path.exists(pre_command_path):
38
- print(f"🔍 从{pre_command_path}加载预定义任务...")
39
- try:
40
- with open(pre_command_path, "r", encoding="utf-8", errors="ignore") as f:
41
- user_tasks = yaml.safe_load(f)
42
- if isinstance(user_tasks, dict):
43
- for name, desc in user_tasks.items():
44
- if desc:
45
- tasks[str(name)] = str(desc)
46
- print(f"✅ 预定义任务加载完成 {pre_command_path}")
47
- except (yaml.YAMLError, OSError):
48
- print(f"❌ 预定义任务加载失败 {pre_command_path}")
49
-
50
- # Check .jarvis/pre-command in current directory
51
- pre_command_path = ".jarvis/pre-command"
52
- if os.path.exists(pre_command_path):
53
- abs_path = os.path.abspath(pre_command_path)
54
- print(f"🔍 从{abs_path}加载预定义任务...")
55
- try:
56
- with open(pre_command_path, "r", encoding="utf-8", errors="ignore") as f:
57
- local_tasks = yaml.safe_load(f)
58
- if isinstance(local_tasks, dict):
59
- for name, desc in local_tasks.items():
60
- if desc:
61
- tasks[str(name)] = str(desc)
62
- print(f"✅ 预定义任务加载完成 {pre_command_path}")
63
- except (yaml.YAMLError, OSError):
64
- print(f"❌ 预定义任务加载失败 {pre_command_path}")
65
-
66
- return tasks
67
-
68
-
69
- def _select_task(tasks: Dict[str, str]) -> str:
70
- """Let user select a task from the list or skip. Returns task description if selected."""
71
- if not tasks:
72
- return ""
73
-
74
- task_names = list(tasks.keys())
75
- task_list = ["可用任务:"]
76
- for i, name in enumerate(task_names, 1):
77
- task_list.append(f"[{i}] {name}")
78
- task_list.append("[0] 跳过预定义任务")
79
- PrettyOutput.print("\n".join(task_list), OutputType.INFO)
80
-
81
- while True:
82
- try:
83
- choice_str = prompt("\n请选择一个任务编号(0 跳过预定义任务):").strip()
84
- if not choice_str:
85
- return ""
86
-
87
- choice = int(choice_str)
88
- if choice == 0:
89
- return ""
90
- if 1 <= choice <= len(task_names):
91
- selected_task = tasks[task_names[choice - 1]]
92
- PrettyOutput.print(f"将要执行任务:\n {selected_task}", OutputType.INFO)
93
- # 询问是否需要补充信息
94
- need_additional = user_confirm(
95
- "需要为此任务添加补充信息吗?", default=False
96
- )
97
- if need_additional:
98
- additional_input = get_multiline_input("请输入补充信息:")
99
- if additional_input:
100
- selected_task = (
101
- f"{selected_task}\n\n补充信息:\n{additional_input}"
102
- )
103
- return selected_task
104
- PrettyOutput.print(
105
- "无效的选择。请选择列表中的一个号码。", OutputType.WARNING
106
- )
107
-
108
- except (KeyboardInterrupt, EOFError):
109
- return ""
110
- except ValueError as val_err:
111
- PrettyOutput.print(f"选择任务失败: {str(val_err)}", OutputType.ERROR)
112
-
113
-
114
- def _handle_edit_mode(edit: bool, config_file: Optional[str]) -> None:
115
- """If edit flag is set, open config file in editor and exit."""
116
- if not edit:
117
- return
118
-
119
- config_file_path = (
120
- Path(config_file)
121
- if config_file
122
- else Path(os.path.expanduser("~/.jarvis/config.yaml"))
123
- )
124
- # 根据操作系统选择合适的编辑器
125
- import platform
126
-
127
- if platform.system() == "Windows":
128
- # 优先级:终端工具 -> 代码编辑器 -> 通用文本编辑器
129
- editors = ["nvim", "vim", "nano", "code", "notepad++", "notepad"]
130
- else:
131
- # 优先级:终端工具 -> 代码编辑器 -> 通用文本编辑器
132
- editors = ["nvim", "vim", "vi", "nano", "emacs", "code", "gedit", "kate"]
133
-
134
- editor = next((e for e in editors if shutil.which(e)), None)
135
-
136
- if editor:
137
- try:
138
- subprocess.run([editor, str(config_file_path)], check=True)
139
- raise typer.Exit(code=0)
140
- except (subprocess.CalledProcessError, FileNotFoundError) as e:
141
- PrettyOutput.print(f"Failed to open editor: {e}", OutputType.ERROR)
142
- raise typer.Exit(code=1)
143
- else:
144
- PrettyOutput.print(
145
- f"No suitable editor found. Tried: {', '.join(editors)}", OutputType.ERROR
146
- )
147
- raise typer.Exit(code=1)
148
-
149
-
150
- def _initialize_agent(
151
- llm_type: str, model_group: Optional[str], restore_session: bool
152
- ) -> Agent:
153
- """Initialize the agent and restore session if requested."""
154
- agent = Agent(
155
- system_prompt=origin_agent_system_prompt,
156
- llm_type=llm_type,
157
- model_group=model_group,
158
- input_handler=[shell_input_handler, builtin_input_handler],
159
- output_handler=[ToolRegistry()], # type: ignore
160
- need_summary=False,
161
- )
162
-
163
- # 尝试恢复会话
164
- if restore_session:
165
- if agent.restore_session():
166
- PrettyOutput.print("会话已成功恢复。", OutputType.SUCCESS)
167
- else:
168
- PrettyOutput.print("无法恢复会话。", OutputType.WARNING)
169
- return agent
170
-
171
-
172
- def _get_and_run_task(agent: Agent, task_content: Optional[str] = None) -> None:
173
- """Get task from various sources and run it."""
174
- # 优先处理命令行直接传入的任务
175
- if task_content:
176
- agent.run(task_content)
177
- raise typer.Exit(code=0)
178
-
179
- if agent.first:
180
- tasks = _load_tasks()
181
- if tasks and (selected_task := _select_task(tasks)):
182
- PrettyOutput.print(f"开始执行任务: \n{selected_task}", OutputType.INFO)
183
- agent.run(selected_task)
184
- raise typer.Exit(code=0)
185
-
186
- user_input = get_multiline_input("请输入你的任务(输入空行退出):")
187
- if user_input:
188
- agent.run(user_input)
189
- raise typer.Exit(code=0)
190
-
191
-
192
- def _parse_selection(selection_str: str, max_value: int) -> List[int]:
193
- """解析用户输入的选择字符串,支持逗号分隔和范围选择
194
-
195
- 例如: "1,2,3,4-9,20" -> [1, 2, 3, 4, 5, 6, 7, 8, 9, 20]
196
- """
197
- selected: set[int] = set()
198
- parts = selection_str.split(",")
199
-
200
- for part in parts:
201
- part = part.strip()
202
- if "-" in part:
203
- # 处理范围选择
204
- try:
205
- start_str, end_str = part.split("-")
206
- start_num = int(start_str.strip())
207
- end_num = int(end_str.strip())
208
- if 1 <= start_num <= max_value and 1 <= end_num <= max_value:
209
- selected.update(range(start_num, end_num + 1))
210
- except ValueError:
211
- continue
212
- else:
213
- # 处理单个数字
214
- try:
215
- num = int(part)
216
- if 1 <= num <= max_value:
217
- selected.add(num)
218
- except ValueError:
219
- continue
220
-
221
- return sorted(list(selected))
222
-
223
-
224
- def _handle_share_methodology(config_file: Optional[str] = None) -> None:
225
- """处理方法论分享功能"""
226
- from jarvis.jarvis_utils.config import (
227
- get_central_methodology_repo,
228
- get_methodology_dirs,
229
- get_data_dir,
230
- )
231
- import glob
232
- import json
233
- import shutil
234
-
235
- # 获取中心方法论仓库配置
236
- central_repo = get_central_methodology_repo()
237
- if not central_repo:
238
- PrettyOutput.print(
239
- "错误:未配置中心方法论仓库(JARVIS_CENTRAL_METHODOLOGY_REPO)",
240
- OutputType.ERROR,
241
- )
242
- PrettyOutput.print("请在配置文件中设置中心方法论仓库的Git地址", OutputType.INFO)
243
- raise typer.Exit(code=1)
244
-
245
- # 克隆或更新中心方法论仓库
246
- central_repo_path = os.path.join(get_data_dir(), "central_methodology_repo")
247
- if not os.path.exists(central_repo_path):
248
- PrettyOutput.print(f"正在克隆中心方法论仓库...", OutputType.INFO)
249
- subprocess.run(["git", "clone", central_repo, central_repo_path], check=True)
250
- else:
251
- PrettyOutput.print(f"正在更新中心方法论仓库...", OutputType.INFO)
252
- subprocess.run(["git", "pull"], cwd=central_repo_path, check=True)
253
-
254
- # 获取中心仓库中已有的方法论
255
- existing_methodologies = {} # 改为字典,存储 problem_type -> content 的映射
256
- for filepath in glob.glob(os.path.join(central_repo_path, "*.json")):
257
- try:
258
- with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
259
- methodology = json.load(f)
260
- problem_type = methodology.get("problem_type", "")
261
- content = methodology.get("content", "")
262
- if problem_type and content:
263
- existing_methodologies[problem_type] = content
264
- except Exception:
265
- pass
266
-
267
- # 获取所有方法论目录
268
- from jarvis.jarvis_utils.methodology import _get_methodology_directory
269
-
270
- methodology_dirs = [_get_methodology_directory()] + get_methodology_dirs()
271
-
272
- # 收集所有方法论文件(排除中心仓库目录和已存在的方法论)
273
- all_methodologies = {}
274
- methodology_files = []
275
- seen_problem_types = set() # 用于去重
276
-
277
- for directory in set(methodology_dirs):
278
- # 跳过中心仓库目录
279
- if os.path.abspath(directory) == os.path.abspath(central_repo_path):
280
- continue
281
-
282
- if not os.path.isdir(directory):
283
- continue
284
-
285
- for filepath in glob.glob(os.path.join(directory, "*.json")):
286
- try:
287
- with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
288
- methodology = json.load(f)
289
- problem_type = methodology.get("problem_type", "")
290
- content = methodology.get("content", "")
291
- # 基于内容判断是否已存在于中心仓库
292
- is_duplicate = False
293
- if problem_type in existing_methodologies:
294
- # 如果problem_type相同,比较内容
295
- if (
296
- content.strip()
297
- == existing_methodologies[problem_type].strip()
298
- ):
299
- is_duplicate = True
300
-
301
- # 排除已存在于中心仓库的方法论(基于内容),以及本地重复的方法论
302
- if (
303
- problem_type
304
- and content
305
- and not is_duplicate
306
- and problem_type not in seen_problem_types
307
- ):
308
- methodology_files.append(
309
- {
310
- "path": filepath,
311
- "problem_type": problem_type,
312
- "directory": directory,
313
- }
314
- )
315
- all_methodologies[problem_type] = methodology
316
- seen_problem_types.add(problem_type)
317
- except Exception:
318
- pass
319
-
320
- if not methodology_files:
321
- PrettyOutput.print(
322
- "没有找到新的方法论文件(所有方法论可能已存在于中心仓库)",
323
- OutputType.WARNING,
324
- )
325
- raise typer.Exit(code=0)
326
-
327
- # 显示可选的方法论
328
- methodology_list = ["\n可分享的方法论(已排除中心仓库中已有的):"]
329
- for i, meth in enumerate(methodology_files, 1):
330
- dir_name = os.path.basename(meth["directory"])
331
- methodology_list.append(f"[{i}] {meth['problem_type']} (来自: {dir_name})")
332
-
333
- # 一次性打印所有方法论
334
- PrettyOutput.print("\n".join(methodology_list), OutputType.INFO)
335
-
336
- # 让用户选择要分享的方法论
337
- while True:
338
- try:
339
- choice_str = prompt(
340
- "\n请选择要分享的方法论编号(支持格式: 1,2,3,4-9,20 或 all):"
341
- ).strip()
342
- if choice_str == "0":
343
- raise typer.Exit(code=0)
344
-
345
- selected_methodologies = []
346
- if choice_str.lower() == "all":
347
- selected_methodologies = methodology_files
348
- else:
349
- selected_indices = _parse_selection(choice_str, len(methodology_files))
350
- if not selected_indices:
351
- PrettyOutput.print("无效的选择", OutputType.WARNING)
352
- continue
353
- selected_methodologies = [
354
- methodology_files[i - 1] for i in selected_indices
355
- ]
356
-
357
- # 确认操作
358
- share_list = ["\n将要分享以下方法论到中心仓库:"]
359
- for meth in selected_methodologies:
360
- share_list.append(f"- {meth['problem_type']}")
361
- PrettyOutput.print("\n".join(share_list), OutputType.INFO)
362
-
363
- if not user_confirm("确认分享这些方法论吗?"):
364
- continue
365
-
366
- # 复制选中的方法论到中心仓库
367
- copied_list = []
368
- for meth in selected_methodologies:
369
- src_file = meth["path"]
370
- dst_file = os.path.join(central_repo_path, os.path.basename(src_file))
371
- shutil.copy2(src_file, dst_file)
372
- copied_list.append(f"已复制: {meth['problem_type']}")
373
-
374
- # 一次性显示所有复制结果
375
- if copied_list:
376
- PrettyOutput.print("\n".join(copied_list), OutputType.SUCCESS)
377
-
378
- # 提交并推送更改
379
- PrettyOutput.print("\n正在提交更改...", OutputType.INFO)
380
- subprocess.run(["git", "add", "."], cwd=central_repo_path, check=True)
381
-
382
- commit_msg = f"Add {len(selected_methodologies)} methodology(ies) from local collection"
383
- subprocess.run(
384
- ["git", "commit", "-m", commit_msg], cwd=central_repo_path, check=True
385
- )
386
-
387
- PrettyOutput.print("正在推送到远程仓库...", OutputType.INFO)
388
- subprocess.run(["git", "push"], cwd=central_repo_path, check=True)
389
-
390
- PrettyOutput.print("\n方法论已成功分享到中心仓库!", OutputType.SUCCESS)
391
- break
392
-
393
- except ValueError:
394
- PrettyOutput.print("请输入有效的数字", OutputType.WARNING)
395
- except subprocess.CalledProcessError as e:
396
- PrettyOutput.print(f"Git操作失败: {str(e)}", OutputType.ERROR)
397
- raise typer.Exit(code=1)
398
- except Exception as e:
399
- PrettyOutput.print(f"分享方法论时出错: {str(e)}", OutputType.ERROR)
400
- raise typer.Exit(code=1)
401
-
402
-
403
17
  @app.callback(invoke_without_command=True)
404
18
  def run_cli(
405
19
  ctx: typer.Context,
@@ -414,6 +28,9 @@ def run_cli(
414
28
  model_group: Optional[str] = typer.Option(
415
29
  None, "--llm_group", help="使用的模型组,覆盖配置文件中的设置"
416
30
  ),
31
+ tool_group: Optional[str] = typer.Option(
32
+ None, "--tool_group", help="使用的工具组,覆盖配置文件中的设置"
33
+ ),
417
34
  config_file: Optional[str] = typer.Option(
418
35
  None, "-f", "--config", help="自定义配置文件路径"
419
36
  ),
@@ -426,26 +43,48 @@ def run_cli(
426
43
  share_methodology: bool = typer.Option(
427
44
  False, "--share-methodology", help="分享本地方法论到中心方法论仓库"
428
45
  ),
46
+ share_tool: bool = typer.Option(
47
+ False, "--share-tool", help="分享本地工具到中心工具仓库"
48
+ ),
429
49
  ) -> None:
430
50
  """Jarvis AI assistant command-line interface."""
431
51
  if ctx.invoked_subcommand is not None:
432
52
  return
433
53
 
434
- _handle_edit_mode(edit, config_file)
54
+ # 处理配置文件编辑
55
+ if edit:
56
+ ConfigEditor.edit_config(config_file)
57
+ return
435
58
 
436
59
  # 处理方法论分享
437
60
  if share_methodology:
438
61
  init_env("", config_file=config_file) # 初始化配置但不显示欢迎信息
439
- _handle_share_methodology(config_file)
440
- raise typer.Exit(code=0)
62
+ manager = MethodologyShareManager()
63
+ manager.run()
64
+ return
441
65
 
66
+ # 处理工具分享
67
+ if share_tool:
68
+ init_env("", config_file=config_file) # 初始化配置但不显示欢迎信息
69
+ manager = ToolShareManager()
70
+ manager.run()
71
+ return
72
+
73
+ # 初始化环境
442
74
  init_env(
443
75
  "欢迎使用 Jarvis AI 助手,您的智能助理已准备就绪!", config_file=config_file
444
76
  )
445
77
 
78
+ # 运行主流程
446
79
  try:
447
- agent = _initialize_agent(llm_type, model_group, restore_session)
448
- _get_and_run_task(agent, task)
80
+ agent_manager = AgentManager(
81
+ llm_type=llm_type,
82
+ model_group=model_group,
83
+ tool_group=tool_group,
84
+ restore_session=restore_session,
85
+ )
86
+ agent_manager.initialize()
87
+ agent_manager.run_task(task)
449
88
  except typer.Exit:
450
89
  raise
451
90
  except Exception as err: # pylint: disable=broad-except
@@ -0,0 +1,133 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 记忆管理器模块
4
+ 负责处理Agent的记忆保存和检索功能
5
+ """
6
+ from typing import Optional, Dict, List, Any
7
+
8
+ from jarvis.jarvis_utils.globals import get_all_memory_tags
9
+ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
10
+
11
+
12
+ class MemoryManager:
13
+ """记忆管理器,负责处理记忆相关的功能"""
14
+
15
+ def __init__(self, agent):
16
+ """
17
+ 初始化记忆管理器
18
+
19
+ 参数:
20
+ agent: Agent实例
21
+ """
22
+ self.agent = agent
23
+
24
+ def prepare_memory_tags_prompt(self) -> str:
25
+ """准备记忆标签提示"""
26
+ memory_tags = get_all_memory_tags()
27
+ memory_tags_prompt = ""
28
+
29
+ # 检查是否有save_memory工具
30
+ if self._has_save_memory_tool():
31
+ memory_tags_prompt = "\n\n💡 提示:在分析任务之前,建议使用 save_memory 工具将关键信息记录下来,便于后续检索和复用。"
32
+
33
+ # 构建记忆标签列表
34
+ if any(tags for tags in memory_tags.values()):
35
+ memory_tags_prompt += self._format_memory_tags(memory_tags)
36
+
37
+ return memory_tags_prompt
38
+
39
+ def _has_save_memory_tool(self) -> bool:
40
+ """检查是否有save_memory工具"""
41
+ tool_registry = self.agent.get_tool_registry()
42
+ if tool_registry:
43
+ tool_names = [tool.name for tool in tool_registry.tools.values()]
44
+ return "save_memory" in tool_names
45
+ return False
46
+
47
+ def _format_memory_tags(self, memory_tags: dict) -> str:
48
+ """格式化记忆标签"""
49
+ prompt = (
50
+ "\n\n系统中存在以下记忆标签,你可以使用 retrieve_memory 工具检索相关记忆:"
51
+ )
52
+
53
+ type_names = {
54
+ "short_term": "短期记忆",
55
+ "project_long_term": "项目长期记忆",
56
+ "global_long_term": "全局长期记忆",
57
+ }
58
+
59
+ for memory_type, tags in memory_tags.items():
60
+ if tags:
61
+ type_name = type_names.get(memory_type, memory_type)
62
+ prompt += f"\n- {type_name}: {', '.join(tags)}"
63
+
64
+ return prompt
65
+
66
+ def prompt_memory_save(self):
67
+ """让大模型自动判断并保存值得记忆的信息"""
68
+ # 检查是否有记忆相关工具
69
+ tool_registry = self.agent.get_tool_registry()
70
+ if not tool_registry:
71
+ return
72
+
73
+ tool_names = [tool.name for tool in tool_registry.tools.values()]
74
+ if "save_memory" not in tool_names:
75
+ return
76
+
77
+ print("🔍 正在分析是否有值得记忆的信息...")
78
+
79
+ # 构建提示词,让大模型自己判断并保存记忆
80
+ prompt = """请回顾本次任务的整个过程,判断是否有值得长期记忆或项目记忆的信息。
81
+
82
+ 如果有以下类型的信息,请使用 save_memory 工具保存:
83
+ 1. 解决问题的新方法或技巧(适合保存为 global_long_term)
84
+ 2. 项目相关的重要发现或配置(适合保存为 project_long_term)
85
+ 3. 用户的偏好或习惯(适合保存为 global_long_term)
86
+ 4. 重要的技术知识或经验(适合保存为 global_long_term)
87
+ 5. 项目特定的实现细节或约定(适合保存为 project_long_term)
88
+
89
+ 请分析并保存有价值的信息,选择合适的记忆类型和标签。如果没有值得记忆的信息,请直接说明。"""
90
+
91
+ # 处理记忆保存
92
+ try:
93
+ response = self.agent.model.chat_until_success(prompt) # type: ignore
94
+
95
+ # 执行工具调用(如果有)
96
+ need_return, result = self.agent._call_tools(response)
97
+
98
+ # 根据响应判断是否保存了记忆
99
+ if "save_memory" in response:
100
+ print("✅ 已自动保存有价值的信息到记忆系统")
101
+ else:
102
+ print("📝 本次任务没有特别需要记忆的信息")
103
+
104
+ except Exception as e:
105
+ print(f"❌ 记忆分析失败: {str(e)}")
106
+
107
+ def add_memory_prompts_to_addon(self, addon_prompt: str, tool_registry) -> str:
108
+ """在附加提示中添加记忆相关提示"""
109
+ memory_prompts = ""
110
+
111
+ if tool_registry:
112
+ tool_names = [tool.name for tool in tool_registry.tools.values()]
113
+
114
+ # 如果有save_memory工具,添加相关提示
115
+ if "save_memory" in tool_names:
116
+ memory_prompts += (
117
+ "\n - 如果有关键信息需要记忆,请调用save_memory工具进行记忆:"
118
+ )
119
+ memory_prompts += (
120
+ "\n * project_long_term: 保存与当前项目相关的长期信息"
121
+ )
122
+ memory_prompts += (
123
+ "\n * global_long_term: 保存通用的信息、用户喜好、知识、方法等"
124
+ )
125
+ memory_prompts += "\n * short_term: 保存当前任务相关的临时信息"
126
+
127
+ # 如果有retrieve_memory工具,添加相关提示
128
+ if "retrieve_memory" in tool_names:
129
+ memory_prompts += (
130
+ "\n - 如果需要检索相关记忆信息,请调用retrieve_memory工具"
131
+ )
132
+
133
+ return memory_prompts