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
@@ -1,40 +1,70 @@
1
1
  import re
2
2
  import os
3
3
  from typing import List, Tuple, Dict
4
+ import yaml
5
+ from pathlib import Path
4
6
 
7
+ from jarvis.jarvis_coder.git_utils import generate_commit_message, init_git_repo, save_edit_record
5
8
  from jarvis.models.base import BasePlatform
6
9
  from jarvis.models.registry import PlatformRegistry
7
- from jarvis.utils import OutputType, PrettyOutput, get_multiline_input, while_success
10
+ from jarvis.utils import OutputType, PrettyOutput, get_multiline_input, get_single_line_input, while_success
8
11
 
9
12
  class Patch:
10
- def __init__(self, old_code: str, new_code: str):
11
- self.old_code = old_code
12
- self.new_code = new_code
13
+ def __init__(self, start: int, end: int, new_code: str):
14
+ self.start = start # Line number where patch starts (inclusive)
15
+ self.end = end # Line number where patch ends (exclusive)
16
+ self.new_code = new_code # New code to insert/replace
13
17
 
14
18
  class PatchHandler:
19
+ def __init__(self):
20
+ self.prompt_file = Path.home() / ".jarvis-coder-patch-prompt"
21
+ self.additional_info = self._load_additional_info()
22
+ self.root_dir = init_git_repo(os.getcwd())
23
+ self.record_dir = os.path.join(self.root_dir, ".jarvis-coder", "record")
24
+ if not os.path.exists(self.record_dir):
25
+ os.makedirs(self.record_dir)
26
+ def _load_additional_info(self) -> str:
27
+ """Load saved additional info from prompt file"""
28
+ if not self.prompt_file.exists():
29
+ return ""
30
+ try:
31
+ with open(self.prompt_file, 'r') as f:
32
+ data = yaml.safe_load(f)
33
+ return data.get('additional_info', '') if data else ''
34
+ except Exception as e:
35
+ PrettyOutput.print(f"Failed to load additional info: {e}", OutputType.WARNING)
36
+ return ""
15
37
 
38
+ def _save_additional_info(self, info: str):
39
+ """Save additional info to prompt file"""
40
+ try:
41
+ with open(self.prompt_file, 'w') as f:
42
+ yaml.dump({'additional_info': info}, f)
43
+ except Exception as e:
44
+ PrettyOutput.print(f"Failed to save additional info: {e}", OutputType.WARNING)
16
45
 
17
46
  def _extract_patches(self, response: str) -> List[Patch]:
18
- """Extract patches from response
47
+ """Extract patches from response with hexadecimal line numbers
19
48
 
20
49
  Args:
21
50
  response: Model response content
22
51
 
23
52
  Returns:
