jarvis-ai-assistant 0.1.102__py3-none-any.whl → 0.1.104__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 (55) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/agent.py +138 -117
  3. jarvis/jarvis_code_agent/code_agent.py +234 -0
  4. jarvis/{jarvis_coder → jarvis_code_agent}/file_select.py +19 -22
  5. jarvis/jarvis_code_agent/patch.py +120 -0
  6. jarvis/jarvis_code_agent/relevant_files.py +97 -0
  7. jarvis/jarvis_codebase/main.py +871 -0
  8. jarvis/jarvis_platform/main.py +5 -3
  9. jarvis/jarvis_rag/main.py +818 -0
  10. jarvis/jarvis_smart_shell/main.py +2 -2
  11. jarvis/models/ai8.py +3 -1
  12. jarvis/models/kimi.py +36 -30
  13. jarvis/models/ollama.py +17 -11
  14. jarvis/models/openai.py +15 -12
  15. jarvis/models/oyi.py +24 -7
  16. jarvis/models/registry.py +1 -25
  17. jarvis/tools/__init__.py +0 -6
  18. jarvis/tools/ask_codebase.py +96 -0
  19. jarvis/tools/ask_user.py +1 -9
  20. jarvis/tools/chdir.py +2 -37
  21. jarvis/tools/code_review.py +210 -0
  22. jarvis/tools/create_code_test_agent.py +115 -0
  23. jarvis/tools/create_ctags_agent.py +164 -0
  24. jarvis/tools/create_sub_agent.py +2 -2
  25. jarvis/tools/execute_shell.py +2 -2
  26. jarvis/tools/file_operation.py +2 -2
  27. jarvis/tools/find_in_codebase.py +78 -0
  28. jarvis/tools/git_commiter.py +68 -0
  29. jarvis/tools/methodology.py +3 -3
  30. jarvis/tools/rag.py +141 -0
  31. jarvis/tools/read_code.py +116 -0
  32. jarvis/tools/read_webpage.py +1 -1
  33. jarvis/tools/registry.py +47 -31
  34. jarvis/tools/search.py +8 -6
  35. jarvis/tools/select_code_files.py +4 -4
  36. jarvis/utils.py +375 -85
  37. {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.104.dist-info}/METADATA +107 -32
  38. jarvis_ai_assistant-0.1.104.dist-info/RECORD +50 -0
  39. jarvis_ai_assistant-0.1.104.dist-info/entry_points.txt +11 -0
  40. jarvis/jarvis_code_agent/main.py +0 -200
  41. jarvis/jarvis_coder/git_utils.py +0 -123
  42. jarvis/jarvis_coder/patch_handler.py +0 -340
  43. jarvis/jarvis_github/main.py +0 -232
  44. jarvis/tools/create_code_sub_agent.py +0 -56
  45. jarvis/tools/execute_code_modification.py +0 -70
  46. jarvis/tools/find_files.py +0 -119
  47. jarvis/tools/generate_tool.py +0 -174
  48. jarvis/tools/thinker.py +0 -151
  49. jarvis_ai_assistant-0.1.102.dist-info/RECORD +0 -46
  50. jarvis_ai_assistant-0.1.102.dist-info/entry_points.txt +0 -6
  51. /jarvis/{jarvis_coder → jarvis_codebase}/__init__.py +0 -0
  52. /jarvis/{jarvis_github → jarvis_rag}/__init__.py +0 -0
  53. {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.104.dist-info}/LICENSE +0 -0
  54. {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.104.dist-info}/WHEEL +0 -0
  55. {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.104.dist-info}/top_level.txt +0 -0
@@ -73,9 +73,9 @@ class MethodologyTool:
73
73
  Returns:
74
74
  Dict[str, Any]: A dictionary containing the execution result
