jarvis-ai-assistant 0.1.113__py3-none-any.whl → 0.1.115__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.
Potentially problematic release.
This version of jarvis-ai-assistant might be problematic. Click here for more details.
- jarvis/__init__.py +1 -1
- jarvis/agent.py +130 -74
- jarvis/jarvis_code_agent/code_agent.py +1 -1
- jarvis/jarvis_code_agent/patch.py +2 -4
- jarvis/jarvis_codebase/main.py +2 -0
- jarvis/jarvis_dev/main.py +664 -0
- jarvis/jarvis_platform/ai8.py +1 -1
- jarvis/jarvis_platform/openai.py +9 -1
- jarvis/jarvis_tools/file_operation.py +89 -57
- jarvis/jarvis_tools/read_code.py +92 -41
- jarvis/jarvis_tools/registry.py +9 -16
- jarvis/multi_agent.py +76 -0
- jarvis/utils.py +24 -9
- {jarvis_ai_assistant-0.1.113.dist-info → jarvis_ai_assistant-0.1.115.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.1.113.dist-info → jarvis_ai_assistant-0.1.115.dist-info}/RECORD +19 -19
- {jarvis_ai_assistant-0.1.113.dist-info → jarvis_ai_assistant-0.1.115.dist-info}/entry_points.txt +1 -0
- jarvis/jarvis_tools/deep_thinking.py +0 -160
- jarvis/jarvis_tools/deep_thinking_agent.py +0 -146
- {jarvis_ai_assistant-0.1.113.dist-info → jarvis_ai_assistant-0.1.115.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.113.dist-info → jarvis_ai_assistant-0.1.115.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.113.dist-info → jarvis_ai_assistant-0.1.115.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Dict, Any
|
|
1
|
+
from typing import Dict, Any, List, Union
|
|
2
2
|
import os
|
|
3
3
|
|
|
4
4
|
from jarvis.utils import OutputType, PrettyOutput
|
|
@@ -6,54 +6,38 @@ from jarvis.utils import OutputType, PrettyOutput
|
|
|
6
6
|
|
|
7
7
|
class FileOperationTool:
|
|
8
8
|
name = "file_operation"
|
|
9
|
-
description = "File operations
|
|
9
|
+
description = "File operations for reading and writing multiple files"
|
|
10
10
|
parameters = {
|
|
11
11
|
"type": "object",
|
|
12
12
|
"properties": {
|
|
13
13
|
"operation": {
|
|
14
14
|
"type": "string",
|
|
15
|
-
"enum": ["read", "write"
|
|
16
|
-
"description": "Type of file operation to perform"
|
|
15
|
+
"enum": ["read", "write"],
|
|
16
|
+
"description": "Type of file operation to perform (read or write multiple files)"
|
|
17
17
|
},
|
|
18
|
-
"
|
|
19
|
-
"type": "
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
"description": "File encoding (default: utf-8)",
|
|
30
|
-
"default": "utf-8"
|
|
18
|
+
"files": {
|
|
19
|
+
"type": "array",
|
|
20
|
+
"items": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": {
|
|
23
|
+
"path": {"type": "string"},
|
|
24
|
+
"content": {"type": "string"}
|
|
25
|
+
},
|
|
26
|
+
"required": ["path"]
|
|
27
|
+
},
|
|
28
|
+
"description": "List of files to operate on"
|
|
31
29
|
}
|
|
32
30
|
},
|
|
33
|
-
"required": ["operation", "
|
|
31
|
+
"required": ["operation", "files"]
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"""Execute file operations"""
|
|
34
|
+
def _handle_single_file(self, operation: str, filepath: str, content: str = "") -> Dict[str, Any]:
|
|
35
|
+
"""Handle operations for a single file"""
|
|
39
36
|
try:
|
|
40
|
-
operation = args["operation"].strip()
|
|
41
|
-
filepath = args["filepath"].strip()
|
|
42
|
-
encoding = args.get("encoding", "utf-8")
|
|
43
|
-
|
|
44
|
-
# Record the operation and the full path
|
|
45
37
|
abs_path = os.path.abspath(filepath)
|
|
46
38
|
PrettyOutput.print(f"文件操作: {operation} - {abs_path}", OutputType.INFO)
|
|
47
39
|
|
|
48
|
-
if operation == "
|
|
49
|
-
exists = os.path.exists(filepath)
|
|
50
|
-
return {
|
|
51
|
-
"success": True,
|
|
52
|
-
"stdout": str(exists),
|
|
53
|
-
"stderr": ""
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
elif operation == "read":
|
|
40
|
+
if operation == "read":
|
|
57
41
|
if not os.path.exists(filepath):
|
|
58
42
|
return {
|
|
59
43
|
"success": False,
|
|
@@ -61,7 +45,6 @@ class FileOperationTool:
|
|
|
61
45
|
"stderr": f"文件不存在: {filepath}"
|
|
62
46
|
}
|
|
63
47
|
|
|
64
|
-
# Check file size
|
|
65
48
|
if os.path.getsize(filepath) > 10 * 1024 * 1024: # 10MB
|
|
66
49
|
return {
|
|
67
50
|
"success": False,
|
|
@@ -69,41 +52,90 @@ class FileOperationTool:
|
|
|
69
52
|
"stderr": "File too large (>10MB)"
|
|
70
53
|
}
|
|
71
54
|
|
|
72
|
-
content = open(filepath, 'r', encoding=
|
|
73
|
-
|
|
55
|
+
content = open(filepath, 'r', encoding='utf-8').read()
|
|
56
|
+
output = f"File: {filepath}\n{content}"
|
|
57
|
+
|
|
58
|
+
# Print file content
|
|
59
|
+
PrettyOutput.print(f"读取文件: {filepath}\n{content}", OutputType.INFO)
|
|
60
|
+
|
|
74
61
|
return {
|
|
75
62
|
"success": True,
|
|
76
|
-
"stdout":
|
|
63
|
+
"stdout": output,
|
|
77
64
|
"stderr": ""
|
|
78
65
|
}
|
|
79
66
|
|
|
80
|
-
elif operation
|
|
81
|
-
if not args.get("content"):
|
|
82
|
-
return {
|
|
83
|
-
"success": False,
|
|
84
|
-
"stdout": "",
|
|
85
|
-
"stderr": "Write/append operation requires providing the content parameter"
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
# Create directory (if it doesn't exist)
|
|
67
|
+
elif operation == "write":
|
|
89
68
|
os.makedirs(os.path.dirname(os.path.abspath(filepath)), exist_ok=True)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
69
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
70
|
+
f.write(content)
|
|
71
|
+
|
|
72
|
+
PrettyOutput.print(f"写入文件: {filepath}", OutputType.INFO)
|
|
95
73
|
return {
|
|
96
74
|
"success": True,
|
|
97
|
-
"stdout": f"Successfully
|
|
75
|
+
"stdout": f"Successfully wrote content to {filepath}",
|
|
98
76
|
"stderr": ""
|
|
99
77
|
}
|
|
100
|
-
|
|
101
|
-
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
"success": False,
|
|
81
|
+
"stdout": "",
|
|
82
|
+
"stderr": f"Unknown operation: {operation}"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
except Exception as e:
|
|
86
|
+
PrettyOutput.print(str(e), OutputType.ERROR)
|
|
87
|
+
return {
|
|
88
|
+
"success": False,
|
|
89
|
+
"stdout": "",
|
|
90
|
+
"stderr": f"File operation failed for {filepath}: {str(e)}"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
94
|
+
"""Execute file operations for multiple files
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
args: Dictionary containing operation and files list
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Dict containing:
|
|
101
|
+
- success: Boolean indicating overall success
|
|
102
|
+
- stdout: Combined output of all operations as string
|
|
103
|
+
- stderr: Error message if any
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
operation = args["operation"].strip()
|
|
107
|
+
|
|
108
|
+
if "files" not in args or not isinstance(args["files"], list):
|
|
102
109
|
return {
|
|
103
110
|
"success": False,
|
|
104
111
|
"stdout": "",
|
|
105
|
-
"stderr":
|
|
112
|
+
"stderr": "files parameter is required and must be a list"
|
|
106
113
|
}
|
|
114
|
+
|
|
115
|
+
all_outputs = []
|
|
116
|
+
success = True
|
|
117
|
+
|
|
118
|
+
for file_info in args["files"]:
|
|
119
|
+
if not isinstance(file_info, dict) or "path" not in file_info:
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
content = file_info.get("content", "") if operation == "write" else ""
|
|
123
|
+
result = self._handle_single_file(operation, file_info["path"], content)
|
|
124
|
+
|
|
125
|
+
if result["success"]:
|
|
126
|
+
all_outputs.append(result["stdout"])
|
|
127
|
+
else:
|
|
128
|
+
all_outputs.append(f"Error with {file_info['path']}: {result['stderr']}")
|
|
129
|
+
success = success and result["success"]
|
|
130
|
+
|
|
131
|
+
# Combine all outputs with separators
|
|
132
|
+
combined_output = "\n\n" + "="*80 + "\n\n".join(all_outputs)
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
"success": success,
|
|
136
|
+
"stdout": combined_output,
|
|
137
|
+
"stderr": ""
|
|
138
|
+
}
|
|
107
139
|
|
|
108
140
|
except Exception as e:
|
|
109
141
|
PrettyOutput.print(str(e), OutputType.ERROR)
|
jarvis/jarvis_tools/read_code.py
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
from typing import Dict, Any
|
|
1
|
+
from typing import Dict, Any, List
|
|
2
2
|
import os
|
|
3
3
|
from jarvis.utils import OutputType, PrettyOutput
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class ReadCodeTool:
|
|
7
|
-
"""Read code
|
|
7
|
+
"""Read multiple code files with line numbers"""
|
|
8
8
|
|
|
9
9
|
name = "read_code"
|
|
10
|
-
description = "Read code
|
|
10
|
+
description = "Read multiple code files with line numbers"
|
|
11
11
|
parameters = {
|
|
12
12
|
"type": "object",
|
|
13
13
|
"properties": {
|
|
14
|
-
"
|
|
15
|
-
"type": "
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
"files": {
|
|
15
|
+
"type": "array",
|
|
16
|
+
"items": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"properties": {
|
|
19
|
+
"path": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Path to the file"
|
|
22
|
+
},
|
|
23
|
+
"start_line": {
|
|
24
|
+
"type": "integer",
|
|
25
|
+
"description": "Start line number (0-based, inclusive)",
|
|
26
|
+
"default": 0
|
|
27
|
+
},
|
|
28
|
+
"end_line": {
|
|
29
|
+
"type": "integer",
|
|
30
|
+
"description": "End line number (0-based, exclusive). -1 means read to end",
|
|
31
|
+
"default": -1
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"required": ["path"]
|
|
35
|
+
},
|
|
36
|
+
"description": "List of files to read"
|
|
27
37
|
}
|
|
28
38
|
},
|
|
29
|
-
"required": ["
|
|
39
|
+
"required": ["files"]
|
|
30
40
|
}
|
|
31
41
|
|
|
32
|
-
def
|
|
33
|
-
"""
|
|
42
|
+
def _read_single_file(self, filepath: str, start_line: int = 0, end_line: int = -1) -> Dict[str, Any]:
|
|
43
|
+
"""Read a single code file with line numbers
|
|
34
44
|
|
|
35
45
|
Args:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
filepath: Path to the file
|
|
47
|
+
start_line: Start line number (0-based, inclusive)
|
|
48
|
+
end_line: End line number (0-based, exclusive). -1 means read to end
|
|
49
|
+
|
|
41
50
|
Returns:
|
|
42
|
-
Dict containing
|
|
43
|
-
- success: Boolean indicating success
|
|
44
|
-
- stdout: File content with line numbers
|
|
45
|
-
- stderr: Error message if any
|
|
51
|
+
Dict containing operation result
|
|
46
52
|
"""
|
|
47
53
|
try:
|
|
48
|
-
filepath = args["filepath"].strip()
|
|
49
|
-
start_line = args.get("start_line", 0)
|
|
50
|
-
end_line = args.get("end_line", -1)
|
|
51
|
-
|
|
52
|
-
# Record the operation and the full path
|
|
53
54
|
abs_path = os.path.abspath(filepath)
|
|
54
55
|
PrettyOutput.print(f"正在读取代码文件:{abs_path}", OutputType.INFO)
|
|
55
56
|
|
|
56
|
-
# Check if file exists
|
|
57
57
|
if not os.path.exists(filepath):
|
|
58
58
|
return {
|
|
59
59
|
"success": False,
|
|
@@ -61,7 +61,6 @@ class ReadCodeTool:
|
|
|
61
61
|
"stderr": f"File does not exist: {filepath}"
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
# Check file size
|
|
65
64
|
if os.path.getsize(filepath) > 10 * 1024 * 1024: # 10MB
|
|
66
65
|
return {
|
|
67
66
|
"success": False,
|
|
@@ -69,7 +68,6 @@ class ReadCodeTool:
|
|
|
69
68
|
"stderr": "File too large (>10MB)"
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
# Read file content
|
|
73
71
|
try:
|
|
74
72
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
75
73
|
lines = f.readlines()
|
|
@@ -80,7 +78,6 @@ class ReadCodeTool:
|
|
|
80
78
|
"stderr": "Failed to decode file with UTF-8 encoding"
|
|
81
79
|
}
|
|
82
80
|
|
|
83
|
-
# Validate line range
|
|
84
81
|
if start_line < 0:
|
|
85
82
|
start_line = 0
|
|
86
83
|
if end_line == -1 or end_line > len(lines):
|
|
@@ -92,16 +89,13 @@ class ReadCodeTool:
|
|
|
92
89
|
"stderr": f"Invalid line range: [{start_line}, {end_line})"
|
|
93
90
|
}
|
|
94
91
|
|
|
95
|
-
# Format lines with hexadecimal line numbers
|
|
96
92
|
formatted_lines = []
|
|
97
93
|
for i, line in enumerate(lines[start_line:end_line]):
|
|
98
94
|
line_num = start_line + i
|
|
99
95
|
formatted_lines.append(f"{line_num:>5}:{line}")
|
|
100
96
|
|
|
101
97
|
content = "".join(formatted_lines)
|
|
102
|
-
|
|
103
|
-
output = f"File: {filepath}\nLines: [{start_line}, {end_line})\n{content}";
|
|
104
|
-
PrettyOutput.print(output, OutputType.CODE)
|
|
98
|
+
output = f"File: {filepath}\nLines: [{start_line}, {end_line})\n{content}"
|
|
105
99
|
|
|
106
100
|
return {
|
|
107
101
|
"success": True,
|
|
@@ -115,3 +109,60 @@ class ReadCodeTool:
|
|
|
115
109
|
"stdout": "",
|
|
116
110
|
"stderr": f"Failed to read code: {str(e)}"
|
|
117
111
|
}
|
|
112
|
+
|
|
113
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
114
|
+
"""Execute code reading for multiple files
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
args: Dictionary containing:
|
|
118
|
+
- files: List of file info with path and optional line range
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Dict containing:
|
|
122
|
+
- success: Boolean indicating overall success
|
|
123
|
+
- stdout: Combined output of all files as string
|
|
124
|
+
- stderr: Error message if any
|
|
125
|
+
"""
|
|
126
|
+
try:
|
|
127
|
+
if "files" not in args or not isinstance(args["files"], list):
|
|
128
|
+
return {
|
|
129
|
+
"success": False,
|
|
130
|
+
"stdout": "",
|
|
131
|
+
"stderr": "files parameter is required and must be a list"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
all_outputs = []
|
|
135
|
+
success = True
|
|
136
|
+
|
|
137
|
+
for file_info in args["files"]:
|
|
138
|
+
if not isinstance(file_info, dict) or "path" not in file_info:
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
result = self._read_single_file(
|
|
142
|
+
file_info["path"],
|
|
143
|
+
file_info.get("start_line", 0),
|
|
144
|
+
file_info.get("end_line", -1)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if result["success"]:
|
|
148
|
+
all_outputs.append(result["stdout"])
|
|
149
|
+
PrettyOutput.print(result["stdout"], OutputType.CODE)
|
|
150
|
+
else:
|
|
151
|
+
all_outputs.append(f"Error reading {file_info['path']}: {result['stderr']}")
|
|
152
|
+
success = success and result["success"]
|
|
153
|
+
|
|
154
|
+
# Combine all outputs with separators
|
|
155
|
+
combined_output = "\n\n" + "="*80 + "\n\n".join(all_outputs)
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
"success": success,
|
|
159
|
+
"stdout": combined_output,
|
|
160
|
+
"stderr": ""
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
except Exception as e:
|
|
164
|
+
return {
|
|
165
|
+
"success": False,
|
|
166
|
+
"stdout": "",
|
|
167
|
+
"stderr": f"Failed to read code files: {str(e)}"
|
|
168
|
+
}
|
jarvis/jarvis_tools/registry.py
CHANGED
|
@@ -17,14 +17,14 @@ arguments:
|
|
|
17
17
|
param2: value2
|
|
18
18
|
</TOOL_CALL>
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
20
|
+
STRICT RULES:
|
|
21
|
+
- EXECUTE ONLY ONE TOOL AT EVERY TURN
|
|
22
|
+
- TOOL EXECUTION MUST STRICTLY FOLLOW THE TOOL USAGE FORMAT
|
|
23
|
+
- WAIT FOR USER TO PROVIDE EXECUTION RESULTS
|
|
24
|
+
- DON'T ASSUME OR IMAGINE RESULTS
|
|
25
|
+
- DON'T CREATE FAKE DIALOGUES
|
|
26
|
+
- IF CURRENT INFORMATION IS INSUFFICIENT, YOU MAY ASK THE USER FOR MORE INFORMATION
|
|
27
|
+
- NOT ALL PROBLEM-SOLVING STEPS ARE MANDATORY, SKIP AS APPROPRIATE
|
|
28
28
|
- Request user guidance when multiple iterations show no progress
|
|
29
29
|
- ALWAYS use | syntax for string parameters to prevent parsing errors
|
|
30
30
|
Example:
|
|
@@ -189,14 +189,10 @@ class ToolRegistry:
|
|
|
189
189
|
return {"success": False, "stderr": f"Tool {name} does not exist, available tools: {', '.join(self.tools.keys())}", "stdout": ""}
|
|
190
190
|
return tool.execute(arguments)
|
|
191
191
|
|
|
192
|
-
def handle_tool_calls(self,
|
|
192
|
+
def handle_tool_calls(self, tool_call: Dict) -> str:
|
|
193
193
|
"""Handle tool calls, only process the first tool"""
|
|
194
194
|
try:
|
|
195
|
-
if not tool_calls:
|
|
196
|
-
return ""
|
|
197
|
-
|
|
198
195
|
# Only process the first tool call
|
|
199
|
-
tool_call = tool_calls[0]
|
|
200
196
|
name = tool_call["name"]
|
|
201
197
|
args = tool_call["arguments"]
|
|
202
198
|
|
|
@@ -288,9 +284,6 @@ Please provide a summary:"""
|
|
|
288
284
|
else:
|
|
289
285
|
PrettyOutput.section("执行失败", OutputType.WARNING)
|
|
290
286
|
PrettyOutput.print(result["stderr"], OutputType.WARNING)
|
|
291
|
-
|
|
292
|
-
if len(tool_calls) > 1:
|
|
293
|
-
output += f"\n\n--- Only one tool can be executed at a time, the following tools were not executed\n{str(tool_calls[1:])} ---"
|
|
294
287
|
return output
|
|
295
288
|
|
|
296
289
|
except Exception as e:
|
jarvis/multi_agent.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from jarvis.agent import Agent
|
|
6
|
+
from jarvis.utils import OutputType, PrettyOutput
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AgentConfig:
|
|
10
|
+
def __init__(self, **config):
|
|
11
|
+
self.system_prompt = config.get('system_prompt', '')
|
|
12
|
+
self.name = config.get('name', 'Jarvis')
|
|
13
|
+
self.description = config.get('description', '')
|
|
14
|
+
self.is_sub_agent = config.get('is_sub_agent', False)
|
|
15
|
+
self.tool_registry = config.get('tool_registry', [])
|
|
16
|
+
self.platform = config.get('platform')
|
|
17
|
+
self.model_name = config.get('model_name')
|
|
18
|
+
self.summary_prompt = config.get('summary_prompt')
|
|
19
|
+
self.auto_complete = config.get('auto_complete', False)
|
|
20
|
+
self.output_handler_before_tool = config.get('output_handler_before_tool')
|
|
21
|
+
self.output_handler_after_tool = config.get('output_handler_after_tool')
|
|
22
|
+
self.input_handler = config.get('input_handler')
|
|
23
|
+
self.max_context_length = config.get('max_context_length')
|
|
24
|
+
self.execute_tool_confirm = config.get('execute_tool_confirm')
|
|
25
|
+
|
|
26
|
+
class MultiAgent:
|
|
27
|
+
def __init__(self, configs: List[AgentConfig], main_agent_name: str):
|
|
28
|
+
self.agents_config = configs
|
|
29
|
+
self.agents = {}
|
|
30
|
+
self.init_agents()
|
|
31
|
+
self.main_agent_name = main_agent_name
|
|
32
|
+
|
|
33
|
+
def init_agents(self):
|
|
34
|
+
for agent_config in self.agents_config:
|
|
35
|
+
agent = Agent(system_prompt=agent_config.system_prompt,
|
|
36
|
+
name=agent_config.name,
|
|
37
|
+
description=agent_config.description,
|
|
38
|
+
model_name=agent_config.model_name,
|
|
39
|
+
platform=agent_config.platform,
|
|
40
|
+
max_context_length=agent_config.max_context_length,
|
|
41
|
+
support_send_msg=True,
|
|
42
|
+
execute_tool_confirm=agent_config.execute_tool_confirm,
|
|
43
|
+
input_handler=agent_config.input_handler,
|
|
44
|
+
output_handler_before_tool=agent_config.output_handler_before_tool,
|
|
45
|
+
output_handler_after_tool=agent_config.output_handler_after_tool,
|
|
46
|
+
use_methodology=False,
|
|
47
|
+
record_methodology=False,
|
|
48
|
+
need_summary=False,
|
|
49
|
+
auto_complete=agent_config.auto_complete,
|
|
50
|
+
summary_prompt=agent_config.summary_prompt,
|
|
51
|
+
is_sub_agent=agent_config.is_sub_agent,
|
|
52
|
+
tool_registry=agent_config.tool_registry,
|
|
53
|
+
)
|
|
54
|
+
agent.system_prompt += "You can send message to following agents: " + "\n".join([f"{c.name}: {c.description}" for c in self.agents_config])
|
|
55
|
+
self.agents[agent_config.name] = agent
|
|
56
|
+
|
|
57
|
+
def run(self, user_input: str, file_list: Optional[List[str]] = None) -> str:
|
|
58
|
+
last_agent = self.main_agent_name
|
|
59
|
+
msg = self.agents[self.main_agent_name].run(user_input, file_list)
|
|
60
|
+
while msg:
|
|
61
|
+
if isinstance(msg, str):
|
|
62
|
+
return msg
|
|
63
|
+
elif isinstance(msg, Dict):
|
|
64
|
+
prompt = f"""
|
|
65
|
+
Please handle this message:
|
|
66
|
+
from: {last_agent}
|
|
67
|
+
content: {msg['content']}
|
|
68
|
+
"""
|
|
69
|
+
if msg['to'] not in self.agents:
|
|
70
|
+
PrettyOutput.print(f"没有找到{msg['to']},重试...", OutputType.WARNING)
|
|
71
|
+
msg = self.agents[last_agent].run(f"The agent {msg['to']} is not found, agent list: {self.agents.keys()}")
|
|
72
|
+
continue
|
|
73
|
+
PrettyOutput.print(f"{last_agent} 发送消息给 {msg['to']}...", OutputType.INFO)
|
|
74
|
+
last_agent = self.agents[msg['to']]
|
|
75
|
+
msg = self.agents[msg['to']].run(prompt)
|
|
76
|
+
return ""
|
jarvis/utils.py
CHANGED
|
@@ -36,7 +36,8 @@ colorama.init()
|
|
|
36
36
|
|
|
37
37
|
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
global_agents = set()
|
|
40
|
+
current_agent_name = ""
|
|
40
41
|
|
|
41
42
|
# Install rich traceback handler
|
|
42
43
|
install_rich_traceback()
|
|
@@ -59,14 +60,28 @@ custom_theme = Theme({
|
|
|
59
60
|
|
|
60
61
|
console = Console(theme=custom_theme)
|
|
61
62
|
|
|
62
|
-
def
|
|
63
|
-
|
|
63
|
+
def make_agent_name(agent_name: str):
|
|
64
|
+
if agent_name in global_agents:
|
|
65
|
+
i = 1
|
|
66
|
+
while f"{agent_name}_{i}" in global_agents:
|
|
67
|
+
i += 1
|
|
68
|
+
return f"{agent_name}_{i}"
|
|
69
|
+
else:
|
|
70
|
+
return agent_name
|
|
71
|
+
|
|
72
|
+
def set_agent(agent_name: str, agent: Any):
|
|
73
|
+
global_agents.add(agent_name)
|
|
74
|
+
global current_agent_name
|
|
75
|
+
current_agent_name = agent_name
|
|
64
76
|
|
|
65
77
|
def get_agent_list():
|
|
66
|
-
return
|
|
78
|
+
return "[" + str(len(global_agents)) + "]" + current_agent_name if global_agents else "No Agent"
|
|
67
79
|
|
|
68
|
-
def
|
|
69
|
-
|
|
80
|
+
def delete_agent(agent_name: str):
|
|
81
|
+
if agent_name in global_agents:
|
|
82
|
+
global_agents.remove(agent_name)
|
|
83
|
+
global current_agent_name
|
|
84
|
+
current_agent_name = ""
|
|
70
85
|
|
|
71
86
|
class OutputType(Enum):
|
|
72
87
|
SYSTEM = "system" # AI assistant message
|
|
@@ -156,7 +171,7 @@ class PrettyOutput:
|
|
|
156
171
|
# Add timestamp and agent info
|
|
157
172
|
if timestamp:
|
|
158
173
|
formatted.append(f"[{datetime.now().strftime('%H:%M:%S')}] ", style="white")
|
|
159
|
-
formatted.append(f"
|
|
174
|
+
formatted.append(f"{get_agent_list()}", style="blue")
|
|
160
175
|
# Add icon
|
|
161
176
|
icon = PrettyOutput._ICONS.get(output_type, "")
|
|
162
177
|
formatted.append(f"{icon} ", style=output_type.value)
|
|
@@ -212,7 +227,7 @@ class PrettyOutput:
|
|
|
212
227
|
border_style=styles[output_type],
|
|
213
228
|
title=header,
|
|
214
229
|
title_align="left",
|
|
215
|
-
padding=(1,
|
|
230
|
+
padding=(1, 1),
|
|
216
231
|
highlight=True
|
|
217
232
|
)
|
|
218
233
|
|
|
@@ -426,7 +441,7 @@ def has_uncommitted_changes():
|
|
|
426
441
|
working_changes = os.popen("git diff --exit-code").read().strip() != ""
|
|
427
442
|
# Check staged changes
|
|
428
443
|
staged_changes = os.popen("git diff --cached --exit-code").read().strip() != ""
|
|
429
|
-
os.system("git reset
|
|
444
|
+
os.system("git reset")
|
|
430
445
|
return working_changes or staged_changes
|
|
431
446
|
|
|
432
447
|
def load_embedding_model():
|