24
- List[Tuple[str, str, str]]: Patch list, each patch is a tuple of (format, file path, patch content)
53
+ List[Patch]: List of patches, each containing the line range and new code
25
54
  """
26
- # 修改后的正则表达式匹配三种补丁格式
27
- fmt_pattern = r'<PATCH>\n>>>>>> SEARCH\n(.*?)\n?(={5,})\n(.*?)\n?<<<<<< REPLACE\n</PATCH>'
55
+ fmt_pattern = r'<PATCH>\n\[([0-9a-f]+),([0-9a-f]+)\)\n(.*?\n)</PATCH>'
28
56
  ret = []
29
57
  for m in re.finditer(fmt_pattern, response, re.DOTALL):
30
- ret.append(Patch(m.group(1), m.group(3)))
58
+ start = int(m.group(1), 16) # Convert hex to decimal
59
+ end = int(m.group(2), 16)
60
+ new_code = m.group(3)
61
+ ret.append(Patch(start, end, new_code))
31
62
  return ret
32
63
 
33
-
34
64
  def _confirm_and_apply_changes(self, file_path: str) -> bool:
35
65
  """Confirm and apply changes"""
36
66
  os.system(f"git diff --cached {file_path}")
37
- confirm = input(f"\nAccept {file_path} changes? (y/n) [y]: ").lower() or "y"
67
+ confirm = get_single_line_input(f"Accept {file_path} changes? (y/n) [y]").lower() or "y"
38
68
  if confirm == "y":
39
69
  return True
40
70
  else:
@@ -43,87 +73,208 @@ class PatchHandler:
43
73
  os.system(f"git checkout -- {file_path}")
44
74
  PrettyOutput.print(f"Changes to {file_path} have been rolled back", OutputType.WARNING)
45
75
  return False
46
-
76
+
47
77
 
78
+
79
+ def _finalize_changes(self) -> None:
80
+ """Complete changes and commit"""
81
+ PrettyOutput.print("Modification confirmed, committing...", OutputType.INFO)
82
+
83
+ # Add only modified files under git control
84
+ os.system("git add -u")
85
+
86
+ # Then get git diff
87
+ git_diff = os.popen("git diff --cached").read()
88
+
89
+ # Automatically generate commit information, pass in feature
90
+ commit_message = generate_commit_message(git_diff)
91
+
92
+ # Display and confirm commit information
93
+ PrettyOutput.print(f"Automatically generated commit information: {commit_message}", OutputType.INFO)
94
+ user_confirm = get_single_line_input("Use this commit information? (y/n) [y]").lower() or "y"
95
+
96
+ if user_confirm.lower() != "y":
97
+ commit_message = get_single_line_input("Please enter a new commit information")
98
+
99
+ # No need to git add again, it has already been added
100
+ os.system(f"git commit -m '{commit_message}'")
101
+ save_edit_record(self.record_dir, commit_message, git_diff)
102
+
103
+ def _revert_changes(self) -> None:
104
+ """Revert all changes"""
105
+ PrettyOutput.print("Modification cancelled, reverting changes", OutputType.INFO)
106
+ os.system(f"git reset --hard")
107
+ os.system(f"git clean -df")
108
+
109
+ def _check_patches_overlap(self, patches: List[Patch]) -> bool:
110
+ """Check if any patches overlap with each other
111
+
112
+ Args:
113
+ patches: List of patches to check
114
+
115
+ Returns:
116
+ bool: True if patches overlap, False otherwise
117
+ """
118
+ if not patches:
119
+ return False
120
+
121
+ # Sort patches by start line
122
+ sorted_patches = sorted(patches, key=lambda x: x.start)
123
+
124
+ # Check for overlaps
125
+ for i in range(len(sorted_patches) - 1):
126
+ current = sorted_patches[i]
127
+ next_patch = sorted_patches[i + 1]
128
+
129
+ if current.end > next_patch.start:
130
+ PrettyOutput.print(
131
+ f"Overlapping patches detected: [{current.start:04x},{current.end:04x}) and [{next_patch.start:04x},{next_patch.end:04x})",
132
+ OutputType.WARNING
133
+ )
134
+ return True
135
+
136
+ return False
137
+
48
138
  def apply_file_patch(self, file_path: str, patches: List[Patch]) -> bool:
49
- """Apply file patch"""
139
+ """Apply file patches using line numbers"""
50
140
  if not os.path.exists(file_path):
51
141
  base_dir = os.path.dirname(file_path)
52
142
  os.makedirs(base_dir, exist_ok=True)
53
143
  open(file_path, "w", encoding="utf-8").close()
54
- file_content = open(file_path, "r", encoding="utf-8").read()
144
+
145
+ # Check for overlapping patches
146
+ if self._check_patches_overlap(patches):
147
+ PrettyOutput.print("Cannot apply overlapping patches", OutputType.ERROR)
148
+ os.system(f"git reset {file_path}")
149
+ os.system(f"git checkout -- {file_path}")
150
+ return False
151
+
152
+ with open(file_path, "r", encoding="utf-8") as f:
153
+ lines = f.readlines()
154
+
155
+ # Sort patches by start line in reverse order to apply from bottom to top
156
+ patches.sort(key=lambda x: x.start, reverse=True)
157
+
55
158
  for i, patch in enumerate(patches):
56
- if patch.old_code == "" and patch.new_code == "":
57
- PrettyOutput.print(f"Apply patch {i+1}/{len(patches)}: Delete file {file_path}", OutputType.INFO)
58
- file_content = ""
59
- os.system(f"git rm {file_path}")
60
- PrettyOutput.print(f"Apply patch {i+1}/{len(patches)} successfully", OutputType.SUCCESS)
61
- elif patch.old_code == "":
62
- PrettyOutput.print(f"Apply patch {i+1}/{len(patches)}: Replace file {file_path} content: \n{patch.new_code}", OutputType.INFO)
63
- file_content = patch.new_code
64
- open(file_path, "w", encoding="utf-8").write(patch.new_code)
65
- os.system(f"git add {file_path}")
66
- PrettyOutput.print(f"Apply patch {i+1}/{len(patches)} successfully", OutputType.SUCCESS)
159
+ PrettyOutput.print(f"Applying patch {i+1}/{len(patches)} at lines [{patch.start},{patch.end})", OutputType.INFO)
160
+
161
+ if patch.start > len(lines):
162
+ PrettyOutput.print(f"Invalid patch: start line {patch.start} exceeds file length {len(lines)}", OutputType.WARNING)
163
+ os.system(f"git reset {file_path}")
164
+ os.system(f"git checkout -- {file_path}")
165
+ return False
166
+
167
+ if patch.new_code:
168
+ new_lines = patch.new_code.splitlines(keepends=True)
169
+ lines[patch.start:patch.end] = new_lines
67
170
  else:
68
- PrettyOutput.print(f"Apply patch {i+1}/{len(patches)}: File original content: \n{patch.old_code}\nReplace with: \n{patch.new_code}", OutputType.INFO)
69
- if file_content.find(patch.old_code) == -1:
70
- PrettyOutput.print(f"File {file_path} does not contain {patch.old_code}", OutputType.WARNING)
71
- os.system(f"git reset {file_path}")
72
- os.system(f"git checkout -- {file_path}")
73
- return False
74
- else:
75
- file_content = file_content.replace(patch.old_code, patch.new_code, 1)
76
- open(file_path, "w", encoding="utf-8").write(file_content)
77
- os.system(f"git add {file_path}")
78
- PrettyOutput.print(f"Apply patch {i+1}/{len(patches)} successfully", OutputType.SUCCESS)
171
+ del lines[patch.start:patch.end]
172
+
173
+ # Write modified content back to file
174
+ with open(file_path, "w", encoding="utf-8") as f:
175
+ f.writelines(lines)
176
+
177
+ os.system(f"git add {file_path}")
178
+ PrettyOutput.print(f"Successfully applied all patches to {file_path}", OutputType.SUCCESS)
79
179
  return True
80
180
 
81
181
 
82
- def retry_comfirm(self) -> Tuple[str, str]:# Restore user selection logic
83
- choice = input("\nPlease choose an action: (1) Retry (2) Skip (3) Completely stop [1]: ") or "1"
182
+ def retry_comfirm(self) -> Tuple[str, str]:
183
+ choice = get_single_line_input("\nPlease choose an action: (1) Retry (2) Skip (3) Completely stop [1]: ") or "1"
84
184
  if choice == "2":
85
185
  return "skip", ""
86
186
  if choice == "3":
87
187
  return "break", ""
88
- return "continue", get_multiline_input("Please enter additional information and requirements:")
188
+
189
+ feedback = get_multiline_input("Please enter additional information and requirements:")
190
+ if feedback:
191
+ save_prompt = get_single_line_input("Would you like to save this as general feedback for future patches? (y/n) [n]: ").lower() or "n"
192
+ if save_prompt == "y":
193
+ self._save_additional_info(feedback)
194
+ PrettyOutput.print("Feedback saved for future use", OutputType.SUCCESS)
195
+
196
+ return "continue", feedback
89
197
 
90
- def apply_patch(self, feature: str, raw_plan: str, structed_plan: Dict[str, str]) -> Tuple[bool, str]:
198
+ def apply_patch(self, feature: str, structed_plan: Dict[str, str]) -> Tuple[bool, str]:
91
199
  """Apply patch (main entry)"""
200
+ feedback = ""
92
201
  for file_path, current_plan in structed_plan.items():
93
- additional_info = ""
202
+ additional_info = self.additional_info # Initialize with saved info
94
203
  while True:
95
-
96
204
  if os.path.exists(file_path):
97
- content = open(file_path, "r", encoding="utf-8").read()
205
+ # Read file and add line numbers
206
+ lines = []
207
+ with open(file_path, "r", encoding="utf-8") as f:
208
+ for i, line in enumerate(f):
209
+ lines.append(f"{i:04x}{line}") # Changed from i+1 to i for 0-based indexing
210
+ content = "".join(lines)
98
211
  else:
99
212
  content = "<File does not exist, need to create>"
213
+
100
214
  prompt = """You are a senior software development expert who can generate code patches based on the complete modification plan, current original code file path, code content, and current file's modification plan. The output format should be as follows:
