jarvis-ai-assistant 0.1.45__py3-none-any.whl → 0.1.46__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- jarvis/__init__.py +1 -1
- jarvis/agent.py +47 -32
- jarvis/main.py +52 -30
- jarvis/models/__init__.py +1 -1
- jarvis/models/ai8.py +88 -58
- jarvis/models/base.py +6 -6
- jarvis/models/kimi.py +171 -80
- jarvis/models/openai.py +43 -23
- jarvis/models/oyi.py +93 -65
- jarvis/models/registry.py +63 -44
- jarvis/tools/__init__.py +1 -1
- jarvis/tools/base.py +2 -2
- jarvis/tools/file_ops.py +19 -15
- jarvis/tools/generator.py +15 -12
- jarvis/tools/methodology.py +20 -20
- jarvis/tools/registry.py +44 -30
- jarvis/tools/shell.py +12 -11
- jarvis/tools/sub_agent.py +1 -2
- jarvis/utils.py +47 -27
- {jarvis_ai_assistant-0.1.45.dist-info → jarvis_ai_assistant-0.1.46.dist-info}/METADATA +1 -1
- jarvis_ai_assistant-0.1.46.dist-info/RECORD +25 -0
- jarvis_ai_assistant-0.1.45.dist-info/RECORD +0 -25
- {jarvis_ai_assistant-0.1.45.dist-info → jarvis_ai_assistant-0.1.46.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.45.dist-info → jarvis_ai_assistant-0.1.46.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.45.dist-info → jarvis_ai_assistant-0.1.46.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.45.dist-info → jarvis_ai_assistant-0.1.46.dist-info}/top_level.txt +0 -0
jarvis/__init__.py
CHANGED
jarvis/agent.py
CHANGED
@@ -10,10 +10,11 @@ import os
|
|
10
10
|
from datetime import datetime
|
11
11
|
from prompt_toolkit import prompt
|
12
12
|
|
13
|
+
|
13
14
|
class Agent:
|
14
15
|
def __init__(self, name: str = "Jarvis", is_sub_agent: bool = False):
|
15
16
|
"""Initialize Agent with a model, optional tool registry and name
|
16
|
-
|
17
|
+
|
17
18
|
Args:
|
18
19
|
model: 语言模型实例
|
19
20
|
tool_registry: 工具注册表实例
|
@@ -26,7 +27,6 @@ class Agent:
|
|
26
27
|
self.is_sub_agent = is_sub_agent
|
27
28
|
self.prompt = ""
|
28
29
|
|
29
|
-
|
30
30
|
@staticmethod
|
31
31
|
def extract_tool_calls(content: str) -> List[Dict]:
|
32
32
|
"""从内容中提取工具调用,如果检测到多个工具调用则抛出异常,并返回工具调用之前的内容和工具调用"""
|
@@ -34,7 +34,7 @@ class Agent:
|
|
34
34
|
lines = content.split('\n')
|
35
35
|
tool_call_lines = []
|
36
36
|
in_tool_call = False
|
37
|
-
|
37
|
+
|
38
38
|
# 逐行处理
|
39
39
|
for line in lines:
|
40
40
|
if '<START_TOOL_CALL>' in line:
|
@@ -46,7 +46,7 @@ class Agent:
|
|
46
46
|
# 直接解析YAML
|
47
47
|
tool_call_text = '\n'.join(tool_call_lines)
|
48
48
|
tool_call_data = yaml.safe_load(tool_call_text)
|
49
|
-
|
49
|
+
|
50
50
|
# 验证必要的字段
|
51
51
|
if "name" in tool_call_data and "arguments" in tool_call_data:
|
52
52
|
# 返回工具调用之前的内容和工具调用
|
@@ -58,17 +58,21 @@ class Agent:
|
|
58
58
|
PrettyOutput.print("工具调用缺少必要字段", OutputType.ERROR)
|
59
59
|
raise Exception("工具调用缺少必要字段")
|
60
60
|
except yaml.YAMLError as e:
|
61
|
-
PrettyOutput.print(
|
61
|
+
PrettyOutput.print(
|
62
|
+
f"YAML解析错误: {
|
63
|
+
str(e)}", OutputType.ERROR)
|
62
64
|
raise Exception(f"YAML解析错误: {str(e)}")
|
63
65
|
except Exception as e:
|
64
|
-
PrettyOutput.print(
|
66
|
+
PrettyOutput.print(
|
67
|
+
f"处理工具调用时发生错误: {
|
68
|
+
str(e)}", OutputType.ERROR)
|
65
69
|
raise Exception(f"处理工具调用时发生错误: {str(e)}")
|
66
70
|
in_tool_call = False
|
67
71
|
continue
|
68
|
-
|
72
|
+
|
69
73
|
if in_tool_call:
|
70
74
|
tool_call_lines.append(line)
|
71
|
-
|
75
|
+
|
72
76
|
return []
|
73
77
|
|
74
78
|
def _call_model(self, message: str) -> str:
|
@@ -79,14 +83,15 @@ class Agent:
|
|
79
83
|
if ret:
|
80
84
|
return ret
|
81
85
|
else:
|
82
|
-
PrettyOutput.print(
|
86
|
+
PrettyOutput.print(
|
87
|
+
f"调用模型失败,重试中... 等待 {sleep_time}s",
|
88
|
+
OutputType.INFO)
|
83
89
|
time.sleep(sleep_time)
|
84
90
|
sleep_time *= 2
|
85
91
|
if sleep_time > 30:
|
86
92
|
sleep_time = 30
|
87
93
|
continue
|
88
94
|
|
89
|
-
|
90
95
|
def _load_methodology(self) -> str:
|
91
96
|
"""加载经验总结"""
|
92
97
|
user_jarvis_methodology = os.path.expanduser("~/.jarvis_methodology")
|
@@ -96,23 +101,28 @@ class Agent:
|
|
96
101
|
data = yaml.safe_load(f)
|
97
102
|
for k, v in data.items():
|
98
103
|
ret += f"问题类型: \n{k}\n经验总结: \n{v}\n\n"
|
99
|
-
PrettyOutput.print(
|
104
|
+
PrettyOutput.print(
|
105
|
+
f"从 {user_jarvis_methodology} 加载经验总结: {
|
106
|
+
', '.join(
|
107
|
+
data.keys())}",
|
108
|
+
OutputType.INFO)
|
100
109
|
return ret
|
101
110
|
|
102
|
-
def run(self, user_input: str,
|
111
|
+
def run(self, user_input: str,
|
112
|
+
file_list: Optional[List[str]] = None, keep_history: bool = False) -> str:
|
103
113
|
"""处理用户输入并返回响应,返回任务总结报告
|
104
|
-
|
114
|
+
|
105
115
|
Args:
|
106
116
|
user_input: 用户输入的任务描述
|
107
117
|
file_list: 可选的文件列表,默认为None
|
108
118
|
keep_history: 是否保留对话历史,默认为False
|
109
|
-
|
119
|
+
|
110
120
|
Returns:
|
111
121
|
str: 任务总结报告
|
112
122
|
"""
|
113
123
|
try:
|
114
124
|
self.clear_history()
|
115
|
-
|
125
|
+
|
116
126
|
if file_list:
|
117
127
|
self.model.upload_files(file_list)
|
118
128
|
|
@@ -125,7 +135,7 @@ class Agent:
|
|
125
135
|
{methodology}
|
126
136
|
|
127
137
|
"""
|
128
|
-
|
138
|
+
|
129
139
|
# 显示任务开始
|
130
140
|
PrettyOutput.section(f"开始新任务: {self.name}", OutputType.PLANNING)
|
131
141
|
|
@@ -136,7 +146,7 @@ class Agent:
|
|
136
146
|
tools_prompt += f" 参数: {tool['parameters']}\n"
|
137
147
|
|
138
148
|
self.model.set_system_message(f"""你是 {self.name},一个问题处理能力强大的 AI 助手。
|
139
|
-
|
149
|
+
|
140
150
|
你会严格按照以下步骤处理问题:
|
141
151
|
1. 问题重述:确认理解问题
|
142
152
|
2. 根因分析(如果是问题分析类需要,其他不需要)
|
@@ -148,7 +158,7 @@ class Agent:
|
|
148
158
|
8. 监控与调整:如果执行结果与预期不符,则反思并调整行动计划,迭代之前的步骤
|
149
159
|
9. 更新经验总结(如有必要):任务完成后总结执行过程中的经验教训,生成同类问题的通用经验总结,使用经验总结工具进行更新或者添加
|
150
160
|
|
151
|
-
-------------------------------------------------------------
|
161
|
+
-------------------------------------------------------------
|
152
162
|
|
153
163
|
经验总结模板:
|
154
164
|
1. 问题重述
|
@@ -193,27 +203,30 @@ arguments:
|
|
193
203
|
try:
|
194
204
|
# 显示思考状态
|
195
205
|
PrettyOutput.print("分析任务...", OutputType.PROGRESS)
|
196
|
-
|
206
|
+
|
197
207
|
current_response = self._call_model(self.prompt)
|
198
|
-
|
208
|
+
|
199
209
|
try:
|
200
210
|
result = Agent.extract_tool_calls(current_response)
|
201
211
|
except Exception as e:
|
202
|
-
PrettyOutput.print(
|
212
|
+
PrettyOutput.print(
|
213
|
+
f"工具调用错误: {str(e)}", OutputType.ERROR)
|
203
214
|
self.prompt = f"工具调用错误: {str(e)}"
|
204
215
|
continue
|
205
|
-
|
216
|
+
|
206
217
|
if len(result) > 0:
|
207
218
|
|
208
219
|
PrettyOutput.print("执行工具调用...", OutputType.PROGRESS)
|
209
|
-
tool_result = self.tool_registry.handle_tool_calls(
|
220
|
+
tool_result = self.tool_registry.handle_tool_calls(
|
221
|
+
result)
|
210
222
|
PrettyOutput.print(tool_result, OutputType.RESULT)
|
211
223
|
|
212
224
|
self.prompt = tool_result
|
213
225
|
continue
|
214
|
-
|
226
|
+
|
215
227
|
# 获取用户输入
|
216
|
-
user_input = get_multiline_input(
|
228
|
+
user_input = get_multiline_input(
|
229
|
+
f"{self.name}: 您可以继续输入,或输入空行结束当前任务")
|
217
230
|
if user_input == "__interrupt__":
|
218
231
|
PrettyOutput.print("任务已取消", OutputType.WARNING)
|
219
232
|
return "Task cancelled by user"
|
@@ -221,10 +234,11 @@ arguments:
|
|
221
234
|
if user_input:
|
222
235
|
self.prompt = user_input
|
223
236
|
continue
|
224
|
-
|
237
|
+
|
225
238
|
if not user_input:
|
226
239
|
while True:
|
227
|
-
choice = prompt(
|
240
|
+
choice = prompt(
|
241
|
+
"是否需要手动为此任务生成经验总结以提升Jarvis对类似任务的处理能力?(y/n), 回车跳过: ")
|
228
242
|
if choice == "y":
|
229
243
|
self._make_methodology()
|
230
244
|
break
|
@@ -259,18 +273,20 @@ arguments:
|
|
259
273
|
except Exception as e:
|
260
274
|
PrettyOutput.print(str(e), OutputType.ERROR)
|
261
275
|
return f"Task failed: {str(e)}"
|
262
|
-
|
276
|
+
|
263
277
|
finally:
|
264
278
|
# 只在不保留历史时删除会话
|
265
279
|
if not keep_history:
|
266
280
|
try:
|
267
281
|
self.model.delete_chat()
|
268
282
|
except Exception as e:
|
269
|
-
PrettyOutput.print(
|
283
|
+
PrettyOutput.print(
|
284
|
+
f"清理会话时发生错误: {
|
285
|
+
str(e)}", OutputType.ERROR)
|
270
286
|
|
271
287
|
def clear_history(self):
|
272
288
|
"""清除对话历史,只保留系统提示"""
|
273
|
-
self.prompt = ""
|
289
|
+
self.prompt = ""
|
274
290
|
self.model.reset()
|
275
291
|
|
276
292
|
def _make_methodology(self):
|
@@ -281,7 +297,7 @@ arguments:
|
|
281
297
|
2. 最优解决方案
|
282
298
|
3. 最优方案执行步骤(失败的行动不需要体现)
|
283
299
|
""")
|
284
|
-
|
300
|
+
|
285
301
|
try:
|
286
302
|
result = Agent.extract_tool_calls(current_response)
|
287
303
|
except Exception as e:
|
@@ -291,4 +307,3 @@ arguments:
|
|
291
307
|
PrettyOutput.print("执行工具调用...", OutputType.PROGRESS)
|
292
308
|
tool_result = self.tool_registry.handle_tool_calls(result)
|
293
309
|
PrettyOutput.print(tool_result, OutputType.RESULT)
|
294
|
-
|
jarvis/main.py
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
"""Command line interface for Jarvis."""
|
3
3
|
|
4
|
+
from jarvis.utils import PrettyOutput, OutputType, get_multiline_input, load_env_from_file
|
5
|
+
from jarvis.tools import ToolRegistry
|
6
|
+
from jarvis.agent import Agent
|
4
7
|
import argparse
|
5
8
|
import yaml
|
6
9
|
import os
|
@@ -13,73 +16,76 @@ from jarvis.models.registry import PlatformRegistry
|
|
13
16
|
# 添加父目录到Python路径以支持导入
|
14
17
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
15
18
|
|
16
|
-
from jarvis.agent import Agent
|
17
|
-
from jarvis.tools import ToolRegistry
|
18
|
-
from jarvis.utils import PrettyOutput, OutputType, get_multiline_input, load_env_from_file
|
19
|
-
|
20
19
|
|
21
20
|
def load_tasks() -> dict:
|
22
21
|
"""Load tasks from .jarvis files in user home and current directory."""
|
23
22
|
tasks = {}
|
24
|
-
|
23
|
+
|
25
24
|
# 检查用户目录下的 .jarvis
|
26
25
|
user_jarvis = os.path.expanduser("~/.jarvis")
|
27
26
|
if os.path.exists(user_jarvis):
|
28
27
|
try:
|
29
28
|
with open(user_jarvis, "r", encoding="utf-8") as f:
|
30
29
|
user_tasks = yaml.safe_load(f)
|
31
|
-
|
30
|
+
|
32
31
|
if isinstance(user_tasks, dict):
|
33
32
|
# 验证并添加用户目录的任务
|
34
33
|
for name, desc in user_tasks.items():
|
35
34
|
if desc: # 确保描述不为空
|
36
35
|
tasks[str(name)] = str(desc)
|
37
36
|
else:
|
38
|
-
PrettyOutput.print(
|
37
|
+
PrettyOutput.print(
|
38
|
+
"Warning: ~/.jarvis file should contain a dictionary of task_name: task_description",
|
39
|
+
OutputType.ERROR)
|
39
40
|
except Exception as e:
|
40
|
-
PrettyOutput.print(
|
41
|
-
|
41
|
+
PrettyOutput.print(
|
42
|
+
f"Error loading ~/.jarvis file: {str(e)}", OutputType.ERROR)
|
43
|
+
|
42
44
|
# 检查当前目录下的 .jarvis
|
43
45
|
if os.path.exists(".jarvis"):
|
44
46
|
try:
|
45
47
|
with open(".jarvis", "r", encoding="utf-8") as f:
|
46
48
|
local_tasks = yaml.safe_load(f)
|
47
|
-
|
49
|
+
|
48
50
|
if isinstance(local_tasks, dict):
|
49
51
|
# 验证并添加当前目录的任务,如果有重名则覆盖用户目录的任务
|
50
52
|
for name, desc in local_tasks.items():
|
51
53
|
if desc: # 确保描述不为空
|
52
54
|
tasks[str(name)] = str(desc)
|
53
55
|
else:
|
54
|
-
PrettyOutput.print(
|
56
|
+
PrettyOutput.print(
|
57
|
+
"Warning: .jarvis file should contain a dictionary of task_name: task_description",
|
58
|
+
OutputType.ERROR)
|
55
59
|
except Exception as e:
|
56
|
-
PrettyOutput.print(
|
57
|
-
|
60
|
+
PrettyOutput.print(
|
61
|
+
f"Error loading .jarvis file: {
|
62
|
+
str(e)}", OutputType.ERROR)
|
63
|
+
|
58
64
|
return tasks
|
59
65
|
|
66
|
+
|
60
67
|
def select_task(tasks: dict) -> str:
|
61
68
|
"""Let user select a task from the list or skip. Returns task description if selected."""
|
62
69
|
if not tasks:
|
63
70
|
return ""
|
64
|
-
|
71
|
+
|
65
72
|
# Convert tasks to list for ordered display
|
66
73
|
task_names = list(tasks.keys())
|
67
|
-
|
74
|
+
|
68
75
|
PrettyOutput.print("\nAvailable tasks:", OutputType.INFO)
|
69
76
|
for i, name in enumerate(task_names, 1):
|
70
77
|
PrettyOutput.print(f"[{i}] {name}", OutputType.INFO)
|
71
78
|
PrettyOutput.print("[0] 跳过预定义任务", OutputType.INFO)
|
72
|
-
|
73
|
-
|
79
|
+
|
74
80
|
while True:
|
75
81
|
try:
|
76
82
|
choice = prompt(
|
77
83
|
"\n请选择一个任务编号(0跳过): ",
|
78
84
|
).strip()
|
79
|
-
|
85
|
+
|
80
86
|
if not choice:
|
81
87
|
return ""
|
82
|
-
|
88
|
+
|
83
89
|
choice = int(choice)
|
84
90
|
if choice == 0:
|
85
91
|
return ""
|
@@ -87,8 +93,10 @@ def select_task(tasks: dict) -> str:
|
|
87
93
|
selected_name = task_names[choice - 1]
|
88
94
|
return tasks[selected_name] # Return the task description
|
89
95
|
else:
|
90
|
-
PrettyOutput.print(
|
91
|
-
|
96
|
+
PrettyOutput.print(
|
97
|
+
"Invalid choice. Please select a number from the list.",
|
98
|
+
OutputType.ERROR)
|
99
|
+
|
92
100
|
except KeyboardInterrupt:
|
93
101
|
return "" # Return empty on Ctrl+C
|
94
102
|
except EOFError:
|
@@ -97,12 +105,16 @@ def select_task(tasks: dict) -> str:
|
|
97
105
|
PrettyOutput.print(f"选择失败: {str(e)}", OutputType.ERROR)
|
98
106
|
continue
|
99
107
|
|
108
|
+
|
100
109
|
def main():
|
101
110
|
"""Jarvis 的主入口点"""
|
102
111
|
# 添加参数解析器
|
103
112
|
parser = argparse.ArgumentParser(description='Jarvis AI 助手')
|
104
113
|
parser.add_argument('-f', '--files', nargs='*', help='要处理的文件列表')
|
105
|
-
parser.add_argument(
|
114
|
+
parser.add_argument(
|
115
|
+
'--keep-history',
|
116
|
+
action='store_true',
|
117
|
+
help='保持聊天历史(不删除会话)')
|
106
118
|
parser.add_argument('-p', '--platform', default='', help='选择AI平台')
|
107
119
|
args = parser.parse_args()
|
108
120
|
|
@@ -111,36 +123,45 @@ def main():
|
|
111
123
|
platform = args.platform if args.platform else os.getenv('JARVIS_PLATFORM')
|
112
124
|
|
113
125
|
if not platform:
|
114
|
-
PrettyOutput.print(
|
126
|
+
PrettyOutput.print(
|
127
|
+
"未指定AI平台,请使用 -p 参数或者设置 JARVIS_PLATFORM 环境变量",
|
128
|
+
OutputType.ERROR)
|
115
129
|
return 1
|
116
130
|
|
117
131
|
PlatformRegistry.get_global_platform_registry().set_global_platform_name(platform)
|
118
|
-
|
132
|
+
|
119
133
|
try:
|
120
134
|
# 获取全局模型实例
|
121
135
|
agent = Agent()
|
122
136
|
|
123
137
|
# 欢迎信息
|
124
|
-
PrettyOutput.print(
|
138
|
+
PrettyOutput.print(
|
139
|
+
f"Jarvis 已初始化 - With {platform} 平台,模型: {agent.model.name()}", OutputType.SYSTEM)
|
125
140
|
if args.keep_history:
|
126
141
|
PrettyOutput.print("已启用历史保留模式", OutputType.INFO)
|
127
|
-
|
142
|
+
|
128
143
|
# 加载预定义任务
|
129
144
|
tasks = load_tasks()
|
130
145
|
if tasks:
|
131
146
|
selected_task = select_task(tasks)
|
132
147
|
if selected_task:
|
133
148
|
PrettyOutput.print(f"\n执行任务: {selected_task}", OutputType.INFO)
|
134
|
-
agent.run(
|
149
|
+
agent.run(
|
150
|
+
selected_task,
|
151
|
+
args.files,
|
152
|
+
keep_history=args.keep_history)
|
135
153
|
return 0
|
136
|
-
|
154
|
+
|
137
155
|
# 如果没有选择预定义任务,进入交互模式
|
138
156
|
while True:
|
139
157
|
try:
|
140
158
|
user_input = get_multiline_input("请输入您的任务(输入空行退出):")
|
141
159
|
if not user_input or user_input == "__interrupt__":
|
142
160
|
break
|
143
|
-
agent.run(
|
161
|
+
agent.run(
|
162
|
+
user_input,
|
163
|
+
args.files,
|
164
|
+
keep_history=args.keep_history)
|
144
165
|
except Exception as e:
|
145
166
|
PrettyOutput.print(f"错误: {str(e)}", OutputType.ERROR)
|
146
167
|
|
@@ -150,5 +171,6 @@ def main():
|
|
150
171
|
|
151
172
|
return 0
|
152
173
|
|
174
|
+
|
153
175
|
if __name__ == "__main__":
|
154
|
-
exit(main())
|
176
|
+
exit(main())
|
jarvis/models/__init__.py
CHANGED