75
75
  """
76
- operation = args.get("operation")
77
- problem_type = args.get("problem_type")
78
- content = args.get("content")
76
+ operation = args.get("operation", "").strip()
77
+ problem_type = args.get("problem_type", "").strip()
78
+ content = args.get("content", "").strip()
79
79
 
80
80
  if not operation or not problem_type:
81
81
  return {
jarvis/tools/rag.py ADDED
@@ -0,0 +1,141 @@
1
+ from typing import Dict, Any
2
+ import os
3
+ from jarvis.utils import OutputType, PrettyOutput, dont_use_local_model
4
+ from jarvis.jarvis_rag.main import RAGTool as RAGCore
5
+
6
+ class RAGTool:
7
+ name = "rag"
8
+ description = "Ask questions based on a document directory, supporting multiple document formats (txt, pdf, docx, etc.)"
9
+ parameters = {
10
+ "type": "object",
11
+ "properties": {
12
+ "dir": {
13
+ "type": "string",
14
+ "description": "Document directory path, supports both relative and absolute paths"
15
+ },
16
+ "question": {
17
+ "type": "string",
18
+ "description": "The question to ask"
19
+ },
20
+ "rebuild_index": {
21
+ "type": "boolean",
22
+ "description": "Whether to rebuild the index",
23
+ "default": False
24
+ }
25
+ },
26
+ "required": ["dir", "question"]
27
+ }
28
+
29
+ @staticmethod
30
+ def check() -> bool:
31
+ return not dont_use_local_model()
32
+
33
+ def __init__(self):
34
+ """Initialize RAG tool"""
35
+ self.rag_instances = {} # Cache RAG instances for different directories
36
+
37
+ def _get_rag_instance(self, dir_path: str) -> RAGCore:
38
+ """Get or create RAG instance
39
+
40
+ Args:
41
+ dir_path: The absolute path of the document directory
42
+
43
+ Returns:
44
+ RAGCore: RAG instance
45
+ """
46
+ if dir_path not in self.rag_instances:
47
+ self.rag_instances[dir_path] = RAGCore(dir_path)
48
+ return self.rag_instances[dir_path]
49
+
50
+ def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
51
+ """Execute document question and answer
52
+
53
+ Args:
54
+ args: A dictionary containing parameters
55
+ - dir: The document directory path
56
+ - question: The question to ask
57
+ - rebuild_index: Whether to rebuild the index
58
+
59
+ Returns:
60
+ Dict[str, Any]: The execution result
61
+ """
62
+ try:
63
+ # Get parameters
64
+ dir_path = os.path.expanduser(args["dir"]) # Expand ~ paths
65
+ dir_path = os.path.abspath(dir_path) # Convert to absolute path
66
+ question = args["question"]
67
+ rebuild_index = args.get("rebuild_index", False)
68
+
69
+ # Check if the directory exists
70
+ if not os.path.exists(dir_path):
71
+ return {
72
+ "success": False,
73
+ "stdout": "",
74
+ "stderr": f"Directory does not exist: {dir_path}"
75
+ }
76
+
77
+ # Check if it is a directory
78
+ if not os.path.isdir(dir_path):
79
+ return {
80
+ "success": False,
81
+ "stdout": "",
82
+ "stderr": f"The path is not a directory: {dir_path}"
83
+ }
84
+
85
+ # Get RAG instance
86
+ rag = self._get_rag_instance(dir_path)
87
+
88
+ # If you need to rebuild the index or the index does not exist
89
+ if rebuild_index or not rag.is_index_built():
90
+ PrettyOutput.print("Building document index...", OutputType.INFO)
91
+ rag.build_index(dir_path)
92
+
93
+ # Execute question and answer
94
+ PrettyOutput.print(f"Question: {question}", OutputType.INFO)
95
+ response = rag.ask(question)
96
+
97
+ if response is None:
98
+ return {
99
+ "success": False,
100
+ "stdout": "",
101
+ "stderr": "Failed to get answer, possibly no relevant documents found"
102
+ }
103
+
104
+ return {
105
+ "success": True,
106
+ "stdout": response,
107
+ "stderr": ""
108
+ }
109
+
110
+ except Exception as e:
111
+ PrettyOutput.print(f"Document question and answer failed: {str(e)}", OutputType.ERROR)
112
+ return {
113
+ "success": False,
114
+ "stdout": "",
115
+ "stderr": f"Execution failed: {str(e)}"
116
+ }
117
+
118
+ def main():
119
+ """Run the tool directly from the command line"""
120
+ import argparse
121
+
122
+ parser = argparse.ArgumentParser(description='Document question and answer tool')
123
+ parser.add_argument('--dir', required=True, help='Document directory path')
124
+ parser.add_argument('--question', required=True, help='The question to ask')
125
+ parser.add_argument('--rebuild', action='store_true', help='Rebuild index')
126
+ args = parser.parse_args()
127
+
128
+ tool = RAGTool()
129
+ result = tool.execute({
130
+ "dir": args.dir,
131
+ "question": args.question,
132
+ "rebuild_index": args.rebuild
133
+ })
134
+
135
+ if result["success"]:
136
+ PrettyOutput.print(f"{result['stdout']}", OutputType.INFO, lang="markdown")
137
+ else:
138
+ PrettyOutput.print(result["stderr"], OutputType.ERROR)
139
+
140
+ if __name__ == "__main__":
141
+ main()
@@ -0,0 +1,116 @@
1
+ from typing import Dict, Any
2
+ import os
3
+ from jarvis.utils import OutputType, PrettyOutput
4
+
5
+
6
+ class ReadCodeTool:
7
+ """Read code file with line numbers"""
8
+
9
+ name = "read_code"
10
+ description = "Read code file with line numbers"
11
+ parameters = {
12
+ "type": "object",
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
27
+ }
28
+ },
29
+ "required": ["filepath"]
30
+ }
31
+
32
+ def execute(self, args: Dict) -> Dict[str, Any]:
33
+ """Execute code reading with line numbers
34
+
35
+ 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
+
41
+ Returns:
42
+ Dict containing:
43
+ - success: Boolean indicating success
44
+ - stdout: File content with line numbers
45
+ - stderr: Error message if any
46
+ """
47
+ 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
+ abs_path = os.path.abspath(filepath)
54
+ PrettyOutput.print(f"Reading code file: {abs_path}", OutputType.INFO)
55
+
56
+ # Check if file exists
57
+ if not os.path.exists(filepath):
58
+ return {
59
+ "success": False,
60
+ "stdout": "",
61
+ "stderr": f"File does not exist: {filepath}"
62
+ }
63
+
64
+ # Check file size
65
+ if os.path.getsize(filepath) > 10 * 1024 * 1024: # 10MB
66
+ return {
67
+ "success": False,
68
+ "stdout": "",
69
+ "stderr": "File too large (>10MB)"
70
+ }
71
+
72
+ # Read file content
73
+ try:
74
+ with open(filepath, 'r', encoding='utf-8') as f:
75
+ lines = f.readlines()
76
+ except UnicodeDecodeError:
77
+ return {
78
+ "success": False,
79
+ "stdout": "",
80
+ "stderr": "Failed to decode file with UTF-8 encoding"
81
+ }
82
+
83
+ # Validate line range
84
+ if start_line < 0:
85
+ start_line = 0
86
+ if end_line == -1 or end_line > len(lines):
87
+ end_line = len(lines)
88
+ if start_line >= end_line:
89
+ return {
90
+ "success": False,
91
+ "stdout": "",
92
+ "stderr": f"Invalid line range: [{start_line}, {end_line})"
93
+ }
94
+
95
+ # Format lines with hexadecimal line numbers
96
+ formatted_lines = []
97
+ for i, line in enumerate(lines[start_line:end_line]):
98
+ line_num = start_line + i
99
+ formatted_lines.append(f"{line_num:>5}:{line}")
100
+
101
+ content = "".join(formatted_lines)
102
+
103
+ PrettyOutput.print(f"File: {filepath}\nLines: [{start_line}, {end_line})\n{content}", OutputType.CODE)
104
+
105
+ return {
106
+ "success": True,
107
+ "stdout": content,
108
+ "stderr": ""
109
+ }
110
+
111
+ except Exception as e:
112
+ return {
113
+ "success": False,
114
+ "stdout": "",
115
+ "stderr": f"Failed to read code: {str(e)}"
116
+ }
@@ -20,7 +20,7 @@ class WebpageTool:
20
20
  def execute(self, args: Dict) -> Dict[str, Any]:
21
21
  """Read webpage content"""
22
22
  try:
23
- url = args["url"]
23
+ url = args["url"].strip()
24
24
 
25
25
  # Set request headers
26
26
  headers = {
jarvis/tools/registry.py CHANGED
@@ -1,6 +1,4 @@
1
- import importlib
2
1
  import json
3
- import os
4
2
  from pathlib import Path
5
3
  import sys
6
4
  from typing import Any, Callable, Dict, List, Optional
@@ -10,20 +8,7 @@ from jarvis.tools.base import Tool
10
8
  from jarvis.utils import OutputType, PrettyOutput, get_max_context_length
11
9
 
12
10
 
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 += """
26
- Tool Usage Format:
11
+ tool_call_help = """Tool Usage Format:
27
12
 
28
13
  <TOOL_CALL>
29
14
  name: tool_name
@@ -31,13 +16,48 @@ arguments:
31
16
  param1: value1
32
17
  param2: value2
33
18
  </TOOL_CALL>
34
- ---------------------------------------------
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
28
+ - Request user guidance when multiple iterations show no progress
29
+ - ALWAYS use | syntax for string parameters to prevent parsing errors
30
+ Example:
31
+ <TOOL_CALL>
32
+ name: execute_shell
33
+ arguments:
34
+ command: |
35
+ git status --porcelain
36
+ </TOOL_CALL>
37
+ <TOOL_CALL>
38
+ name: execute_shell
39
+ arguments:
40
+ command: |
41
+ git commit -m "fix bug"
42
+ </TOOL_CALL>
43
+
44
+ - If you can start executing the task, please start directly without asking the user if you can begin.
35
45
  """