101
- <PATCH>
102
- >>>>>> SEARCH
103
- old_code
104
- ======
105
- new_code
106
- <<<<<< REPLACE
107
- </PATCH>
108
- Rules:
109
- 1. When old_code is empty, it means replace everything from start to end
110
- 2. When new_code is empty, it means delete old_code
111
- 3. When both old_code and new_code are empty, it means delete the file
112
- Notes:
113
- 1. Multiple patches can be generated
114
- 2. old_code will be replaced with new_code, pay attention to context continuity
115
- 3. Avoid breaking existing code logic when generating patches, e.g., don't insert function definitions inside existing function bodies
116
- 4. Include sufficient context to avoid ambiguity
117
- 5. Patches will be merged using file_content.replace(patch.old_code, patch.new_code, 1), so old_code and new_code need to match exactly, including empty lines, line breaks, whitespace, tabs, and comments
118
- 6. Ensure generated code has correct format (syntax, indentation, line breaks)
119
- 7. Ensure new_code's indentation and format matches old_code
120
- 8. Ensure code is inserted in appropriate locations, e.g., code using variables should be after declarations/definitions
121
- 9. Provide at least 3 lines of context before and after modified code for location
122
-
123
-
124
- """
215
+
216
+ <PATCH>
217
+ [start,end)
218
+ new_code
219
+ </PATCH>
220
+
221
+ Example:
222
+ <PATCH>
223
+ [0004,0004)
224
+ def new_function():
225
+ pass
226
+ </PATCH>
227
+
228
+ means:
229
+ Insert code BEFORE line 4:
230
+ ```
231
+ def new_function():
232
+ pass
233
+ ```
234
+
235
+ Example 2:
236
+ <PATCH>
237
+ [0004,000b)
238
+ aa
239
+ bb
240
+ cc
241
+ </PATCH>
242
+
243
+ means:
244
+ Replace lines [4,11) with:
245
+ ```
246
+ aa
247
+ bb
248
+ cc
249
+ ```
250
+
251
+ Rules:
252
+ 1. start and end are hexadecimal line numbers (e.g., 000a)
253
+ 2. The patch will replace lines [start,end) with new_code (including start, excluding end)
254
+ 3. If start equals end, new_code will be inserted BEFORE that line
255
+ 4. If new_code is empty, lines [start,end) will be deleted
256
+ 5. Multiple patches can be generated
257
+ 6. Each line in the input file starts with its 4-digit hexadecimal line number (0-based)
258
+ 7. Your new_code should NOT include line numbers
259
+ 8. CRITICAL: Patches MUST NOT overlap - ensure each line is modified by at most one patch
260
+ 9. Generate patches from bottom to top of the file
261
+ 10. Ensure new_code maintains correct indentation and formatting
262
+ 11. Each patch should modify no more than 20 lines
263
+ 12. Include sufficient context in new_code to maintain code consistency
264
+ 13. `[` and `)` must be included in the line range
265
+ 14. Line numbers start from 0
266
+ 15. Example of INVALID overlapping patches:
267
+ <PATCH>
268
+ [0001,0005)
269
+ code1
270
+ </PATCH>
271
+ <PATCH>
272
+ [0003,0007) # This overlaps with the previous patch
273
+ code2
274
+ </PATCH>
275
+ """
276
+
125
277
  prompt += f"""# Original requirement: {feature}
126
- # Complete modification plan: {raw_plan}
127
278
  # Current file path: {file_path}
128
279
  # Current file content:
129
280
  <CONTENT>
@@ -146,21 +297,25 @@ class PatchHandler:
146
297
  act, msg = self.retry_comfirm()
147
298
  if act == "break":
148
299
  PrettyOutput.print("Terminate patch application", OutputType.WARNING)
149
- return False, msg
300
+ additional_info = get_multiline_input("Please enter your additional information or suggestions (press Enter to cancel):")
301
+ return False, additional_info
150
302
  if act == "skip":
151
303
  PrettyOutput.print(f"Skip file {file_path}", OutputType.WARNING)
304
+ feedback += f"Skip file {file_path}\n"
305
+ feedback += "Reason: " + get_multiline_input("Please enter your reason:") + "\n"
152
306
  break
153
307
  else:
154
308
  additional_info += msg + "\n"
155
309
  continue
156
310
  else:
311
+ self._finalize_changes()
157
312
  break
158
313
 
159
- return True, ""
314
+ return True, feedback
160
315
 
161
316
 
162
317
 
163
- def handle_patch_application(self, feature: str, raw_plan: str, structed_plan: Dict[str,str]) -> bool:
318
+ def handle_patch_application(self, feature: str, structed_plan: Dict[str,str]) -> Tuple[bool, str]:
164
319
  """Process patch application process
