jarvis-ai-assistant 0.1.113__py3-none-any.whl → 0.1.114__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.
@@ -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 (read/write/append/exists)"
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", "append", "exists"],
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
- "filepath": {
19
- "type": "string",
20
- "description": "Absolute or relative path to the file"
21
- },
22
- "content": {
23
- "type": "string",
24
- "description": "Content to write (required for write/append operations)",
25
- "default": ""
26
- },
27
- "encoding": {
28
- "type": "string",
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", "filepath"]
31
+ "required": ["operation", "files"]
34
32
  }
35
33
 
36
-
37
- def execute(self, args: Dict) -> Dict[str, Any]:
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 == "exists":
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=encoding).read()
73
- PrettyOutput.print(content, OutputType.INFO)
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": content,
63
+ "stdout": output,
77
64
  "stderr": ""
78
65
  }
79
66
 
80
- elif operation in ["write", "append"]:
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
- mode = 'a' if operation == "append" else 'w'
92
- with open(filepath, mode, encoding=encoding) as f:
93
- f.write(args["content"])
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 {operation} content to {filepath}",
75
+ "stdout": f"Successfully wrote content to {filepath}",
98
76
  "stderr": ""
99
77
  }
100
-
101
- else:
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": f"Unknown operation: {operation}"
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)
@@ -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 file with line numbers"""
7
+ """Read multiple code files with line numbers"""
8
8
 
9
9
  name = "read_code"
10
- description = "Read code file with line numbers"
10
+ description = "Read multiple code files with line numbers"
11
11
  parameters = {
12
12
  "type": "object",
13
13
  "properties": {
14
- "filepath": {
15
- "type": "string",
16
- "description": "Absolute or relative path to the file"
17
- },
18
- "start_line": {
19
- "type": "integer",
20
- "description": "Start line number (0-based, inclusive)",
21
- "default": 0
22
- },
23
- "end_line": {
24
- "type": "integer",
25
- "description": "End line number (0-based, exclusive). -1 means read to end",
26
- "default": -1
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": ["filepath"]
39
+ "required": ["files"]
30
40
  }
31
41
 
32
- def execute(self, args: Dict) -> Dict[str, Any]:
33
- """Execute code reading with line numbers
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
- args: Dictionary containing:
37
- - filepath: Path to the file
38
- - start_line: Start line number (optional)
39
- - end_line: End line number (optional)
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
+ }
@@ -17,14 +17,14 @@ arguments:
17
17
  param2: value2
18
18
  </TOOL_CALL>
19
19
 
20
- Strict Rules:
21
- - Execute only one tool at a time
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
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, tool_calls: List[Dict]) -> str:
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
- current_agent = []
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 add_agent(agent_name: str):
63
- current_agent.append(agent_name)
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 ']['.join(current_agent) if current_agent else "No Agent"
78
+ return "[" + str(len(global_agents)) + "]" + current_agent_name if global_agents else "No Agent"
67
79
 
68
- def delete_current_agent():
69
- current_agent.pop()
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"[{get_agent_list()}]", style="blue")
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, 2),
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 HEAD")
444
+ os.system("git reset")
430
445
  return working_changes or staged_changes
431
446
 
432
447
  def load_embedding_model():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: jarvis-ai-assistant
3
- Version: 0.1.113
3
+ Version: 0.1.114
4
4
  Summary: Jarvis: An AI assistant that uses tools to interact with the system
5
5
  Home-page: https://github.com/skyfireitdiy/Jarvis
6
6
  Author: skyfire