36
- return tools_prompt
37
- return ""
38
46
 
39
47
  class ToolRegistry:
40
- global_tool_registry = None # type: ignore
48
+ def load_tools(self) -> str:
49
+ """Load tools"""
50
+ tools = self.get_all_tools()
51
+ if tools:
52
+ tools_prompt = "Available tools:\n"
53
+ for tool in tools:
54
+ tools_prompt += f"- Name: {tool['name']}\n"
55
+ tools_prompt += f" Description: {tool['description']}\n"
56
+ tools_prompt += f" Parameters: {tool['parameters']}\n"
57
+ tools_prompt += tool_call_help
58
+ return tools_prompt
59
+ return ""
60
+
41
61
  def __init__(self):
42
62
  """Initialize tool registry"""
43
63
  self.tools: Dict[str, Tool] = {}
@@ -57,12 +77,6 @@ class ToolRegistry:
57
77
  def dont_use_tools(self, names: List[str]):
58
78
  """Remove specified tools from the registry"""
59
79
  self.tools = {name: tool for name, tool in self.tools.items() if name not in names}
60
- @staticmethod
61
- def get_global_tool_registry():
62
- """Get the global tool registry"""
63
- if ToolRegistry.global_tool_registry is None:
64
- ToolRegistry.global_tool_registry = ToolRegistry()
65
- return ToolRegistry.global_tool_registry
66
80
 
