jarvis-ai-assistant 0.1.134__py3-none-any.whl → 0.1.138__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +201 -79
- jarvis/jarvis_agent/builtin_input_handler.py +16 -6
- jarvis/jarvis_agent/file_input_handler.py +9 -9
- jarvis/jarvis_agent/jarvis.py +10 -10
- jarvis/jarvis_agent/main.py +12 -11
- jarvis/jarvis_agent/output_handler.py +3 -3
- jarvis/jarvis_agent/patch.py +86 -62
- jarvis/jarvis_agent/shell_input_handler.py +5 -3
- jarvis/jarvis_code_agent/code_agent.py +134 -99
- jarvis/jarvis_code_agent/file_select.py +24 -24
- jarvis/jarvis_dev/main.py +45 -51
- jarvis/jarvis_git_details/__init__.py +0 -0
- jarvis/jarvis_git_details/main.py +179 -0
- jarvis/jarvis_git_squash/main.py +7 -7
- jarvis/jarvis_lsp/base.py +11 -11
- jarvis/jarvis_lsp/cpp.py +14 -14
- jarvis/jarvis_lsp/go.py +13 -13
- jarvis/jarvis_lsp/python.py +8 -8
- jarvis/jarvis_lsp/registry.py +21 -21
- jarvis/jarvis_lsp/rust.py +15 -15
- jarvis/jarvis_methodology/main.py +101 -0
- jarvis/jarvis_multi_agent/__init__.py +11 -11
- jarvis/jarvis_multi_agent/main.py +6 -6
- jarvis/jarvis_platform/__init__.py +1 -1
- jarvis/jarvis_platform/ai8.py +67 -89
- jarvis/jarvis_platform/base.py +14 -13
- jarvis/jarvis_platform/kimi.py +25 -28
- jarvis/jarvis_platform/ollama.py +24 -26
- jarvis/jarvis_platform/openai.py +15 -19
- jarvis/jarvis_platform/oyi.py +48 -50
- jarvis/jarvis_platform/registry.py +27 -28
- jarvis/jarvis_platform/yuanbao.py +38 -42
- jarvis/jarvis_platform_manager/main.py +81 -81
- jarvis/jarvis_platform_manager/openai_test.py +21 -21
- jarvis/jarvis_rag/file_processors.py +18 -18
- jarvis/jarvis_rag/main.py +261 -277
- jarvis/jarvis_smart_shell/main.py +12 -12
- jarvis/jarvis_tools/ask_codebase.py +28 -28
- jarvis/jarvis_tools/ask_user.py +8 -8
- jarvis/jarvis_tools/base.py +4 -4
- jarvis/jarvis_tools/chdir.py +9 -9
- jarvis/jarvis_tools/code_review.py +19 -19
- jarvis/jarvis_tools/create_code_agent.py +15 -15
- jarvis/jarvis_tools/execute_python_script.py +3 -3
- jarvis/jarvis_tools/execute_shell.py +11 -11
- jarvis/jarvis_tools/execute_shell_script.py +3 -3
- jarvis/jarvis_tools/file_analyzer.py +29 -29
- jarvis/jarvis_tools/file_operation.py +22 -20
- jarvis/jarvis_tools/find_caller.py +25 -25
- jarvis/jarvis_tools/find_methodolopy.py +65 -0
- jarvis/jarvis_tools/find_symbol.py +24 -24
- jarvis/jarvis_tools/function_analyzer.py +27 -27
- jarvis/jarvis_tools/git_commiter.py +9 -9
- jarvis/jarvis_tools/lsp_get_diagnostics.py +19 -19
- jarvis/jarvis_tools/methodology.py +23 -62
- jarvis/jarvis_tools/project_analyzer.py +29 -33
- jarvis/jarvis_tools/rag.py +15 -15
- jarvis/jarvis_tools/read_code.py +24 -22
- jarvis/jarvis_tools/read_webpage.py +31 -31
- jarvis/jarvis_tools/registry.py +72 -52
- jarvis/jarvis_tools/tool_generator.py +18 -18
- jarvis/jarvis_utils/config.py +23 -23
- jarvis/jarvis_utils/embedding.py +83 -83
- jarvis/jarvis_utils/git_utils.py +20 -20
- jarvis/jarvis_utils/globals.py +18 -6
- jarvis/jarvis_utils/input.py +10 -9
- jarvis/jarvis_utils/methodology.py +140 -136
- jarvis/jarvis_utils/output.py +11 -11
- jarvis/jarvis_utils/utils.py +22 -70
- {jarvis_ai_assistant-0.1.134.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/METADATA +1 -1
- jarvis_ai_assistant-0.1.138.dist-info/RECORD +85 -0
- {jarvis_ai_assistant-0.1.134.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/entry_points.txt +2 -0
- jarvis/jarvis_tools/select_code_files.py +0 -62
- jarvis_ai_assistant-0.1.134.dist-info/RECORD +0 -82
- {jarvis_ai_assistant-0.1.134.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.134.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.134.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/top_level.txt +0 -0
|
@@ -24,38 +24,38 @@ class WebpageTool:
|
|
|
24
24
|
"""Read webpage content using Playwright to handle JavaScript-rendered pages"""
|
|
25
25
|
try:
|
|
26
26
|
url = args["url"].strip()
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
with sync_playwright() as p:
|
|
29
29
|
# Launch browser
|
|
30
30
|
browser = p.chromium.launch(
|
|
31
31
|
headless=True,
|
|
32
32
|
args=['--disable-gpu', '--no-sandbox', '--disable-dev-shm-usage']
|
|
33
33
|
)
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
# Create a new page with appropriate settings
|
|
36
36
|
page = browser.new_page(
|
|
37
37
|
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',
|
|
38
38
|
viewport={'width': 1920, 'height': 1080}
|
|
39
39
|
)
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
# Set timeout to avoid long waits
|
|
42
42
|
page.set_default_timeout(30000) # 30 seconds
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
try:
|
|
45
45
|
# Navigate to URL and wait for page to load
|
|
46
46
|
response = page.goto(url, wait_until="domcontentloaded")
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
# Additional wait for network to be idle (with a timeout)
|
|
49
49
|
try:
|
|
50
50
|
page.wait_for_load_state("networkidle", timeout=10000)
|
|
51
51
|
except PlaywrightTimeoutError:
|
|
52
52
|
# Continue even if network doesn't become completely idle
|
|
53
53
|
pass
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
# Make sure we got a valid response
|
|
56
56
|
if not response or response.status >= 400:
|
|
57
57
|
raise Exception(f"Failed to load page: HTTP {response.status if response else 'No response'}")
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
# Get page title safely
|
|
60
60
|
title = "No title"
|
|
61
61
|
try:
|
|
@@ -68,26 +68,26 @@ class WebpageTool:
|
|
|
68
68
|
title = title_element.text_content() or "No title"
|
|
69
69
|
except Exception:
|
|
70
70
|
pass
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
# Get the HTML content after JavaScript execution
|
|
73
73
|
html_content = page.content()
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
except Exception as e:
|
|
76
76
|
raise Exception(f"Error navigating to page: {str(e)}")
|
|
77
77
|
finally:
|
|
78
78
|
# Always close browser
|
|
79
79
|
browser.close()
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
# Parse with BeautifulSoup and convert to markdown
|
|
82
82
|
markdown_content = self._html_to_markdown(html_content, url)
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
# Build output in markdown format
|
|
85
85
|
output = [
|
|
86
86
|
f"# {title}",
|
|
87
87
|
f"Url: {url}",
|
|
88
88
|
markdown_content
|
|
89
89
|
]
|
|
90
|
-
|
|
90
|
+
|
|
91
91
|
return {
|
|
92
92
|
"success": True,
|
|
93
93
|
"stdout": "\n".join(output),
|
|
@@ -101,7 +101,7 @@ class WebpageTool:
|
|
|
101
101
|
"stdout": "",
|
|
102
102
|
"stderr": f"Failed to parse webpage: {str(e)}"
|
|
103
103
|
}
|
|
104
|
-
|
|
104
|
+
|
|
105
105
|
def _create_soup_element(self, content):
|
|
106
106
|
"""Safely create a BeautifulSoup element, ensuring it's treated as markup"""
|
|
107
107
|
if isinstance(content, str):
|
|
@@ -112,15 +112,15 @@ class WebpageTool:
|
|
|
112
112
|
# Return an empty list if the div is None
|
|
113
113
|
return []
|
|
114
114
|
return content
|
|
115
|
-
|
|
115
|
+
|
|
116
116
|
def _html_to_markdown(self, html_content: str, base_url: str) -> str:
|
|
117
117
|
"""Convert HTML to Markdown format preserving the content structure"""
|
|
118
118
|
soup = BeautifulSoup(html_content, 'html.parser')
|
|
119
|
-
|
|
119
|
+
|
|
120
120
|
# Remove unwanted elements
|
|
121
121
|
for element in soup(['script', 'style', 'meta', 'noscript', 'head']):
|
|
122
122
|
element.decompose()
|
|
123
|
-
|
|
123
|
+
|
|
124
124
|
# Process headings
|
|
125
125
|
for level in range(1, 7):
|
|
126
126
|
for heading in soup.find_all(f'h{level}'):
|
|
@@ -128,14 +128,14 @@ class WebpageTool:
|
|
|
128
128
|
heading_md = "\n\n" + "#" * level + " " + text + "\n\n"
|
|
129
129
|
new_element = self._create_soup_element(heading_md)
|
|
130
130
|
heading.replace_with(*new_element)
|
|
131
|
-
|
|
131
|
+
|
|
132
132
|
# Process paragraphs
|
|
133
133
|
for p in soup.find_all('p'):
|
|
134
134
|
text = p.get_text().strip()
|
|
135
135
|
if text:
|
|
136
136
|
new_element = self._create_soup_element("\n\n" + text + "\n\n")
|
|
137
137
|
p.replace_with(*new_element)
|
|
138
|
-
|
|
138
|
+
|
|
139
139
|
# Process unordered lists
|
|
140
140
|
for ul in soup.find_all('ul'):
|
|
141
141
|
items = []
|
|
@@ -143,7 +143,7 @@ class WebpageTool:
|
|
|
143
143
|
items.append("* " + li.get_text().strip())
|
|
144
144
|
new_element = self._create_soup_element("\n\n" + "\n".join(items) + "\n\n")
|
|
145
145
|
ul.replace_with(*new_element)
|
|
146
|
-
|
|
146
|
+
|
|
147
147
|
# Process ordered lists
|
|
148
148
|
for ol in soup.find_all('ol'):
|
|
149
149
|
items = []
|
|
@@ -151,7 +151,7 @@ class WebpageTool:
|
|
|
151
151
|
items.append(str(i) + ". " + li.get_text().strip())
|
|
152
152
|
new_element = self._create_soup_element("\n\n" + "\n".join(items) + "\n\n")
|
|
153
153
|
ol.replace_with(*new_element)
|
|
154
|
-
|
|
154
|
+
|
|
155
155
|
# Process links (first pass)
|
|
156
156
|
for a in soup.find_all('a', href=True):
|
|
157
157
|
try:
|
|
@@ -166,7 +166,7 @@ class WebpageTool:
|
|
|
166
166
|
a.replace_with(*new_element)
|
|
167
167
|
except (KeyError, AttributeError):
|
|
168
168
|
continue
|
|
169
|
-
|
|
169
|
+
|
|
170
170
|
# Process images
|
|
171
171
|
for img in soup.find_all('img', src=True):
|
|
172
172
|
try:
|
|
@@ -180,37 +180,37 @@ class WebpageTool:
|
|
|
180
180
|
img.replace_with(*new_element)
|
|
181
181
|
except (KeyError, AttributeError, UnboundLocalError):
|
|
182
182
|
continue
|
|
183
|
-
|
|
183
|
+
|
|
184
184
|
# Process code blocks
|
|
185
185
|
for pre in soup.find_all('pre'):
|
|
186
186
|
code = pre.get_text().strip()
|
|
187
187
|
pre_md = "\n\n```\n" + code + "\n```\n\n"
|
|
188
188
|
new_element = self._create_soup_element(pre_md)
|
|
189
189
|
pre.replace_with(*new_element)
|
|
190
|
-
|
|
190
|
+
|
|
191
191
|
# Process inline code
|
|
192
192
|
for code in soup.find_all('code'):
|
|
193
193
|
text = code.get_text().strip()
|
|
194
194
|
code_md = "`" + text + "`"
|
|
195
195
|
new_element = self._create_soup_element(code_md)
|
|
196
196
|
code.replace_with(*new_element)
|
|
197
|
-
|
|
197
|
+
|
|
198
198
|
# Process line breaks
|
|
199
199
|
for br in soup.find_all('br'):
|
|
200
200
|
new_element = self._create_soup_element('\n')
|
|
201
201
|
br.replace_with(*new_element)
|
|
202
|
-
|
|
202
|
+
|
|
203
203
|
# Get the full text
|
|
204
204
|
markdown_text = soup.get_text()
|
|
205
|
-
|
|
205
|
+
|
|
206
206
|
# Clean up extra whitespace and line breaks
|
|
207
207
|
markdown_text = re.sub(r'\n{3,}', '\n\n', markdown_text)
|
|
208
208
|
markdown_text = re.sub(r'\s{2,}', ' ', markdown_text)
|
|
209
|
-
|
|
209
|
+
|
|
210
210
|
# Process links again (for any that might have been missed)
|
|
211
211
|
link_pattern = r'\[([^\]]+)\]\(([^)]+)\)'
|
|
212
212
|
all_links = re.findall(link_pattern, markdown_text)
|
|
213
|
-
|
|
213
|
+
|
|
214
214
|
# Add a section with all links at the end
|
|
215
215
|
if all_links:
|
|
216
216
|
link_section = ["", "## Links", ""]
|
|
@@ -220,7 +220,7 @@ class WebpageTool:
|
|
|
220
220
|
if link_entry not in seen_links:
|
|
221
221
|
link_section.append(link_entry)
|
|
222
222
|
seen_links.add(link_entry)
|
|
223
|
-
|
|
223
|
+
|
|
224
224
|
markdown_text += "\n\n" + "\n".join(link_section)
|
|
225
|
-
|
|
226
|
-
return markdown_text.strip()
|
|
225
|
+
|
|
226
|
+
return markdown_text.strip()
|
jarvis/jarvis_tools/registry.py
CHANGED
|
@@ -85,21 +85,40 @@ class ToolRegistry(OutputHandler):
|
|
|
85
85
|
if self._extract_tool_calls(response):
|
|
86
86
|
return True
|
|
87
87
|
return False
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
def prompt(self) -> str:
|
|
90
90
|
"""加载工具"""
|
|
91
91
|
tools = self.get_all_tools()
|
|
92
92
|
if tools:
|
|
93
93
|
tools_prompt = "## 可用工具:\n"
|
|
94
94
|
for tool in tools:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
try:
|
|
96
|
+
tools_prompt += f"- 名称: {tool['name']}\n"
|
|
97
|
+
tools_prompt += f" 描述: {tool['description']}\n"
|
|
98
|
+
tools_prompt += " 参数: |\n"
|
|
99
|
+
|
|
100
|
+
# 生成格式化的YAML参数
|
|
101
|
+
yaml_params = yaml.dump(
|
|
102
|
+
tool['parameters'],
|
|
103
|
+
allow_unicode=True,
|
|
104
|
+
indent=4,
|
|
105
|
+
sort_keys=False,
|
|
106
|
+
width=120 # 增加行宽限制
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# 添加缩进并移除尾部空格
|
|
110
|
+
for line in yaml_params.split('\n'):
|
|
111
|
+
tools_prompt += f" {line.rstrip()}\n"
|
|
112
|
+
|
|
113
|
+
except yaml.YAMLError as e:
|
|
114
|
+
PrettyOutput.print(f"工具 {tool['name']} 参数序列化失败: {str(e)}", OutputType.ERROR)
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
tools_prompt += tool_call_help.rstrip() # 移除帮助文本尾部空格
|
|
99
118
|
return tools_prompt
|
|
100
119
|
return ""
|
|
101
|
-
|
|
102
|
-
def handle(self, response: str) -> Tuple[bool, Any]:
|
|
120
|
+
|
|
121
|
+
def handle(self, response: str, agent: Any) -> Tuple[bool, Any]:
|
|
103
122
|
tool_calls = self._extract_tool_calls(response)
|
|
104
123
|
if len(tool_calls) > 1:
|
|
105
124
|
PrettyOutput.print(f"操作失败:检测到多个操作。一次只能执行一个操作。尝试执行的操作:{', '.join([tool_call['name'] for tool_call in tool_calls])}", OutputType.WARNING)
|
|
@@ -107,7 +126,7 @@ class ToolRegistry(OutputHandler):
|
|
|
107
126
|
if len(tool_calls) == 0:
|
|
108
127
|
return False, ""
|
|
109
128
|
tool_call = tool_calls[0]
|
|
110
|
-
return False, self.handle_tool_calls(tool_call)
|
|
129
|
+
return False, self.handle_tool_calls(tool_call, agent)
|
|
111
130
|
|
|
112
131
|
def __init__(self):
|
|
113
132
|
"""初始化工具注册表"""
|
|
@@ -132,13 +151,13 @@ class ToolRegistry(OutputHandler):
|
|
|
132
151
|
def _load_builtin_tools(self):
|
|
133
152
|
"""从内置工具目录加载工具"""
|
|
134
153
|
tools_dir = Path(__file__).parent
|
|
135
|
-
|
|
154
|
+
|
|
136
155
|
# 遍历目录中的所有.py文件
|
|
137
156
|
for file_path in tools_dir.glob("*.py"):
|
|
138
157
|
# 跳过base.py和__init__.py
|
|
139
158
|
if file_path.name in ["base.py", "__init__.py", "registry.py"]:
|
|
140
159
|
continue
|
|
141
|
-
|
|
160
|
+
|
|
142
161
|
self.register_tool_by_file(str(file_path))
|
|
143
162
|
|
|
144
163
|
def _load_external_tools(self):
|
|
@@ -146,21 +165,21 @@ class ToolRegistry(OutputHandler):
|
|
|
146
165
|
external_tools_dir = Path.home() / '.jarvis/tools'
|
|
147
166
|
if not external_tools_dir.exists():
|
|
148
167
|
return
|
|
149
|
-
|
|
168
|
+
|
|
150
169
|
# 遍历目录中的所有.py文件
|
|
151
170
|
for file_path in external_tools_dir.glob("*.py"):
|
|
152
171
|
# 跳过__init__.py
|
|
153
172
|
if file_path.name == "__init__.py":
|
|
154
173
|
continue
|
|
155
|
-
|
|
174
|
+
|
|
156
175
|
self.register_tool_by_file(str(file_path))
|
|
157
176
|
|
|
158
177
|
def register_tool_by_file(self, file_path: str):
|
|
159
178
|
"""从指定文件加载并注册工具
|
|
160
|
-
|
|
179
|
+
|
|
161
180
|
参数:
|
|
162
181
|
file_path: 工具文件的路径
|
|
163
|
-
|
|
182
|
+
|
|
164
183
|
返回:
|
|
165
184
|
bool: 工具是否加载成功
|
|
166
185
|
"""
|
|
@@ -169,35 +188,35 @@ class ToolRegistry(OutputHandler):
|
|
|
169
188
|
if not p_file_path.exists() or not p_file_path.is_file():
|
|
170
189
|
PrettyOutput.print(f"文件不存在: {p_file_path}", OutputType.ERROR)
|
|
171
190
|
return False
|
|
172
|
-
|
|
191
|
+
|
|
173
192
|
# 临时将父目录添加到sys.path
|
|
174
193
|
parent_dir = str(p_file_path.parent)
|
|
175
194
|
sys.path.insert(0, parent_dir)
|
|
176
|
-
|
|
195
|
+
|
|
177
196
|
try:
|
|
178
197
|
# 使用标准导入机制导入模块
|
|
179
198
|
module_name = p_file_path.stem
|
|
180
199
|
module = __import__(module_name)
|
|
181
|
-
|
|
200
|
+
|
|
182
201
|
# 在模块中查找工具类
|
|
183
202
|
tool_found = False
|
|
184
203
|
for item_name in dir(module):
|
|
185
204
|
item = getattr(module, item_name)
|
|
186
205
|
# 检查是否是类并具有必要属性
|
|
187
|
-
if (isinstance(item, type) and
|
|
188
|
-
hasattr(item, 'name') and
|
|
189
|
-
hasattr(item, 'description') and
|
|
206
|
+
if (isinstance(item, type) and
|
|
207
|
+
hasattr(item, 'name') and
|
|
208
|
+
hasattr(item, 'description') and
|
|
190
209
|
hasattr(item, 'parameters') and
|
|
191
|
-
hasattr(item, 'execute') and
|
|
210
|
+
hasattr(item, 'execute') and
|
|
192
211
|
item.name == module_name):
|
|
193
212
|
|
|
194
213
|
if hasattr(item, "check"):
|
|
195
214
|
if not item.check():
|
|
196
215
|
continue
|
|
197
|
-
|
|
216
|
+
|
|
198
217
|
# 实例化工具类
|
|
199
218
|
tool_instance = item()
|
|
200
|
-
|
|
219
|
+
|
|
201
220
|
# 注册工具
|
|
202
221
|
self.register_tool(
|
|
203
222
|
name=tool_instance.name,
|
|
@@ -207,29 +226,29 @@ class ToolRegistry(OutputHandler):
|
|
|
207
226
|
)
|
|
208
227
|
tool_found = True
|
|
209
228
|
break
|
|
210
|
-
|
|
229
|
+
|
|
211
230
|
if not tool_found:
|
|
212
231
|
return False
|
|
213
|
-
|
|
232
|
+
|
|
214
233
|
return True
|
|
215
|
-
|
|
234
|
+
|
|
216
235
|
finally:
|
|
217
236
|
# 从sys.path中移除目录
|
|
218
237
|
sys.path.remove(parent_dir)
|
|
219
|
-
|
|
238
|
+
|
|
220
239
|
except Exception as e:
|
|
221
240
|
PrettyOutput.print(f"从 {Path(file_path).name} 加载工具失败: {str(e)}", OutputType.ERROR)
|
|
222
241
|
return False
|
|
223
242
|
@staticmethod
|
|
224
243
|
def _extract_tool_calls(content: str) -> List[Dict]:
|
|
225
244
|
"""从内容中提取工具调用。
|
|
226
|
-
|
|
245
|
+
|
|
227
246
|
参数:
|
|
228
247
|
content: 包含工具调用的内容
|
|
229
|
-
|
|
248
|
+
|
|
230
249
|
返回:
|
|
231
250
|
List[Dict]: 包含名称和参数的提取工具调用列表
|
|
232
|
-
|
|
251
|
+
|
|
233
252
|
异常:
|
|
234
253
|
Exception: 如果工具调用缺少必要字段
|
|
235
254
|
"""
|
|
@@ -264,12 +283,13 @@ class ToolRegistry(OutputHandler):
|
|
|
264
283
|
return {"success": False, "stderr": f"工具 {name} 不存在,可用的工具有: {', '.join(self.tools.keys())}", "stdout": ""}
|
|
265
284
|
return tool.execute(arguments)
|
|
266
285
|
|
|
267
|
-
def handle_tool_calls(self, tool_call: Dict) -> str:
|
|
286
|
+
def handle_tool_calls(self, tool_call: Dict, agent: Any) -> str:
|
|
268
287
|
"""处理工具调用,只处理第一个工具"""
|
|
269
288
|
try:
|
|
270
289
|
# 只处理第一个工具调用
|
|
271
290
|
name = tool_call["name"]
|
|
272
291
|
args = tool_call["arguments"]
|
|
292
|
+
args["agent"] = agent
|
|
273
293
|
|
|
274
294
|
tool_call_help = f"""
|
|
275
295
|
# 🛠️ 工具使用系统
|
|
@@ -330,14 +350,14 @@ arguments:
|
|
|
330
350
|
- 创建虚构对话
|
|
331
351
|
- 在没有所需信息的情况下继续
|
|
332
352
|
"""
|
|
333
|
-
|
|
353
|
+
|
|
334
354
|
if isinstance(args, str):
|
|
335
355
|
try:
|
|
336
356
|
args = json.loads(args)
|
|
337
357
|
except json.JSONDecodeError:
|
|
338
358
|
PrettyOutput.print(f"工具参数格式无效: {name} {tool_call_help}", OutputType.ERROR)
|
|
339
359
|
return ""
|
|
340
|
-
|
|
360
|
+
|
|
341
361
|
# Execute tool call
|
|
342
362
|
result = self.execute_tool(name, args)
|
|
343
363
|
|
|
@@ -350,14 +370,14 @@ arguments:
|
|
|
350
370
|
output_parts.append(f"错误:\n{stderr}")
|
|
351
371
|
output = "\n\n".join(output_parts)
|
|
352
372
|
output = "无输出和错误" if not output else output
|
|
353
|
-
|
|
373
|
+
|
|
354
374
|
# Process the result
|
|
355
375
|
if result["success"]:
|
|
356
376
|
# If the output exceeds 4k characters, use a large model to summarize
|
|
357
377
|
if get_context_token_count(output) > self.max_token_count:
|
|
358
378
|
PrettyOutput.section("输出过长,正在总结...", OutputType.SYSTEM)
|
|
359
379
|
try:
|
|
360
|
-
|
|
380
|
+
|
|
361
381
|
model = PlatformRegistry.get_global_platform_registry().get_normal_platform()
|
|
362
382
|
model.set_suppress_output(False)
|
|
363
383
|
# If the output exceeds the maximum context length, only take the last part
|
|
@@ -391,7 +411,7 @@ arguments:
|
|
|
391
411
|
PrettyOutput.print(f"总结失败: {str(e)}", OutputType.ERROR)
|
|
392
412
|
output = f"输出过长 ({len(output)} 字符),建议查看原始输出。\n前300字符预览:\n{output[:300]}..."
|
|
393
413
|
return output
|
|
394
|
-
|
|
414
|
+
|
|
395
415
|
except Exception as e:
|
|
396
416
|
PrettyOutput.print(f"工具执行失败:{str(e)}", OutputType.ERROR)
|
|
397
417
|
return f"工具调用失败: {str(e)}"
|
|
@@ -419,13 +439,13 @@ def main():
|
|
|
419
439
|
call_parser.add_argument('--args-file', type=str, help='从文件加载工具参数 (JSON格式)')
|
|
420
440
|
|
|
421
441
|
args = parser.parse_args()
|
|
422
|
-
|
|
442
|
+
|
|
423
443
|
# 初始化工具注册表
|
|
424
444
|
registry = ToolRegistry()
|
|
425
|
-
|
|
445
|
+
|
|
426
446
|
if args.command == 'list':
|
|
427
447
|
tools = registry.get_all_tools()
|
|
428
|
-
|
|
448
|
+
|
|
429
449
|
if args.json:
|
|
430
450
|
if args.detailed:
|
|
431
451
|
print(json.dumps(tools, indent=2, ensure_ascii=False))
|
|
@@ -445,17 +465,17 @@ def main():
|
|
|
445
465
|
req_mark = "*" if param_name in required else ""
|
|
446
466
|
desc = param_info.get('description', '无描述')
|
|
447
467
|
print(f" - {param_name}{req_mark}: {desc}")
|
|
448
|
-
|
|
468
|
+
|
|
449
469
|
elif args.command == 'call':
|
|
450
470
|
tool_name = args.tool_name
|
|
451
471
|
tool = registry.get_tool(tool_name)
|
|
452
|
-
|
|
472
|
+
|
|
453
473
|
if not tool:
|
|
454
474
|
PrettyOutput.print(f"错误: 工具 '{tool_name}' 不存在", OutputType.ERROR)
|
|
455
475
|
available_tools = ", ".join([t["name"] for t in registry.get_all_tools()])
|
|
456
476
|
print(f"可用工具: {available_tools}")
|
|
457
477
|
return 1
|
|
458
|
-
|
|
478
|
+
|
|
459
479
|
# 获取参数
|
|
460
480
|
tool_args = {}
|
|
461
481
|
if args.args:
|
|
@@ -464,7 +484,7 @@ def main():
|
|
|
464
484
|
except json.JSONDecodeError:
|
|
465
485
|
PrettyOutput.print("错误: 参数必须是有效的JSON格式", OutputType.ERROR)
|
|
466
486
|
return 1
|
|
467
|
-
|
|
487
|
+
|
|
468
488
|
elif args.args_file:
|
|
469
489
|
try:
|
|
470
490
|
with open(args.args_file, 'r', encoding='utf-8') as f:
|
|
@@ -472,11 +492,11 @@ def main():
|
|
|
472
492
|
except (json.JSONDecodeError, FileNotFoundError) as e:
|
|
473
493
|
PrettyOutput.print(f"错误: 无法从文件加载参数: {str(e)}", OutputType.ERROR)
|
|
474
494
|
return 1
|
|
475
|
-
|
|
495
|
+
|
|
476
496
|
# 检查必需参数
|
|
477
497
|
required_params = tool.parameters.get('required', [])
|
|
478
498
|
missing_params = [p for p in required_params if p not in tool_args]
|
|
479
|
-
|
|
499
|
+
|
|
480
500
|
if missing_params:
|
|
481
501
|
PrettyOutput.print(f"错误: 缺少必需参数: {', '.join(missing_params)}", OutputType.ERROR)
|
|
482
502
|
print("\n参数说明:")
|
|
@@ -486,30 +506,30 @@ def main():
|
|
|
486
506
|
desc = param_info.get('description', '无描述')
|
|
487
507
|
print(f" - {param_name}: {desc}")
|
|
488
508
|
return 1
|
|
489
|
-
|
|
509
|
+
|
|
490
510
|
# 执行工具
|
|
491
511
|
with yaspin(text=f"正在执行工具 {tool_name}...").dots12:
|
|
492
512
|
result = registry.execute_tool(tool_name, tool_args)
|
|
493
|
-
|
|
513
|
+
|
|
494
514
|
# 显示结果
|
|
495
515
|
if result["success"]:
|
|
496
516
|
PrettyOutput.section(f"工具 {tool_name} 执行成功", OutputType.SUCCESS)
|
|
497
517
|
else:
|
|
498
518
|
PrettyOutput.section(f"工具 {tool_name} 执行失败", OutputType.ERROR)
|
|
499
|
-
|
|
519
|
+
|
|
500
520
|
if result.get("stdout"):
|
|
501
521
|
print("\n输出:")
|
|
502
522
|
print(result["stdout"])
|
|
503
|
-
|
|
523
|
+
|
|
504
524
|
if result.get("stderr"):
|
|
505
525
|
PrettyOutput.print("\n错误:", OutputType.ERROR)
|
|
506
526
|
print(result["stderr"])
|
|
507
|
-
|
|
527
|
+
|
|
508
528
|
return 0 if result["success"] else 1
|
|
509
|
-
|
|
529
|
+
|
|
510
530
|
else:
|
|
511
531
|
parser.print_help()
|
|
512
|
-
|
|
532
|
+
|
|
513
533
|
return 0
|
|
514
534
|
|
|
515
535
|
|
|
@@ -11,7 +11,7 @@ from jarvis.jarvis_utils.utils import ct, ot
|
|
|
11
11
|
|
|
12
12
|
class ToolGenerator:
|
|
13
13
|
"""工具生成器类,用于自动创建与Jarvis系统集成的新工具"""
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
name = "tool_generator"
|
|
16
16
|
description = "使用LLM自动生成与系统集成的新工具"
|
|
17
17
|
parameters = {
|
|
@@ -22,7 +22,7 @@ class ToolGenerator:
|
|
|
22
22
|
"description": "新工具的名称"
|
|
23
23
|
},
|
|
24
24
|
"description": {
|
|
25
|
-
"type": "string",
|
|
25
|
+
"type": "string",
|
|
26
26
|
"description": "工具用途描述"
|
|
27
27
|
},
|
|
28
28
|
"input_spec": {
|
|
@@ -32,7 +32,7 @@ class ToolGenerator:
|
|
|
32
32
|
},
|
|
33
33
|
"required": ["tool_name", "description", "input_spec"]
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
def execute(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
37
37
|
"""
|
|
38
38
|
执行工具生成过程
|
|
@@ -43,19 +43,19 @@ class ToolGenerator:
|
|
|
43
43
|
"""
|
|
44
44
|
# 获取代码生成平台实例
|
|
45
45
|
model = PlatformRegistry.get_global_platform_registry().get_normal_platform()
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
try:
|
|
48
48
|
tool_name = arguments["tool_name"]
|
|
49
49
|
description = arguments["description"]
|
|
50
50
|
input_spec = arguments["input_spec"]
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
# 使用LLM生成工具实现代码
|
|
53
53
|
with yaspin(text="正在生成工具...", color="cyan") as spinner:
|
|
54
54
|
prompt = self._create_prompt(tool_name, description, input_spec)
|
|
55
55
|
llm_response = model.chat_until_success(prompt)
|
|
56
56
|
spinner.text = "工具生成完成"
|
|
57
57
|
spinner.ok("✅")
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
# 从LLM响应中提取实现代码
|
|
60
60
|
with yaspin(text="正在提取工具实现...", color="cyan") as spinner:
|
|
61
61
|
implementation = self._extract_code(llm_response)
|
|
@@ -67,7 +67,7 @@ class ToolGenerator:
|
|
|
67
67
|
}
|
|
68
68
|
spinner.text = "工具实现提取完成"
|
|
69
69
|
spinner.ok("✅")
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
# 验证生成的工具代码是否符合返回值格式要求
|
|
72
72
|
with yaspin(text="正在验证工具返回值格式...", color="cyan") as spinner:
|
|
73
73
|
if not self._validate_return_value_format(implementation):
|
|
@@ -78,31 +78,31 @@ class ToolGenerator:
|
|
|
78
78
|
}
|
|
79
79
|
spinner.text = "工具返回值格式验证完成"
|
|
80
80
|
spinner.ok("✅")
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
# 保存生成的新工具
|
|
83
83
|
with yaspin(text="正在保存工具...", color="cyan") as spinner:
|
|
84
84
|
tools_dir = Path.home() / ".jarvis" / "tools"
|
|
85
85
|
tools_dir.mkdir(parents=True, exist_ok=True)
|
|
86
86
|
tool_file = tools_dir / f"{tool_name}.py"
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
with open(tool_file, "w", errors="ignore") as f:
|
|
89
89
|
f.write(implementation)
|
|
90
90
|
spinner.text = "工具保存完成"
|
|
91
91
|
spinner.ok("✅")
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
return {
|
|
94
94
|
"success": True,
|
|
95
95
|
"stdout": f"工具成功生成于: {tool_file}",
|
|
96
96
|
"stderr": ""
|
|
97
97
|
}
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
except Exception as e:
|
|
100
100
|
return {
|
|
101
101
|
"success": False,
|
|
102
102
|
"stdout": "",
|
|
103
103
|
"stderr": f"工具生成失败: {str(e)}"
|
|
104
104
|
}
|
|
105
|
-
|
|
105
|
+
|
|
106
106
|
def _create_prompt(self, tool_name: str, description: str, input_spec: str) -> str:
|
|
107
107
|
"""
|
|
108
108
|
创建用于工具生成的LLM提示
|
|
@@ -134,10 +134,10 @@ class CustomTool:
|
|
|
134
134
|
|
|
135
135
|
def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
136
136
|
"""执行工具功能
|
|
137
|
-
|
|
137
|
+
|
|
138
138
|
Args:
|
|
139
139
|
args: 传递给工具的参数
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
Returns:
|
|
142
142
|
{
|
|
143
143
|
"success": bool,
|
|
@@ -148,7 +148,7 @@ class CustomTool:
|
|
|
148
148
|
try:
|
|
149
149
|
# 在此实现工具逻辑
|
|
150
150
|
# 使用LLM
|
|
151
|
-
# model = PlatformRegistry.get_global_platform_registry().get_normal_platform()
|
|
151
|
+
# model = PlatformRegistry.get_global_platform_registry().get_normal_platform()
|
|
152
152
|
# result = model.chat_until_success(prompt)
|
|
153
153
|
|
|
154
154
|
result = "工具执行结果"
|
|
@@ -189,7 +189,7 @@ class CustomTool:
|
|
|
189
189
|
示例:
|
|
190
190
|
{example_code}
|
|
191
191
|
'''
|
|
192
|
-
|
|
192
|
+
|
|
193
193
|
def _extract_code(self, response: str) -> str:
|
|
194
194
|
"""
|
|
195
195
|
从LLM响应中提取Python代码
|
|
@@ -202,7 +202,7 @@ class CustomTool:
|
|
|
202
202
|
if sm:
|
|
203
203
|
return sm.group(1)
|
|
204
204
|
return ""
|
|
205
|
-
|
|
205
|
+
|
|
206
206
|
def _validate_return_value_format(self, code: str) -> bool:
|
|
207
207
|
"""
|
|
208
208
|
验证execute方法的返回值格式是否正确
|
|
@@ -216,6 +216,6 @@ class CustomTool:
|
|
|
216
216
|
if "def execute(self, args: Dict) -> Dict:" not in code and \
|
|
217
217
|
"def execute(self, args: Dict) -> Dict[str, Any]:" not in code:
|
|
218
218
|
return False
|
|
219
|
-
|
|
219
|
+
|
|
220
220
|
# 检查返回值中是否包含所有必需字段
|
|
221
221
|
return all(field in code for field in required_fields)
|