jarvis-ai-assistant 0.1.112__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.
jarvis/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Jarvis AI Assistant"""
2
2
 
3
- __version__ = "0.1.112"
3
+ __version__ = "0.1.114"
jarvis/agent.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import argparse
2
+ import re
2
3
  import time
3
4
  from typing import Callable, Dict, List, Optional
4
5
 
@@ -7,8 +8,8 @@ import yaml
7
8
 
8
9
  from jarvis.jarvis_platform.base import BasePlatform
9
10
  from jarvis.jarvis_platform.registry import PlatformRegistry
10
- from jarvis.jarvis_tools.registry import ToolRegistry, tool_call_help
11
- from jarvis.utils import PrettyOutput, OutputType, get_context_token_count, is_auto_complete, is_execute_tool_confirm, is_need_summary, is_record_methodology, load_methodology, add_agent, delete_current_agent, get_max_token_count, get_multiline_input, init_env, is_use_methodology, user_confirm
11
+ from jarvis.jarvis_tools.registry import ToolRegistry
12
+ from jarvis.utils import PrettyOutput, OutputType, get_context_token_count, is_auto_complete, is_execute_tool_confirm, is_need_summary, is_record_methodology, load_methodology, set_agent, delete_agent, get_max_token_count, get_multiline_input, init_env, is_use_methodology, make_agent_name, user_confirm
12
13
  import os
13
14
 
14
15
  class Agent:
@@ -21,6 +22,9 @@ class Agent:
21
22
  """
22
23
  self.summary_prompt = summary_prompt
23
24
 
25
+ def __del__(self):
26
+ delete_agent(self.name)
27
+
24
28
  def set_output_handler_before_tool(self, handler: List[Callable]):
25
29
  """Set handlers to process output before tool execution.
26
30
 
@@ -32,9 +36,11 @@ class Agent:
32
36
  def __init__(self,
33
37
  system_prompt: str,
34
38
  name: str = "Jarvis",
39
+ description: str = "",
35
40
  is_sub_agent: bool = False,
36
- tool_registry: Optional[ToolRegistry] = None,
37
- platform: Optional[BasePlatform] = None,
41
+ tool_registry: Optional[ToolRegistry|List[str]] = None,
42
+ platform: Optional[BasePlatform]|Optional[str] = None,
43
+ model_name: Optional[str] = None,
38
44
  summary_prompt: Optional[str] = None,
39
45
  auto_complete: Optional[bool] = None,
40
46
  output_handler_before_tool: Optional[List[Callable]] = None,
@@ -44,12 +50,14 @@ class Agent:
44
50
  record_methodology: Optional[bool] = None,
45
51
  need_summary: Optional[bool] = None,
46
52
  max_context_length: Optional[int] = None,
53
+ support_send_msg: bool = False,
47
54
  execute_tool_confirm: Optional[bool] = None):
48
55
  """Initialize an Agent instance.
49
56
 
50
57
  Args:
51
58
  system_prompt: The system prompt defining agent behavior
52
59
  name: Agent name, defaults to "Jarvis"
60
+ description: Agent description, defaults to ""
53
61
  is_sub_agent: Whether this is a sub-agent
54
62
  tool_registry: Registry of available tools
55
63
  platform: AI platform to use
@@ -61,16 +69,41 @@ class Agent:
61
69
  record_methodology: Whether to record methodology
62
70
  need_summary: Whether to generate summaries
63
71
  max_context_length: Maximum context length