67
81
  def _load_builtin_tools(self):
68
82
  """Load tools from the built-in tools directory"""
@@ -126,7 +140,6 @@ class ToolRegistry:
126
140
 
127
141
  if hasattr(item, "check"):
128
142
  if not item.check():
129
- PrettyOutput.print(f"Tool {item.name} check failed, skipping", OutputType.INFO)
130
143
  continue
131
144
 
132
145
  # Instantiate the tool class
@@ -143,7 +156,6 @@ class ToolRegistry:
143
156
  break
144
157
 
145
158
  if not tool_found:
146
- PrettyOutput.print(f"No valid tool class found in the file: {p_file_path}", OutputType.INFO)
147
159
  return False
148
160
 
149
161
  return True
@@ -206,11 +218,14 @@ arguments:
206
218
 
207
219
  # Display tool call information
208
220
  PrettyOutput.section(f"Executing tool: {name}", OutputType.TOOL)
221
+ params = "Parameters:\n"
209
222
  if isinstance(args, dict):
210
223
  for key, value in args.items():
211
- PrettyOutput.print(f"Parameter: {key} = {value}", OutputType.DEBUG)
224
+ params += f"{key} = {value}\n"
212
225
  else:
213
- PrettyOutput.print(f"Parameter: {args}", OutputType.DEBUG)
226
+ params += f"{args}"
227
+
228
+ PrettyOutput.print(params, OutputType.INFO)
214
229
 
