jarvis-ai-assistant 0.1.98__py3-none-any.whl → 0.1.100__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.

Files changed (45) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/agent.py +199 -157
  3. jarvis/jarvis_code_agent/__init__.py +0 -0
  4. jarvis/jarvis_code_agent/main.py +202 -0
  5. jarvis/jarvis_codebase/main.py +415 -287
  6. jarvis/jarvis_coder/file_select.py +209 -0
  7. jarvis/jarvis_coder/git_utils.py +64 -2
  8. jarvis/jarvis_coder/main.py +13 -397
  9. jarvis/jarvis_coder/patch_handler.py +229 -81
  10. jarvis/jarvis_coder/plan_generator.py +49 -7
  11. jarvis/jarvis_platform/main.py +2 -2
  12. jarvis/jarvis_rag/main.py +11 -11
  13. jarvis/jarvis_smart_shell/main.py +5 -5
  14. jarvis/models/base.py +6 -1
  15. jarvis/models/kimi.py +2 -2
  16. jarvis/models/ollama.py +2 -2
  17. jarvis/models/openai.py +1 -1
  18. jarvis/models/registry.py +38 -18
  19. jarvis/tools/ask_user.py +12 -9
  20. jarvis/tools/chdir.py +9 -5
  21. jarvis/tools/create_code_sub_agent.py +56 -0
  22. jarvis/tools/{sub_agent.py → create_sub_agent.py} +6 -2
  23. jarvis/tools/execute_code_modification.py +70 -0
  24. jarvis/tools/{shell.py → execute_shell.py} +2 -2
  25. jarvis/tools/{file_ops.py → file_operation.py} +19 -15
  26. jarvis/tools/find_files.py +119 -0
  27. jarvis/tools/{generator.py → generate_tool.py} +27 -25
  28. jarvis/tools/methodology.py +32 -26
  29. jarvis/tools/rag.py +37 -33
  30. jarvis/tools/{webpage.py → read_webpage.py} +4 -2
  31. jarvis/tools/registry.py +94 -48
  32. jarvis/tools/search.py +19 -16
  33. jarvis/tools/select_code_files.py +61 -0
  34. jarvis/tools/thinker.py +7 -5
  35. jarvis/utils.py +155 -32
  36. {jarvis_ai_assistant-0.1.98.dist-info → jarvis_ai_assistant-0.1.100.dist-info}/METADATA +9 -8
  37. jarvis_ai_assistant-0.1.100.dist-info/RECORD +51 -0
  38. {jarvis_ai_assistant-0.1.98.dist-info → jarvis_ai_assistant-0.1.100.dist-info}/entry_points.txt +2 -1
  39. jarvis/main.py +0 -155
  40. jarvis/tools/codebase_qa.py +0 -74
  41. jarvis/tools/coder.py +0 -69
  42. jarvis_ai_assistant-0.1.98.dist-info/RECORD +0 -47
  43. {jarvis_ai_assistant-0.1.98.dist-info → jarvis_ai_assistant-0.1.100.dist-info}/LICENSE +0 -0
  44. {jarvis_ai_assistant-0.1.98.dist-info → jarvis_ai_assistant-0.1.100.dist-info}/WHEEL +0 -0
  45. {jarvis_ai_assistant-0.1.98.dist-info → jarvis_ai_assistant-0.1.100.dist-info}/top_level.txt +0 -0
jarvis/tools/registry.py CHANGED
@@ -10,67 +10,103 @@ from jarvis.tools.base import Tool
10
10
  from jarvis.utils import OutputType, PrettyOutput, get_max_context_length
11
11
 
12
12
 