72
+ support_send_msg: Whether to support sending messages
73
+ execute_tool_confirm: Whether to confirm tool execution
64
74
  """
65
- PrettyOutput.print(f"欢迎使用Jarvis,你的AI助手,正在初始化...", OutputType.SYSTEM)
75
+
76
+ self.name = make_agent_name(name)
77
+ self.description = description
78
+ # 初始化平台和模型
66
79
  if platform is not None:
67
- self.model = platform
80
+ if isinstance(platform, str):
81
+ self.model = PlatformRegistry().create_platform(platform)
82
+ if self.model is None:
83
+ PrettyOutput.print(f"平台 {platform} 不存在,将使用普通模型", OutputType.WARNING)
84
+ self.model = PlatformRegistry().get_normal_platform()
85
+ else:
86
+ self.model = platform
68
87
  else:
69
88
  self.model = PlatformRegistry.get_global_platform_registry().get_normal_platform()
70
- self.tool_registry = tool_registry if tool_registry else ToolRegistry()
89
+
90
+ if model_name is not None:
91
+ self.model.set_model_name(model_name)
92
+
93
+
94
+ # 初始化工具
95
+ if tool_registry is not None:
96
+ if isinstance(tool_registry, ToolRegistry):
97
+ self.tool_registry = tool_registry
98
+ elif isinstance(tool_registry, List):
99
+ self.tool_registry = ToolRegistry()
100
+ self.tool_registry.use_tools(tool_registry)
101
+ else:
102
+ self.tool_registry = ToolRegistry()
103
+
104
+
71
105
  self.record_methodology = record_methodology if record_methodology is not None else is_record_methodology()
72
106
  self.use_methodology = use_methodology if use_methodology is not None else is_use_methodology()
73
- self.name = name
74
107
  self.is_sub_agent = is_sub_agent
75
108
  self.prompt = ""
76
109
  self.conversation_length = 0 # Use length counter instead
@@ -81,6 +114,8 @@ class Agent:
81
114
  self.output_handler_before_tool = output_handler_before_tool if output_handler_before_tool else []
82
115
  self.output_handler_after_tool = output_handler_after_tool if output_handler_after_tool else []
83
116
 
117
+ self.support_send_msg = support_send_msg
118
+
84
119
  self.execute_tool_confirm = execute_tool_confirm if execute_tool_confirm is not None else is_execute_tool_confirm()
85
120
 
86
121
  self.summary_prompt = summary_prompt if summary_prompt else f"""Please generate a concise summary report of the task execution, including:
@@ -95,18 +130,52 @@ Please describe in concise bullet points, highlighting important information.
95
130
  """
96
131
 
97
132
  self.max_token_count = max_context_length if max_context_length is not None else get_max_token_count()
98
-
99
133
  self.auto_complete = auto_complete if auto_complete is not None else is_auto_complete()
100
-
101
- PrettyOutput.section(f"Jarvis 初始化完成 - 使用 {self.model.name()} 模型", OutputType.SYSTEM)
102
-
134
+ welcome_message = f"{name} 初始化完成 - 使用 {self.model.name()} 模型"
103
135
  tools = self.tool_registry.get_all_tools()
104
136
  if tools:
105
- PrettyOutput.section(f"可用工具: {', '.join([tool['name'] for tool in tools])}", OutputType.SYSTEM)
106
-
137
+ welcome_message += f"\n可用工具: \n{','.join([f'{tool['name']}' for tool in tools])}"
138
+ PrettyOutput.print(welcome_message, OutputType.SYSTEM)
107
139
 
108
140
  tools_prompt = self.tool_registry.load_tools()
109
- complete_prompt = """"""
141
+
142
+ send_msg_prompt = ""
143
+ if self.support_send_msg:
144
+ send_msg_prompt = """
145
+ ## Send Message Rules
146
+
147
+ !!! CRITICAL ACTION RULES !!!
148
+ You can ONLY perform ONE action per turn:
149
+ - ENSURE USE ONLY ONE TOOL EVERY TURN (file_operation, ask_user, etc.)
150
+ - OR SEND ONE MESSAGE TO ANOTHER AGENT
151
+ - NEVER DO BOTH IN THE SAME TURN
152
+
153
+ 2. Message Format:
154
+ <SEND_MESSAGE>
155
+ to: agent_name # Target agent name
156
+ content: |
157
+ message_content # Message content, multi-line must be separated by newlines
158
+ </SEND_MESSAGE>
159
+
160
+ 3. Message Handling:
161
+ - After sending a message, WAIT for response
162
+ - Process response before next action
163
+ - Never send multiple messages at once
164
+ - Never combine message with tool calls
165
+
166
+ 4. If Multiple Actions Needed:
167
+ a. Choose most important action first
168
+ b. Wait for response/result
169
+ c. Plan next action based on response
170
+ d. Execute next action in new turn
171
+
172
+ Remember:
173
+ - First action will be executed
174
+ - Additional actions will be IGNORED
175
+ - Always process responses before new actions
176
+ """
177
+
178
+ complete_prompt = ""
110
179
  if self.auto_complete:
111
180
  complete_prompt = """
