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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +267 -240
- jarvis/jarvis_agent/agent_manager.py +85 -0
- jarvis/jarvis_agent/config_editor.py +53 -0
- jarvis/jarvis_agent/file_methodology_manager.py +105 -0
- jarvis/jarvis_agent/jarvis.py +37 -398
- jarvis/jarvis_agent/memory_manager.py +133 -0
- jarvis/jarvis_agent/methodology_share_manager.py +174 -0
- jarvis/jarvis_agent/prompts.py +18 -3
- jarvis/jarvis_agent/share_manager.py +176 -0
- jarvis/jarvis_agent/task_analyzer.py +126 -0
- jarvis/jarvis_agent/task_manager.py +111 -0
- jarvis/jarvis_agent/tool_share_manager.py +139 -0
- jarvis/jarvis_code_agent/code_agent.py +26 -20
- jarvis/jarvis_data/config_schema.json +37 -0
- jarvis/jarvis_platform/ai8.py +13 -1
- jarvis/jarvis_platform/base.py +20 -5
- jarvis/jarvis_platform/human.py +11 -1
- jarvis/jarvis_platform/kimi.py +10 -0
- jarvis/jarvis_platform/openai.py +20 -0
- jarvis/jarvis_platform/tongyi.py +14 -9
- jarvis/jarvis_platform/yuanbao.py +10 -0
- jarvis/jarvis_platform_manager/main.py +12 -12
- jarvis/jarvis_tools/registry.py +79 -20
- jarvis/jarvis_tools/retrieve_memory.py +36 -8
- jarvis/jarvis_utils/clipboard.py +90 -0
- jarvis/jarvis_utils/config.py +64 -0
- jarvis/jarvis_utils/git_utils.py +17 -7
- jarvis/jarvis_utils/globals.py +18 -12
- jarvis/jarvis_utils/input.py +118 -16
- jarvis/jarvis_utils/methodology.py +48 -5
- jarvis/jarvis_utils/utils.py +196 -106
- {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/RECORD +38 -28
- {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/top_level.txt +0 -0
jarvis/jarvis_agent/jarvis.py
CHANGED
@@ -1,405 +1,19 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
-
|
3
|
-
import
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
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
|
-
|
440
|
-
|
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
|
-
|
448
|
-
|
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
|