13
+
14
+ def load_tools() -> str:
15
+ """Load tools"""
16
+ PrettyOutput.section("Available tools", OutputType.PLANNING)
17
+ tools = ToolRegistry.get_global_tool_registry().get_all_tools()
18
+ if tools:
19
+ tools_prompt = "Available tools:\n"
20
+ for tool in tools:
21
+ PrettyOutput.print(f"{tool['name']}: {tool['description']}", OutputType.INFO)
22
+ tools_prompt += f"- Name: {tool['name']}\n"
23
+ tools_prompt += f" Description: {tool['description']}\n"
24
+ tools_prompt += f" Parameters: {tool['parameters']}\n"
25
+ tools_prompt += f" Usage Format: <TOOL_CALL>\n"
26
+ tools_prompt += """
27
+ Tool Usage Format:
28
+
29
+ <TOOL_CALL>
30
+ name: tool_name
31
+ arguments:
32
+ param1: value1
33
+ param2: value2
34
+ </TOOL_CALL>
35
+ ---------------------------------------------
36
+ """
37
+ return tools_prompt
38
+ return ""
39
+
13
40
  class ToolRegistry:
14
41
  global_tool_registry = None # type: ignore
15
42
  def __init__(self):
16
- """初始化工具注册器
17
- """
43
+ """Initialize tool registry"""
18
44
  self.tools: Dict[str, Tool] = {}
19
- # 加载内置工具和外部工具
45
+ # Load built-in tools and external tools
20
46
  self._load_builtin_tools()
21
47
  self._load_external_tools()
22
- # 确保 max_context_length 是整数
48
+ # Ensure max_context_length is an integer
23
49
  self.max_context_length = int(get_max_context_length() * 0.8)
24
50
 
51
+ def use_tools(self, name: List[str]):
52
+ """Use specified tools"""
53
+ missing_tools = [tool_name for tool_name in name if tool_name not in self.tools]
54
+ if missing_tools:
55
+ PrettyOutput.print(f"Tools {missing_tools} do not exist, available tools: {', '.join(self.tools.keys())}", OutputType.WARNING)
56
+ self.tools = {tool_name: self.tools[tool_name] for tool_name in name}
57
+
58
+ def dont_use_tools(self, names: List[str]):
59
+ """Remove specified tools from the registry"""
60
+ self.tools = {name: tool for name, tool in self.tools.items() if name not in names}
25
61
  @staticmethod
26
62
  def get_global_tool_registry():
27
- """获取全局工具注册器"""
63
+ """Get the global tool registry"""
28
64
  if ToolRegistry.global_tool_registry is None:
29
65
  ToolRegistry.global_tool_registry = ToolRegistry()
30
66
  return ToolRegistry.global_tool_registry
31
67
 
32
68
  def _load_builtin_tools(self):
33
- """从内置tools目录加载工具"""
69
+ """Load tools from the built-in tools directory"""
34
70
  tools_dir = Path(__file__).parent
35
71
 
36
- # 遍历目录下的所有.py文件
72
+ # Iterate through all .py files in the directory
37
73
  for file_path in tools_dir.glob("*.py"):
38
- # 跳过基础文件和__init__.py
74
+ # Skip base.py and __init__.py
39
75
  if file_path.name in ["base.py", "__init__.py", "registry.py"]:
40
76
  continue
41
77
 
42
78
  self.register_tool_by_file(str(file_path))
43
79
 
44
80
  def _load_external_tools(self):
45
- """从~/.jarvis_tools加载外部工具"""
46
- external_tools_dir = Path.home() / '.jarvis_tools'
81
+ """Load external tools from ~/.jarvis/tools"""
82
+ external_tools_dir = Path.home() / '.jarvis/tools'
47
83
  if not external_tools_dir.exists():
48
84
  return
49
85
 
50
- # 遍历目录下的所有.py文件
86
+ # Iterate through all .py files in the directory
51
87
  for file_path in external_tools_dir.glob("*.py"):
52
- # 跳过__init__.py
88
+ # Skip __init__.py
53
89
  if file_path.name == "__init__.py":
54
90
  continue
55
91
 
56
92
  self.register_tool_by_file(str(file_path))
57
93
 