215
230
  # Execute tool call
216
231
  result = self.execute_tool(name, args)
@@ -270,9 +285,10 @@ Please provide a summary:"""
270
285
 
271
286
  else:
272
287
  PrettyOutput.section("Execution failed", OutputType.WARNING)
288
+ PrettyOutput.print(result["stderr"], OutputType.WARNING)
273
289
 
274
290
  if len(tool_calls) > 1:
275
- output += f"\n\n--- 一次只能执行一个工具, 因此以下工具未执行\n{str(tool_calls[1:])} ---"
291
+ output += f"\n\n--- Only one tool can be executed at a time, the following tools were not executed\n{str(tool_calls[1:])} ---"
276
292
  return output
277
293
 
278
294
  except Exception as e:
jarvis/tools/search.py CHANGED
@@ -211,17 +211,19 @@ def main():
211
211
  PrettyOutput.print(f"\nFound {len(results)} results:", OutputType.INFO)
212
212
 
213
213
  for i, result in enumerate(results[:args.max], 1):
214
- PrettyOutput.print(f"\n{'-'*50}", OutputType.INFO)
214
+ output = []
215
+ output.append(f"\n{'-'*50}")
215
216
  if args.url_only:
216
- PrettyOutput.print(f"{i}. {result['href']}", OutputType.INFO)
217
+ output.append(f"{i}. {result['href']}")
217
218
  else:
218
- PrettyOutput.print(f"{i}. {result['title']}", OutputType.INFO)
219
- PrettyOutput.print(f"Link: {result['href']}", OutputType.INFO)
219
+ output.append(f"{i}. {result['title']}")
220
+ output.append(f"Link: {result['href']}")
220
221
  if result['abstract']:
221
- PrettyOutput.print(f"Abstract: {result['abstract']}", OutputType.INFO)
222
+ output.append(f"Abstract: {result['abstract']}")
223
+ PrettyOutput.print("\n".join(output), OutputType.INFO)
222
224
 
223
225
  except KeyboardInterrupt:
224
- PrettyOutput.print("\nSearch cancelled", OutputType.WARNING)
226
+ PrettyOutput.print("Search cancelled", OutputType.WARNING)
225
227
  sys.exit(1)
226
228
  except Exception as e:
227
229
  PrettyOutput.print(f"Execution error: {str(e)}", OutputType.ERROR)
@@ -1,7 +1,7 @@
1
- from typing import Dict, Any, List
1
+ from typing import Dict, Any
2
2
 
3
3
  from jarvis.utils import OutputType, PrettyOutput
4
- from jarvis.jarvis_coder.file_select import select_files
4
+ from jarvis.jarvis_code_agent.file_select import select_files
5
5
 
6
6
 
7
7
  class CodeFileSelecterTool:
@@ -30,8 +30,8 @@ class CodeFileSelecterTool:
30
30
  def execute(self, args: Dict) -> Dict[str, Any]:
31
31
  """Execute interactive file selection"""
32
32
  try:
33
- related_files = args["related_files"]
34
- root_dir = args.get("root_dir", ".")
33
+ related_files = args.get("related_files", [])
34
+ root_dir = args.get("root_dir", ".").strip()
35
35
 
36
36
  PrettyOutput.print("Starting interactive file selection...", OutputType.INFO)
37
37