112
181
  ## Task Completion
@@ -114,15 +183,45 @@ Please describe in concise bullet points, highlighting important information.
114
183
  <!!!COMPLETE!!!>
115
184
  """
116
185
 
186
+ additional_prompt = ""
187
+ if not tools_prompt and send_msg_prompt:
188
+ additional_prompt = """YOU MUST CALL ONE TOOL EVERY TURN, UNLESS YOU THINK THE TASK IS COMPLETED!!!"""
189
+ if not send_msg_prompt and tools_prompt:
190
+ additional_prompt = """YOU MUST SEND ONE MESSAGE EVERY TURN, UNLESS YOU THINK THE TASK IS COMPLETED!!!"""
191
+ if send_msg_prompt and tools_prompt:
192
+ additional_prompt = """YOU MUST CALL ONE TOOL OR SEND ONE MESSAGE EVERY TURN, UNLESS YOU THINK THE TASK IS COMPLETED!!!"""
193
+
117
194
  self.model.set_system_message(f"""
118
195
  {self.system_prompt}
119
196
 
120
197
  {tools_prompt}
121
198
 
199
+ {send_msg_prompt}
200
+
122
201
  {complete_prompt}
202
+
203
+ {additional_prompt}
123
204
  """)
124
205
  self.first = True
125
206
 
207
+ @staticmethod
208
+ def _extract_send_msg(content: str) -> List[Dict]:
209
+ """Extract send message from content.
210
+
211
+ Args:
212
+ content: The content containing send message
213
+ """
214
+ data = re.findall(r'<SEND_MESSAGE>(.*?)</SEND_MESSAGE>', content, re.DOTALL)
215
+ ret = []
216
+ for item in data:
217
+ try:
218
+ msg = yaml.safe_load(item)
219
+ if 'to' in msg and 'content' in msg:
220
+ ret.append(msg)
221
+ except Exception as e:
222
+ continue
223
+ return ret
224
+
126
225
  @staticmethod
127
226
  def _extract_tool_calls(content: str) -> List[Dict]:
128
227
  """Extract tool calls from content.
@@ -137,39 +236,17 @@ Please describe in concise bullet points, highlighting important information.
137
236
  Exception: If tool call is missing necessary fields
138
237
  """
139
238
  # Split content into lines
140
- lines = content.split('\n')
141
- tool_call_lines = []
142
- in_tool_call = False
143
-
144
-
145
- # Process line by line
146
- for line in lines:
147
- if '<TOOL_CALL>' in line:
148
- in_tool_call = True
149
- continue
150
- elif '</TOOL_CALL>' in line:
151
- if in_tool_call and tool_call_lines:
152
- # Parse YAML directly
153
- tool_call_text = '\n'.join(tool_call_lines)
154
- tool_call_data = yaml.safe_load(tool_call_text)
155
-
156
- # Validate necessary fields
157
- if "name" in tool_call_data and "arguments" in tool_call_data:
158
- # Return content before tool call and tool call
159
- return [{
160
- "name": tool_call_data["name"],
161
- "arguments": tool_call_data["arguments"]
162
- }]
163
- else:
164
- raise Exception("Tool call missing necessary fields")
165
- in_tool_call = False
239
+ data = re.findall(r'<TOOL_CALL>(.*?)</TOOL_CALL>', content, re.DOTALL)
240
+ ret = []
241
+ for item in data:
242
+ try:
243
+ msg = yaml.safe_load(item)
244
+ if 'name' in msg and 'arguments' in msg:
245
+ ret.append(msg)
246
+ except Exception as e:
166
247
  continue
167
-
168
- if in_tool_call:
169
- tool_call_lines.append(line)
170
-
171
- return []
172
-
248
+ return ret
249
+
173
250
  def _call_model(self, message: str) -> str:
174
251
  """Call the AI model with retry logic.