165
320
 
166
321
  Args:
@@ -176,17 +331,10 @@ class PatchHandler:
176
331
  PrettyOutput.print(f"\nFile: {file_path}", OutputType.INFO)
177
332
  PrettyOutput.print(f"Modification plan: \n{patches_code}", OutputType.INFO)
178
333
  # 3. Apply patches
179
- success, error_msg = self.apply_patch(feature, raw_plan, structed_plan)
334
+ success, additional_info = self.apply_patch(feature, structed_plan)
180
335
  if not success:
181
336
  os.system("git reset --hard")
182
- return False
337
+ return False, additional_info
183
338
  # 6. Apply successfully, let user confirm changes
184
339
  PrettyOutput.print("\nPatches applied, please check the modification effect.", OutputType.SUCCESS)
185
- confirm = input("\nKeep these changes? (y/n) [y]: ").lower() or "y"
186
- if confirm != "y":
187
- PrettyOutput.print("User cancelled changes, rolling back", OutputType.WARNING)
188
- os.system("git reset --hard") # Rollback all changes
189
- return False
190
- else:
191
- return True
192
-
340
+ return True, "Modification applied successfully"
@@ -1,7 +1,7 @@
1
1
  import re
2
2
  from typing import Dict, List, Tuple
3
3
  from jarvis.models.registry import PlatformRegistry