58
94
  def register_tool_by_file(self, file_path: str):
59
- """从指定文件加载并注册工具
95
+ """Load and register tools from a specified file
60
96
 
61
97
  Args:
62
- file_path: 工具文件的路径
98
+ file_path: The path of the tool file
63
99
 
64
100
  Returns:
65
- bool: 是否成功加载工具
101
+ bool: Whether the tool is loaded successfully
66
102
  """
67
103
  try:
68
- p_file_path = Path(file_path).resolve() # 获取绝对路径
104
+ p_file_path = Path(file_path).resolve() # Get the absolute path
69
105
  if not p_file_path.exists() or not p_file_path.is_file():
70
106
  PrettyOutput.print(f"File does not exist: {p_file_path}", OutputType.ERROR)
71
107
  return False
72
108
 
73
- # 动态导入模块
109
+ # Dynamically import the module
74
110
  module_name = p_file_path.stem
75
111
  spec = importlib.util.spec_from_file_location(module_name, p_file_path) # type: ignore
76
112
  if not spec or not spec.loader:
@@ -78,30 +114,29 @@ class ToolRegistry:
78
114
  return False
79
115
 
80
116
  module = importlib.util.module_from_spec(spec) # type: ignore
81
- sys.modules[module_name] = module # 添加到 sys.modules 以支持相对导入
117
+ sys.modules[module_name] = module # Add to sys.modules to support relative imports
82
118
  spec.loader.exec_module(module)
83
119
 
84
- # 查找模块中的工具类
120
+ # Find the tool class in the module
85
121
  tool_found = False
86
122
  for item_name in dir(module):
87
123
  item = getattr(module, item_name)
88
- # 检查是否是类,并且有必要的属性
124
+ # Check if it is a class and has the necessary attributes
89
125
  if (isinstance(item, type) and
90
126
  hasattr(item, 'name') and
91
127
  hasattr(item, 'description') and
92
128
  hasattr(item, 'parameters')):
93
129
 
94
- # 实例化工具类,传入模型和输出处理器
130
+ # Instantiate the tool class, passing in the model and output processor
95
131
  tool_instance = item()
96
132
 
97
- # 注册工具
133
+ # Register the tool
98
134
  self.register_tool(
99
135
  name=tool_instance.name,
100
136
  description=tool_instance.description,
101
137
  parameters=tool_instance.parameters,
102
138
  func=tool_instance.execute
103
139
  )
104
- PrettyOutput.print(f"Loaded tool from {p_file_path}: {tool_instance.name}: {tool_instance.description}", OutputType.SUCCESS)
105
140
  tool_found = True
106
141
  break
107
142
 
@@ -116,43 +151,54 @@ class ToolRegistry:
116
151
  return False
117
152
 
118
153
  def register_tool(self, name: str, description: str, parameters: Dict, func: Callable):
119
- """注册新工具"""
154
+ """Register a new tool"""
120
155
  self.tools[name] = Tool(name, description, parameters, func)
121
156
 
122
157
  def get_tool(self, name: str) -> Optional[Tool]:
123
- """获取工具"""
158
+ """Get a tool"""
124
159
  return self.tools.get(name)
125
160
 
126
161
  def get_all_tools(self) -> List[Dict]:
127
- """获取所有工具的Ollama格式定义"""
162
+ """Get all tools in Ollama format definition"""
128
163
  return [tool.to_dict() for tool in self.tools.values()]
129
164
 
130
165
  def execute_tool(self, name: str, arguments: Dict) -> Dict[str, Any]:
131
- """执行指定工具"""
166
+ """Execute a specified tool"""
132
167
  tool = self.get_tool(name)
133
168
  if tool is None:
134
- return {"success": False, "error": f"Tool {name} does not exist"}
169
+ return {"success": False, "stderr": f"Tool {name} does not exist, available tools: {', '.join(self.tools.keys())}", "stdout": ""}
135
170
  return tool.execute(arguments)
136
171
 
137
172
  def handle_tool_calls(self, tool_calls: List[Dict]) -> str:
138
- """处理工具调用,只处理第一个工具"""
173
+ """Handle tool calls, only process the first tool"""
139
174
  try:
140
175
  if not tool_calls:
141
176
  return ""
142
177
 
143
- # 只处理第一个工具调用
178
+ # Only process the first tool call
144
179
  tool_call = tool_calls[0]
145
180
  name = tool_call["name"]
146
181
  args = tool_call["arguments"]
182
+
183
+ tool_call_help = """
184
+ Tool Usage Format:
185
+
186
+ <TOOL_CALL>
187
+ name: tool_name
188
+ arguments:
189
+ param1: value1
190
+ param2: value2
191
+ </TOOL_CALL>
192
+ """
147
193
 
148
194
  if isinstance(args, str):
149
195
  try:
150
196
  args = json.loads(args)
151
197
  except json.JSONDecodeError:
152
- PrettyOutput.print(f"Invalid tool parameters format: {name}", OutputType.ERROR)
198
+ PrettyOutput.print(f"Invalid tool parameters format: {name} {tool_call_help}", OutputType.ERROR)
153
199
  return ""
154
200
 
155
- # 显示工具调用信息
201
+ # Display tool call information
156
202
  PrettyOutput.section(f"Executing tool: {name}", OutputType.TOOL)
157
203
  if isinstance(args, dict):
158
204
  for key, value in args.items():
@@ -160,29 +206,31 @@ class ToolRegistry:
160
206
  else:
161
207
  PrettyOutput.print(f"Parameter: {args}", OutputType.DEBUG)
162
208
 
163
- # 执行工具调用
209
+ # Execute tool call
164
210
  result = self.execute_tool(name, args)
211
+
212
+ stdout = result["stdout"]
213
+ stderr = result.get("stderr", "")
214
+ output_parts = []
215
+ if stdout:
216
+ output_parts.append(f"Output:\n{stdout}")
217
+ if stderr:
218
+ output_parts.append(f"Error:\n{stderr}")
219
+ output = "\n\n".join(output_parts)
220
+ output = "no output and error" if not output else output
165
221
 
166
- # 处理结果
222
+ # Process the result
167
223
  if result["success"]:
168
- stdout = result["stdout"]
169
- stderr = result.get("stderr", "")
170
- output_parts = []
171
- if stdout:
172
- output_parts.append(f"Output:\n{stdout}")
173
- if stderr:
174
- output_parts.append(f"Error:\n{stderr}")
175
- output = "\n\n".join(output_parts)
176
- output = "No output and error" if not output else output
224
+
177
225
  PrettyOutput.section("Execution successful", OutputType.SUCCESS)
178
226
 
179
- # 如果输出超过4k字符,使用大模型总结
227
+ # If the output exceeds 4k characters, use a large model to summarize
180
228
  if len(output) > self.max_context_length:
181
229
  try:
182
230
  PrettyOutput.print("Output is too long, summarizing...", OutputType.PROGRESS)
183
231
  model = PlatformRegistry.get_global_platform_registry().get_normal_platform()
184
232
 
185
- # 如果输出超过最大上下文长度,只取最后部分
233
+ # If the output exceeds the maximum context length, only take the last part
186
234
  max_len = self.max_context_length
187
235
  if len(output) > max_len:
188
236
  output_to_summarize = output[-max_len:]
@@ -215,9 +263,7 @@ Please provide a summary:"""
215
263
  output = f"Output is too long ({len(output)} characters), it is recommended to view the original output.\nPreview of the first 300 characters:\n{output[:300]}..."