175
252
 
@@ -188,7 +265,7 @@ Please describe in concise bullet points, highlighting important information.
188
265
  message = handler(message)
189
266
 
190
267
  while True:
191
- ret = self.model.chat_until_success(message)
268
+ ret = self.model.chat_until_success(message) # type: ignore
192
269
  if ret:
193
270
  return ret
194
271
  else:
@@ -276,7 +353,7 @@ Please continue the task based on the above information.
276
353
  try:
277
354
  tool_calls = Agent._extract_tool_calls(response)
278
355
  if tool_calls:
279
- self.tool_registry.handle_tool_calls(tool_calls)
356
+ self.tool_registry.handle_tool_calls(tool_calls[0])
280
357
  except Exception as e:
281
358
  PrettyOutput.print(f"处理方法论生成失败: {str(e)}", OutputType.ERROR)
282
359
 
@@ -292,7 +369,7 @@ Please continue the task based on the above information.
292
369
  return "任务完成"
293
370
 
294
371
 
295
- def run(self, user_input: str, file_list: Optional[List[str]] = None) -> str:
372
+ def run(self, user_input: str, file_list: Optional[List[str]] = None) -> str|Dict:
296
373
  """Process user input and execute the task.
297
374
 
298
375
  Args:
@@ -300,30 +377,23 @@ Please continue the task based on the above information.
300
377
  file_list: Optional list of files to process
301
378
 
302
379
  Returns:
303
- str: Task summary report
304
-
305
- Note:
306
- - Handles context management
307
- - Processes tool calls
308
- - Manages conversation flow
309
- - Supports interactive mode
380
+ str|Dict: Task summary report or message to send
310
381
  """
311
-
312
- add_agent(self.name)
313
-
314
382
  try:
383
+ set_agent(self.name, self)
315
384
  PrettyOutput.section("准备环境", OutputType.PLANNING)
316
385
  if file_list:
317
- self.model.upload_files(file_list)
386
+ self.model.upload_files(file_list) # type: ignore
318
387
 
319
388
  # 显示任务开始
320
389
  PrettyOutput.section(f"开始新任务: {self.name}", OutputType.PLANNING)
321
390
 
322
- if self.first and self.use_methodology:
323
- self.prompt = f"{user_input}\n\n{load_methodology(user_input)}"
391
+ self.prompt = f"{user_input}"
392
+
393
+ if self.first:
394
+ if self.use_methodology:
395
+ self.prompt = f"{user_input}\n\n{load_methodology(user_input)}"
324
396
  self.first = False
325
- else:
326
- self.prompt = f"{user_input}"
327
397
 
328
398
  while True:
329
399
  try:
@@ -342,22 +412,36 @@ Please continue the task based on the above information.
342
412
  self.prompt = ""
343
413
  self.conversation_length += get_context_token_count(current_response)
344
414
 
345
- for handler in self.output_handler_before_tool:
346
- self.prompt += handler(current_response)
415
+ # Check for message first
416
+ send_msg = Agent._extract_send_msg(current_response)
417
+ tool_calls = Agent._extract_tool_calls(current_response)
347
418
 
348
- try:
349
- result = Agent._extract_tool_calls(current_response)
350
- except Exception as e:
351
- PrettyOutput.print(f"工具调用错误: {str(e)}", OutputType.ERROR)
352
- self.prompt += f"Tool call error: {str(e)}"
419
+ if len(send_msg) > 1:
420
+ PrettyOutput.print(f"收到多个消息,违反规则,重试...", OutputType.WARNING)
421
+ self.prompt += f"Only one message can be sent, but {len(send_msg)} messages were received. Please retry."
422
+ continue
423
+ if len(tool_calls) > 1:
424
+ PrettyOutput.print(f"收到多个工具调用,违反规则,重试...", OutputType.WARNING)
425
+ self.prompt += f"Only one tool call can be executed, but {len(tool_calls)} tool calls were received. Please retry."
353
426
  continue