4
- from jarvis.utils import PrettyOutput, OutputType, get_multiline_input
4
+ from jarvis.utils import PrettyOutput, OutputType, get_multiline_input, get_single_line_input, is_long_context
5
5
 
6
6
  class PlanGenerator:
7
7
  """Modification plan generator"""
@@ -48,7 +48,42 @@ class PlanGenerator:
48
48
  return prompt
49
49
 
50
50
 
51
- def generate_plan(self, feature: str, related_files: List[Dict]) -> Tuple[str, Dict[str,str]]:
51
+ @staticmethod
52
+ def get_key_code(files: List[str], feature: str)->List[Dict[str, List[str]]]:
53
+ """Extract relevant key code snippets from files"""
54
+ ret = []
55
+ for file in files:
56
+ PrettyOutput.print(f"Analyzing file: {file}", OutputType.INFO)
57
+ model = PlatformRegistry.get_global_platform_registry().get_codegen_platform()
58
+ model.set_suppress_output(True)
59
+ file_path = file
60
+ content = open(file_path, "r", encoding="utf-8").read()
61
+
62
+ try:
63
+ prompt = f"""You are a code analysis expert who can extract relevant snippets from code.
64
+ Please return in the following format:
65
+ <PART>
66
+ content
67
+ </PART>
68
+
69
+ Multiple snippets can be returned. If the file content is not relevant to the requirement, return empty.
70
+
71
+ Requirement: {feature}
72
+ File path: {file_path}
73
+ Code content:
74
+ {content}
75
+ """
76
+
77
+ # 调用大模型进行分析
78
+ response = model.chat_until_success(prompt)
79
+
80
+ parts = re.findall(r'<PART>\n(.*?)\n</PART>', response, re.DOTALL)
81
+ ret.append({"file_path": file, "parts": parts})
82
+ except Exception as e:
83
+ PrettyOutput.print(f"Failed to analyze file: {str(e)}", OutputType.ERROR)
84
+ return ret
85
+
86
+ def generate_plan(self, feature: str, related_files: List[str]) -> Dict[str,str]:
52
87
  """Generate modification plan
53
88
 
54
89
  Args:
@@ -59,8 +94,15 @@ class PlanGenerator:
59
94
  Tuple[str, Dict[str,str]]: Modification plan, return None if user cancels
60
95
  """