216
264
 
217
265
  else:
218
- error_msg = result["error"]
219
- output = f"Execution failed: {error_msg}"
220
- PrettyOutput.section("Execution failed", OutputType.ERROR)
266
+ PrettyOutput.section("Execution failed", OutputType.WARNING)
221
267
 
222
268
  return output
223
269
 
jarvis/tools/search.py CHANGED
@@ -1,39 +1,39 @@
1
1
  from typing import Dict, Any, List
2
2
  from jarvis.models.registry import PlatformRegistry
3
3
  from jarvis.utils import PrettyOutput, OutputType
4
- from jarvis.tools.webpage import WebpageTool
4
+ from jarvis.tools.read_webpage import WebpageTool
5
5
  from playwright.sync_api import sync_playwright
6
6
  from urllib.parse import quote
7
7
 
8
8
  def bing_search(query):
9
9
  try:
10
10
  with sync_playwright() as p:
11
- # 启动浏览器时设置参数
11
+ # Set parameters when starting the browser
12
12
  browser = p.chromium.launch(
13
- headless=True, # 无头模式
13
+ headless=True, # Headless mode
14
14
  args=['--disable-gpu', '--no-sandbox', '--disable-dev-shm-usage']
15
15
  )
16
16
 
17
- # 创建新页面并设置超时
17
+ # Create a new page and set timeout
18
18
  page = browser.new_page(
19
19
  user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
20
20
  viewport={'width': 1920, 'height': 1080}
21
21
  )