427
+ if send_msg and tool_calls:
428
+ PrettyOutput.print(f"收到消息和工具调用,违反规则,重试...", OutputType.WARNING)
429
+ self.prompt += f"Only one action can be performed, but both a message and a tool call were received. Please retry."
430
+ continue
431
+
432
+ if send_msg:
433
+ return send_msg[0] # Return the first message
434
+
435
+ # If no message, process tool calls
436
+ for handler in self.output_handler_before_tool:
437
+ self.prompt += handler(current_response)
354
438
 
355
- if len(result) > 0:
356
- if not self.execute_tool_confirm or user_confirm(f"执行工具调用: {result[0]['name']}?"):
439
+ if tool_calls:
440
+ if not self.execute_tool_confirm or user_confirm(f"执行工具调用: {tool_calls[0]['name']}?"):
357
441
  PrettyOutput.print("正在执行工具调用...", OutputType.PROGRESS)
358
- tool_result = self.tool_registry.handle_tool_calls(result)
442
+ tool_result = self.tool_registry.handle_tool_calls(tool_calls[0])
359
443
  self.prompt += tool_result
360
-
444
+
361
445
  for handler in self.output_handler_after_tool:
362
446
  self.prompt += handler(current_response)
363
447
 
@@ -384,9 +468,6 @@ Please continue the task based on the above information.
384
468
  except Exception as e:
385
469
  PrettyOutput.print(f"任务失败: {str(e)}", OutputType.ERROR)
386
470
  return f"Task failed: {str(e)}"
387
-
388
- finally:
389
- delete_current_agent()
390
471
 
391
472
  def _clear_history(self):
392
473
  """Clear conversation history while preserving system prompt.
@@ -397,7 +478,7 @@ Please continue the task based on the above information.
397
478
  3. Reset conversation length counter
398
479
  """
399
480
  self.prompt = ""
400
- self.model.reset()
481
+ self.model.reset() # type: ignore
401
482
  self.conversation_length = 0 # Reset conversation length
402
483
 
403
484
 
@@ -484,7 +565,7 @@ def _select_task(tasks: dict) -> str:
484
565
  selected_name = task_names[choice - 1]
485
566
  return tasks[selected_name] # Return the task description
486
567
  else:
487
- PrettyOutput.print("无效的选择。请选择列表中的一个号码。", OutputType.ERROR)
568
+ PrettyOutput.print("无效的选择。请选择列表中的一个号码。", OutputType.WARNING)
488
569
 
489
570
  except KeyboardInterrupt:
490
571
  return "" # Return empty on Ctrl+C
@@ -51,7 +51,7 @@ class CodeAgent:
51
51
 
52
52
  # Patch Format
53
53
  <PATCH>
54
- > path/file start,end
54
+ path/file start,end
55
55
  new_content
56
56
  </PATCH>
57
57
 
@@ -8,7 +8,7 @@ from jarvis.utils import OutputType, PrettyOutput, get_multiline_input, has_unco
8
8
  def _parse_patch(patch_str: str) -> Dict[str, List[Dict[str, Any]]]:
9
9
  """Parse patches from string with format:
10
10
  <PATCH>
11
- > path/to/file start_line,end_line
11
+ path/to/file start_line,end_line
12
12
  content_line1
13
13
  content_line2
14
14
  ...
@@ -24,11 +24,9 @@ def _parse_patch(patch_str: str) -> Dict[str, List[Dict[str, Any]]]:
24
24
 
25
25
  # Parse file path and line range
26
26
  file_info = lines[0].strip()
27
- if not file_info.startswith('>'):
28
- continue
29
27
 
30
28
  # Extract file path and line range
31
- match = re.match(r'>\s*([^\s]+)\s+(\d+),(\d+)', file_info)
29
+ match = re.match(r'([^\s]+)\s+(\d+),(\d+)', file_info)
32
30
  if not match:
33
31
  continue
34
32
 
@@ -686,6 +686,8 @@ Please provide 10 search-optimized expressions in the specified format.
686
686
  if question_match:
687
687
  try:
688
688
  variants = yaml.safe_load(question_match.group(1))
689
+ if not isinstance(variants, list):
690
+ variants = [str(variants)]
689
691
  except Exception as e:
690
692
  PrettyOutput.print(f"解析变体失败: {str(e)}", OutputType.ERROR)
691
693