61
96
  additional_info = ""
97
+ file_info = []
98
+ if is_long_context(related_files):
99
+ file_info = PlanGenerator.get_key_code(related_files, feature)
100
+ else:
101
+ for file in related_files:
102
+ file_info.append({"file_path": file, "parts": [open(file, "r", encoding="utf-8").read()]})
103
+
62
104
  while True:
63
- prompt = self._build_prompt(feature, related_files, additional_info)
105
+ prompt = self._build_prompt(feature, file_info, additional_info)
64
106
  # Build prompt
65
107
  PrettyOutput.print("Start generating modification plan...", OutputType.PROGRESS)
66
108
 
@@ -71,17 +113,17 @@ class PlanGenerator:
71
113
  PrettyOutput.print("Modification plan generation failed, please try again", OutputType.ERROR)
72
114
  tmp = get_multiline_input("Please enter your additional information or suggestions (press Enter to cancel):")
73
115
  if tmp == "__interrupt__" or prompt == "":
74
- return "", {}
116
+ return {}
75
117
  additional_info += tmp + "\n"
76
118
  continue
77
- user_input = input("\nDo you agree with this modification plan? (y/n) [y]: ").strip().lower() or 'y'
119
+ user_input = get_single_line_input("Do you agree with this modification plan? (y/n) [y]").strip().lower() or 'y'
78
120
  if user_input == 'y' or user_input == '':
79
- return raw_plan, structed_plan
121
+ return structed_plan
80
122
  elif user_input == 'n':
81
123
  # Get user feedback
82
124
  tmp = get_multiline_input("Please enter your additional information or suggestions (press Enter to cancel):")
83
125
  if prompt == "__interrupt__" or prompt == "":
84
- return "", {}
126
+ return {}
85
127
  additional_info += tmp + "\n"
86
128
  continue
87
129
 
@@ -1,5 +1,5 @@
1
1
  from jarvis.models.registry import PlatformRegistry
2
- from jarvis.utils import PrettyOutput, OutputType, load_env_from_file, get_multiline_input
2
+ from jarvis.utils import PrettyOutput, OutputType, init_env, get_multiline_input
3
3
 
4
4
  def list_platforms():
5
5
  """List all supported platforms and models"""
@@ -106,7 +106,7 @@ def main():
106
106
  """Main function"""