22
22
 
23
- # 设置页面超时
23
+ # Set page timeout
24
24
  page.set_default_timeout(60000)
25
25
 
26
- # 访问搜索页面
26
+ # Visit search page
27
27
  url = f"https://www.bing.com/search?q={quote(query)}&form=QBLH&sp=-1"
28
28
  page.goto(url, wait_until="networkidle")
29
29
 
30
- # 等待搜索结果加载
30
+ # Wait for search results to load
31
31
  page.wait_for_selector("#b_results", state="visible", timeout=30000)
32
32
 
33
- # 等待一下以确保结果完全加载
33
+ # Wait for a moment to ensure the results are fully loaded
34
34
  page.wait_for_timeout(1000)
35
35
 
36
- # 提取搜索结果
36
+ # Extract search results
37
37
  summaries = page.evaluate("""() => {
38
38
  const results = [];
39
39
  const elements = document.querySelectorAll("#b_results > .b_algo");
@@ -96,7 +96,7 @@ class SearchTool:
96
96
  if not results:
97
97
  return []
98
98
 
99
- # 格式化搜索结果
99
+ # Format search results
100
100
  formatted_results = []
101
101
  for result in results[:max_results]:
102
102
  formatted_results.append({
@@ -142,15 +142,16 @@ When answering, pay attention to:
142
142
  PrettyOutput.print(f"Search query: {query}", OutputType.INFO)
143
143
  PrettyOutput.print(f"Related question: {question}", OutputType.INFO)
144
144
 
145
- # 获取搜索结果
145
+ # Get search results
146
146
  results = self._search(query, max_results)
147
147
  if not results:
148
148
  return {
149
149
  "success": False,
150
- "error": "No search results found"
150
+ "stdout": "",
151
+ "stderr": "No search results found"
151
152
  }
152
153
 
153
- # 收集网页内容
154
+ # Collect webpage content
154
155
  contents = []
155
156
  for i, result in enumerate(results, 1):
156
157
  try:
@@ -166,7 +167,8 @@ When answering, pay attention to:
166
167
  if not contents:
167
168
  return {
168
169
  "success": False,
169
- "error": "No valid search results found"
170
+ "stdout": "",
171
+ "stderr": "No valid search results found"
170
172
  }
171
173
 
172
174
  # Extract information
@@ -182,11 +184,12 @@ When answering, pay attention to:
182
184
  except Exception as e:
183
185
  return {
184
186
  "success": False,
185
- "error": f"搜索失败: {str(e)}"
187
+ "stdout": "",
188
+ "stderr": f"Search failed: {str(e)}"
186
189
  }
187
190
 
188
191
  def main():
189
- """命令行直接运行搜索工具"""
192
+ """Command line directly run search tool"""
190
193
  import argparse
191
194
  import sys
192
195
 
@@ -0,0 +1,61 @@
1
+ from typing import Dict, Any, List
2
+
3
+ from jarvis.utils import OutputType, PrettyOutput
4
+ from jarvis.jarvis_coder.file_select import select_files
5
+
6
+
7
+ class CodeFileSelecterTool:
8
+ name = "select_code_files"
9
+ description = "Select and manage code files for modification with interactive file selection"
10
+ parameters = {
11
+ "type": "object",
12
+ "properties": {
13
+ "related_files": {
14
+ "type": "array",
15
+ "items": {
16
+ "type": "string",
17
+ },
18
+ "description": "List of initially related files",
19
+ "default": []
20
+ },
21
+ "root_dir": {
22
+ "type": "string",
23
+ "description": "Root directory of the codebase",
24
+ "default": "."
25
+ }
26
+ },
27
+ "required": ["related_files"]
28
+ }
29
+
30
+ def execute(self, args: Dict) -> Dict[str, Any]:
31
+ """Execute interactive file selection"""
32
+ try:
33
+ related_files = args["related_files"]
34
+ root_dir = args.get("root_dir", ".")
35
+
36
+ PrettyOutput.print("Starting interactive file selection...", OutputType.INFO)
37
+
38
+ # Use file_select module to handle file selection
39
+ selected_files = select_files(
40
+ related_files=related_files,
41
+ root_dir=root_dir
42
+ )
43
+
44
+ # Format output for display
45
+ output = "Selected files:\n"
46
+ for file in selected_files:
47
+ output += f"- {file}\n"
48
+
49
+ return {
50
+ "success": True,
51
+ "stdout": output,
52
+ "stderr": ""
53
+ }
54
+
55
+ except Exception as e:
56
+ PrettyOutput.print(str(e), OutputType.ERROR)
57
+ return {
58
+ "success": False,
59
+ "stdout": "",
60
+ "stderr": str(e)
61
+ }
jarvis/tools/thinker.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from typing import Dict, Any
2
- from jarvis.utils import OutputType, PrettyOutput, load_env_from_file
2
+ from jarvis.utils import OutputType, PrettyOutput, init_env
3
3
  from jarvis.models.registry import PlatformRegistry
4
4
 
5
5
  class ThinkerTool:
@@ -104,7 +104,8 @@ Related context:
104
104
  if not response:
105
105
  return {
106
106
  "success": False,
107
- "error": "Failed to obtain valid analysis results"
107
+ "stdout": "",
108
+ "stderr": "Failed to obtain valid analysis results"
108
109
  }
109
110
 
110
111
  return {
@@ -117,14 +118,15 @@ Related context:
117
118
  PrettyOutput.print(f"Thinking analysis failed: {str(e)}", OutputType.ERROR)
118
119
  return {
119
120
  "success": False,
120
- "error": f"Execution failed: {str(e)}"
121
+ "stdout": "",
122
+ "stderr": f"Execution failed: {str(e)}"
121
123
  }
122
124
 
123
125
  def main():
124
126
  """Run tool directly from command line"""
125
127
  import argparse
126
128
 
127
- load_env_from_file()
129
+ init_env()
128
130
 
129
131
  parser = argparse.ArgumentParser(description='Deep thinking analysis tool')
130
132
  parser.add_argument('--question', required=True, help='The problem to analyze')
@@ -143,7 +145,7 @@ def main():
143
145
  PrettyOutput.print("\nAnalysis results:", OutputType.INFO)
144
146
  PrettyOutput.print(result["stdout"], OutputType.INFO)
145
147
  else:
146
- PrettyOutput.print(result["error"], OutputType.ERROR)
148
+ PrettyOutput.print(result["stderr"], OutputType.ERROR)
147
149
 
148
150
  if __name__ == "__main__":
149
151
  main()