107
107
  import argparse
108
108
 
109
- load_env_from_file()
109
+ init_env()
110
110
 
111
111
  parser = argparse.ArgumentParser(description='Jarvis AI Platform')
112
112
  subparsers = parser.add_subparsers(dest='command', help='Available subcommands')
jarvis/jarvis_rag/main.py CHANGED
@@ -4,7 +4,7 @@ import faiss
4
4
  from typing import List, Tuple, Optional, Dict
5
5
  import pickle
6
6
  from jarvis.utils import OutputType, PrettyOutput, get_file_md5, get_max_context_length, load_embedding_model, load_rerank_model
7
- from jarvis.utils import load_env_from_file
7
+ from jarvis.utils import init_env
8
8
  from dataclasses import dataclass
9
9
  from tqdm import tqdm
10
10
  import fitz # PyMuPDF for PDF files
@@ -90,7 +90,7 @@ class TextFileProcessor(FileProcessor):
90
90
  continue
91
91
 
92
92
  if not detected_encoding:
93
- raise UnicodeDecodeError(f"Failed to decode file with supported encodings: {file_path}")
93
+ raise UnicodeDecodeError(f"Failed to decode file with supported encodings: {file_path}") # type: ignore
94
94
 
95
95
  # Use the detected encoding to read the file
96
96
  with open(file_path, 'r', encoding=detected_encoding, errors='replace') as f:
@@ -114,7 +114,7 @@ class PDFProcessor(FileProcessor):
114
114
  @staticmethod
115
115
  def extract_text(file_path: str) -> str:
116
116
  text_parts = []
117
- with fitz.open(file_path) as doc:
117
+ with fitz.open(file_path) as doc: # type: ignore
118
118
  for page in doc:
119
119
  text_parts.append(page.get_text())
120
120
  return "\n".join(text_parts)
@@ -137,7 +137,7 @@ class RAGTool:
137
137
  Args:
138
138
  root_dir: Project root directory
139
139
  """
140
- load_env_from_file()
140
+ init_env()
141
141
  self.root_dir = root_dir
142
142
  os.chdir(self.root_dir)
143
143
 
@@ -248,7 +248,7 @@ class RAGTool:
248
248
 
249
249
  # Create a flat index to store original vectors, for reconstruction
250
250
  self.flat_index = faiss.IndexFlatIP(self.vector_dim)
251
- self.flat_index.add(vectors)
251
+ self.flat_index.add(vectors) # type: ignore
252
252
 
253
253
  # Create an IVF index for fast search
254
254
  nlist = max(4, int(vectors.shape[0] / 1000)) # 每1000个向量一个聚类中心
@@ -256,8 +256,8 @@ class RAGTool:
256
256
  self.index = faiss.IndexIVFFlat(quantizer, self.vector_dim, nlist, faiss.METRIC_INNER_PRODUCT)
257
257
 
258
258
  # Train and add vectors
259
- self.index.train(vectors)
260
- self.index.add(vectors)
259
+ self.index.train(vectors) # type: ignore
260
+ self.index.add(vectors) # type: ignore
261
261
  # Set the number of clusters to probe during search
262
262
  self.index.nprobe = min(nlist, 10)
263
263
 
@@ -341,7 +341,7 @@ class RAGTool:
341
341
  except Exception as e:
342
342
  PrettyOutput.print(f"Failed to get vector representation: {str(e)}",
343
343
  output_type=OutputType.ERROR)
344
- return np.zeros((len(texts), self.vector_dim), dtype=np.float32)
344
+ return np.zeros((len(texts), self.vector_dim), dtype=np.float32) # type: ignore
345
345
 
346
346
  def _process_document_batch(self, documents: List[Document]) -> List[np.ndarray]:
347
347
  """Process a batch of documents vectorization
@@ -361,7 +361,7 @@ Content: {doc.content}
361
361
  """
362
362
  texts.append(combined_text)
363
363
 
364
- return self._get_embedding_batch(texts)
364
+ return self._get_embedding_batch(texts) # type: ignore
365
365
 
366
366
  def _process_file(self, file_path: str) -> List[Document]:
367
367
  """Process a single file"""
@@ -516,7 +516,7 @@ Content: {doc.content}
516
516
  if d.metadata['file_path'] == doc.metadata['file_path']), None)
517
517
  if doc_idx is not None:
518
518
  # Reconstruct vectors from flat index
519
- vector = np.zeros((1, self.vector_dim), dtype=np.float32)
519
+ vector = np.zeros((1, self.vector_dim), dtype=np.float32) # type: ignore
520
520
  self.flat_index.reconstruct(doc_idx, vector.ravel())
521
521
  unchanged_vectors.append(vector)
522
522
 
@@ -585,7 +585,7 @@ Content: {doc.content}
585
585
 
586
586
  # Initial search more results for MMR
587
587
  initial_k = min(top_k * 2, len(self.documents))
588
- distances, indices = self.index.search(query_vector, initial_k)
588
+ distances, indices = self.index.search(query_vector, initial_k) # type: ignore
589
589
 
590
590
  # Get valid results
591
591
  valid_indices = indices[0][indices[0] != -1]
@@ -4,11 +4,11 @@ import os
4
4
  import sys
5
5
  import readline
6
6
  from typing import Optional
7
- from yaspin import yaspin
8
- from yaspin.spinners import Spinners
7
+ from yaspin import yaspin # type: ignore
8
+ from yaspin.spinners import Spinners # type: ignore
9
9
 
10
10
  from jarvis.models.registry import PlatformRegistry
11
- from jarvis.utils import PrettyOutput, OutputType, load_env_from_file
11
+ from jarvis.utils import PrettyOutput, OutputType, init_env
12
12
 
13
13
  def execute_command(command: str) -> None:
14
14
  """Show command and allow user to edit, then execute, Ctrl+C to cancel"""
@@ -27,6 +27,7 @@ def execute_command(command: str) -> None:
27
27
  except Exception as e:
28
28
  PrettyOutput.print(f"Failed to execute command: {str(e)}", OutputType.ERROR)
29
29
 
30
+
30
31
  def process_request(request: str) -> Optional[str]:
31
32
  """Process user request and return corresponding shell command
32
33
 
@@ -38,7 +39,6 @@ def process_request(request: str) -> Optional[str]:
38
39
  """
39
40
  try:
40
41
  # Get language model instance
41
- PlatformRegistry.suppress_output = True
42
42
  model = PlatformRegistry.get_global_platform_registry().get_normal_platform()
43
43
  model.set_suppress_output(True)
44
44
 
@@ -90,7 +90,7 @@ Remember: Only return the command itself, without any additional content.
90
90
 
91
91
  def main():
92
92
  # 创建参数解析器
93
- load_env_from_file()
93
+ init_env()
94
94
  parser = argparse.ArgumentParser(
95
95
  description="Convert natural language requirements to shell commands",
96
96
  formatter_class=argparse.RawDescriptionHelpFormatter,
jarvis/models/base.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
+ import re
2
3
  from typing import Dict, List, Tuple
3
4
 
4
5
  from jarvis.utils import OutputType, PrettyOutput, while_success, while_true
@@ -26,7 +27,11 @@ class BasePlatform(ABC):
26
27
  raise NotImplementedError("chat is not implemented")
27
28
 
28
29
  def chat_until_success(self, message: str) -> str:
29
- return while_true(lambda: while_success(lambda: self.chat(message), 5), 5)
30
+ def _chat():
31
+ response = self.chat(message)
32
+ response = re.sub(r'<think>(.*?)</think>', '', response, flags=re.DOTALL)
33
+ return response
34
+ return while_true(lambda: while_success(lambda: _chat(), 5), 5)
30
35
 
31
36
  @abstractmethod
32
37
  def upload_files(self, file_list: List[str]) -> List[Dict]: