jarvis-ai-assistant 0.1.150__py3-none-any.whl → 0.1.152__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 +6 -2
- jarvis/jarvis_git_utils/git_commiter.py +7 -0
- jarvis/jarvis_mcp/__init__.py +29 -0
- jarvis/jarvis_mcp/sse_mcp_client.py +590 -0
- jarvis/jarvis_mcp/{local_mcp_client.py → stdio_mcp_client.py} +86 -1
- jarvis/jarvis_tools/ask_codebase.py +3 -10
- jarvis/jarvis_tools/registry.py +252 -147
- jarvis/jarvis_utils/config.py +1 -1
- jarvis/jarvis_utils/methodology.py +3 -3
- {jarvis_ai_assistant-0.1.150.dist-info → jarvis_ai_assistant-0.1.152.dist-info}/METADATA +32 -3
- {jarvis_ai_assistant-0.1.150.dist-info → jarvis_ai_assistant-0.1.152.dist-info}/RECORD +16 -16
- {jarvis_ai_assistant-0.1.150.dist-info → jarvis_ai_assistant-0.1.152.dist-info}/WHEEL +1 -1
- jarvis/jarvis_mcp/remote_mcp_client.py +0 -230
- {jarvis_ai_assistant-0.1.150.dist-info → jarvis_ai_assistant-0.1.152.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.150.dist-info → jarvis_ai_assistant-0.1.152.dist-info/licenses}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.150.dist-info → jarvis_ai_assistant-0.1.152.dist-info}/top_level.txt +0 -0
jarvis/jarvis_tools/registry.py
CHANGED
|
@@ -10,13 +10,17 @@ import yaml
|
|
|
10
10
|
from jarvis.jarvis_agent.output_handler import OutputHandler
|
|
11
11
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
12
12
|
from jarvis.jarvis_tools.base import Tool
|
|
13
|
-
from jarvis.jarvis_utils.config import
|
|
13
|
+
from jarvis.jarvis_utils.config import (
|
|
14
|
+
INPUT_WINDOW_REVERSE_SIZE,
|
|
15
|
+
get_max_input_token_count,
|
|
16
|
+
get_data_dir,
|
|
17
|
+
)
|
|
14
18
|
from jarvis.jarvis_utils.embedding import get_context_token_count
|
|
15
19
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
16
20
|
from jarvis.jarvis_utils.utils import ct, ot, init_env
|
|
17
|
-
from jarvis.jarvis_mcp.
|
|
18
|
-
from jarvis.jarvis_mcp.
|
|
19
|
-
|
|
21
|
+
from jarvis.jarvis_mcp.stdio_mcp_client import StdioMcpClient
|
|
22
|
+
from jarvis.jarvis_mcp.sse_mcp_client import SSEMcpClient
|
|
23
|
+
from jarvis.jarvis_mcp import McpClient
|
|
20
24
|
|
|
21
25
|
|
|
22
26
|
tool_call_help = f"""
|
|
@@ -81,6 +85,7 @@ arguments:
|
|
|
81
85
|
- 在没有所需信息的情况下继续
|
|
82
86
|
"""
|
|
83
87
|
|
|
88
|
+
|
|
84
89
|
class ToolRegistry(OutputHandler):
|
|
85
90
|
|
|
86
91
|
def name(self) -> str:
|
|
@@ -102,19 +107,22 @@ class ToolRegistry(OutputHandler):
|
|
|
102
107
|
|
|
103
108
|
# 生成格式化的YAML参数
|
|
104
109
|
yaml_params = yaml.dump(
|
|
105
|
-
tool[
|
|
110
|
+
tool["parameters"],
|
|
106
111
|
allow_unicode=True,
|
|
107
112
|
indent=4,
|
|
108
113
|
sort_keys=False,
|
|
109
|
-
width=120 # 增加行宽限制
|
|
114
|
+
width=120, # 增加行宽限制
|
|
110
115
|
)
|
|
111
116
|
|
|
112
117
|
# 添加缩进并移除尾部空格
|
|
113
|
-
for line in yaml_params.split(
|
|
118
|
+
for line in yaml_params.split("\n"):
|
|
114
119
|
tools_prompt += f" {line.rstrip()}\n"
|
|
115
120
|
|
|
116
121
|
except yaml.YAMLError as e:
|
|
117
|
-
PrettyOutput.print(
|
|
122
|
+
PrettyOutput.print(
|
|
123
|
+
f"工具 {tool['name']} 参数序列化失败: {str(e)}",
|
|
124
|
+
OutputType.ERROR,
|
|
125
|
+
)
|
|
118
126
|
continue
|
|
119
127
|
|
|
120
128
|
tools_prompt += tool_call_help.rstrip() # 移除帮助文本尾部空格
|
|
@@ -134,30 +142,37 @@ class ToolRegistry(OutputHandler):
|
|
|
134
142
|
self._load_builtin_tools()
|
|
135
143
|
self._load_external_tools()
|
|
136
144
|
self._load_mcp_tools()
|
|
137
|
-
self.max_input_token_count =
|
|
145
|
+
self.max_input_token_count = (
|
|
146
|
+
get_max_input_token_count() - INPUT_WINDOW_REVERSE_SIZE
|
|
147
|
+
)
|
|
138
148
|
|
|
139
149
|
def use_tools(self, name: List[str]) -> None:
|
|
140
150
|
"""使用指定工具
|
|
141
|
-
|
|
151
|
+
|
|
142
152
|
参数:
|
|
143
153
|
name: 要使用的工具名称列表
|
|
144
154
|
"""
|
|
145
155
|
missing_tools = [tool_name for tool_name in name if tool_name not in self.tools]
|
|
146
156
|
if missing_tools:
|
|
147
|
-
PrettyOutput.print(
|
|
157
|
+
PrettyOutput.print(
|
|
158
|
+
f"工具 {missing_tools} 不存在,可用的工具有: {', '.join(self.tools.keys())}",
|
|
159
|
+
OutputType.WARNING,
|
|
160
|
+
)
|
|
148
161
|
self.tools = {tool_name: self.tools[tool_name] for tool_name in name}
|
|
149
162
|
|
|
150
163
|
def dont_use_tools(self, names: List[str]) -> None:
|
|
151
164
|
"""从注册表中移除指定工具
|
|
152
|
-
|
|
165
|
+
|
|
153
166
|
参数:
|
|
154
167
|
names: 要移除的工具名称列表
|
|
155
168
|
"""
|
|
156
|
-
self.tools = {
|
|
169
|
+
self.tools = {
|
|
170
|
+
name: tool for name, tool in self.tools.items() if name not in names
|
|
171
|
+
}
|
|
157
172
|
|
|
158
173
|
def _load_mcp_tools(self) -> None:
|
|
159
174
|
"""从jarvis_data/tools/mcp加载工具"""
|
|
160
|
-
mcp_tools_dir = Path(get_data_dir()) /
|
|
175
|
+
mcp_tools_dir = Path(get_data_dir()) / "mcp"
|
|
161
176
|
if not mcp_tools_dir.exists():
|
|
162
177
|
return
|
|
163
178
|
|
|
@@ -179,7 +194,7 @@ class ToolRegistry(OutputHandler):
|
|
|
179
194
|
|
|
180
195
|
def _load_external_tools(self) -> None:
|
|
181
196
|
"""从jarvis_data/tools加载外部工具"""
|
|
182
|
-
external_tools_dir = Path(get_data_dir()) /
|
|
197
|
+
external_tools_dir = Path(get_data_dir()) / "tools"
|
|
183
198
|
if not external_tools_dir.exists():
|
|
184
199
|
return
|
|
185
200
|
|
|
@@ -193,7 +208,7 @@ class ToolRegistry(OutputHandler):
|
|
|
193
208
|
|
|
194
209
|
def register_mcp_tool_by_file(self, file_path: str) -> bool:
|
|
195
210
|
"""从指定文件加载并注册工具
|
|
196
|
-
|
|
211
|
+
|
|
197
212
|
参数:
|
|
198
213
|
file_path: 工具文件的路径
|
|
199
214
|
|
|
@@ -201,88 +216,145 @@ class ToolRegistry(OutputHandler):
|
|
|
201
216
|
bool: 工具是否加载成功
|
|
202
217
|
"""
|
|
203
218
|
try:
|
|
204
|
-
config = yaml.safe_load(open(file_path,
|
|
205
|
-
if
|
|
219
|
+
config = yaml.safe_load(open(file_path, "r", encoding="utf-8"))
|
|
220
|
+
if "type" not in config:
|
|
206
221
|
PrettyOutput.print(f"文件 {file_path} 缺少type字段", OutputType.WARNING)
|
|
207
222
|
return False
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
223
|
+
|
|
224
|
+
# 检查enable标志
|
|
225
|
+
if not config.get("enable", True):
|
|
226
|
+
PrettyOutput.print(
|
|
227
|
+
f"文件 {file_path} 已禁用(enable=false),跳过注册", OutputType.INFO
|
|
228
|
+
)
|
|
229
|
+
return False
|
|
230
|
+
|
|
231
|
+
name = config.get("name", Path(file_path).stem)
|
|
232
|
+
|
|
233
|
+
# 注册资源工具
|
|
234
|
+
def create_resource_list_func(client: McpClient):
|
|
235
|
+
def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
236
|
+
args = arguments.copy()
|
|
237
|
+
args.pop("agent", None)
|
|
238
|
+
args.pop("want", None)
|
|
239
|
+
ret = client.get_resource_list()
|
|
240
|
+
PrettyOutput.print(
|
|
241
|
+
f"MCP {name} 资源列表:\n{yaml.safe_dump(ret)}", OutputType.TOOL
|
|
242
|
+
)
|
|
243
|
+
return {
|
|
244
|
+
"success": True,
|
|
245
|
+
"stdout": yaml.safe_dump(ret),
|
|
246
|
+
"stderr": "",
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return execute
|
|
250
|
+
|
|
251
|
+
def create_resource_get_func(client: McpClient):
|
|
252
|
+
def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
253
|
+
args = arguments.copy()
|
|
254
|
+
args.pop("agent", None)
|
|
255
|
+
args.pop("want", None)
|
|
256
|
+
if "uri" not in args:
|
|
257
|
+
return {
|
|
258
|
+
"success": False,
|
|
259
|
+
"stdout": "",
|
|
260
|
+
"stderr": "缺少必需的uri参数",
|
|
261
|
+
}
|
|
262
|
+
ret = client.get_resource(args["uri"])
|
|
263
|
+
PrettyOutput.print(
|
|
264
|
+
f"MCP {name} 获取资源:\n{yaml.safe_dump(ret)}", OutputType.TOOL
|
|
265
|
+
)
|
|
266
|
+
return ret
|
|
267
|
+
|
|
268
|
+
return execute
|
|
269
|
+
|
|
270
|
+
def create_mcp_execute_func(tool_name: str, client: McpClient):
|
|
271
|
+
def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
272
|
+
args = arguments.copy()
|
|
273
|
+
args.pop("agent", None)
|
|
274
|
+
args.pop("want", None)
|
|
275
|
+
ret = client.execute(tool_name, args)
|
|
276
|
+
PrettyOutput.print(
|
|
277
|
+
f"MCP {name} {tool_name} 执行结果:\n{yaml.safe_dump(ret)}",
|
|
278
|
+
OutputType.TOOL,
|
|
279
|
+
)
|
|
280
|
+
return ret
|
|
281
|
+
|
|
282
|
+
return execute
|
|
283
|
+
|
|
284
|
+
if config["type"] == "stdio":
|
|
285
|
+
if "command" not in config:
|
|
286
|
+
PrettyOutput.print(
|
|
287
|
+
f"文件 {file_path} 缺少command字段", OutputType.WARNING
|
|
240
288
|
)
|
|
241
|
-
|
|
242
|
-
return True
|
|
243
|
-
|
|
244
|
-
elif config['type'] == 'remote':
|
|
245
|
-
if 'base_url' not in config:
|
|
246
|
-
PrettyOutput.print(f"文件 {file_path} 缺少base_url字段", OutputType.WARNING)
|
|
247
|
-
return False
|
|
248
|
-
|
|
249
|
-
# 创建远程MCP客户端
|
|
250
|
-
mcp_client = RemoteMcpClient(config)
|
|
251
|
-
|
|
252
|
-
# 获取工具信息
|
|
253
|
-
tools = mcp_client.get_tool_list()
|
|
254
|
-
if not tools:
|
|
255
|
-
PrettyOutput.print(f"从 {file_path} 获取工具列表失败", OutputType.WARNING)
|
|
256
289
|
return False
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
262
|
-
args = arguments.copy()
|
|
263
|
-
args.pop('agent', None)
|
|
264
|
-
args.pop('want', None)
|
|
265
|
-
ret = client.execute(tool_name, args)
|
|
266
|
-
PrettyOutput.print(f"MCP {tool_name} 执行结果:\n{yaml.safe_dump(ret)}", OutputType.TOOL)
|
|
267
|
-
return ret
|
|
268
|
-
return execute
|
|
269
|
-
|
|
270
|
-
# 注册工具
|
|
271
|
-
self.register_tool(
|
|
272
|
-
name=tool['name'],
|
|
273
|
-
description=tool['description'],
|
|
274
|
-
parameters=tool['parameters'],
|
|
275
|
-
func=create_remote_execute_func(tool['name'], mcp_client)
|
|
290
|
+
elif config["type"] == "sse":
|
|
291
|
+
if "base_url" not in config:
|
|
292
|
+
PrettyOutput.print(
|
|
293
|
+
f"文件 {file_path} 缺少base_url字段", OutputType.WARNING
|
|
276
294
|
)
|
|
277
|
-
|
|
278
|
-
return True
|
|
295
|
+
return False
|
|
279
296
|
else:
|
|
280
|
-
PrettyOutput.print(
|
|
297
|
+
PrettyOutput.print(
|
|
298
|
+
f"文件 {file_path} 类型错误: {config['type']}", OutputType.WARNING
|
|
299
|
+
)
|
|
300
|
+
return False
|
|
301
|
+
|
|
302
|
+
# 创建MCP客户端
|
|
303
|
+
mcp_client: McpClient = (
|
|
304
|
+
StdioMcpClient(config)
|
|
305
|
+
if config["type"] == "stdio"
|
|
306
|
+
else SSEMcpClient(config)
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# 获取工具信息
|
|
310
|
+
tools = mcp_client.get_tool_list()
|
|
311
|
+
if not tools:
|
|
312
|
+
PrettyOutput.print(
|
|
313
|
+
f"从 {file_path} 获取工具列表失败", OutputType.WARNING
|
|
314
|
+
)
|
|
281
315
|
return False
|
|
316
|
+
|
|
317
|
+
# 注册每个工具
|
|
318
|
+
for tool in tools:
|
|
319
|
+
|
|
320
|
+
# 注册工具
|
|
321
|
+
self.register_tool(
|
|
322
|
+
name=f"{name}.tool_call.{tool['name']}",
|
|
323
|
+
description=tool["description"],
|
|
324
|
+
parameters=tool["parameters"],
|
|
325
|
+
func=create_mcp_execute_func(tool["name"], mcp_client),
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# 注册资源列表工具
|
|
329
|
+
self.register_tool(
|
|
330
|
+
name=f"{name}.resource.get_resource_list",
|
|
331
|
+
description=f"获取{name}MCP服务器上的资源列表",
|
|
332
|
+
parameters={"type": "object", "properties": {}, "required": []},
|
|
333
|
+
func=create_resource_list_func(mcp_client),
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# 注册获取资源工具
|
|
337
|
+
self.register_tool(
|
|
338
|
+
name=f"{name}.resource.get_resource",
|
|
339
|
+
description=f"获取{name}MCP服务器上的指定资源",
|
|
340
|
+
parameters={
|
|
341
|
+
"type": "object",
|
|
342
|
+
"properties": {
|
|
343
|
+
"uri": {"type": "string", "description": "资源的URI标识符"}
|
|
344
|
+
},
|
|
345
|
+
"required": ["uri"],
|
|
346
|
+
},
|
|
347
|
+
func=create_resource_get_func(mcp_client),
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
return True
|
|
351
|
+
|
|
282
352
|
except Exception as e:
|
|
283
|
-
PrettyOutput.print(
|
|
353
|
+
PrettyOutput.print(
|
|
354
|
+
f"文件 {file_path} 加载失败: {str(e)}", OutputType.WARNING
|
|
355
|
+
)
|
|
284
356
|
return False
|
|
285
|
-
|
|
357
|
+
|
|
286
358
|
def register_tool_by_file(self, file_path: str) -> bool:
|
|
287
359
|
"""从指定文件加载并注册工具
|
|
288
360
|
|
|
@@ -312,12 +384,14 @@ class ToolRegistry(OutputHandler):
|
|
|
312
384
|
for item_name in dir(module):
|
|
313
385
|
item = getattr(module, item_name)
|
|
314
386
|
# 检查是否是类并具有必要属性
|
|
315
|
-
if (
|
|
316
|
-
|
|
317
|
-
hasattr(item,
|
|
318
|
-
hasattr(item,
|
|
319
|
-
hasattr(item,
|
|
320
|
-
item
|
|
387
|
+
if (
|
|
388
|
+
isinstance(item, type)
|
|
389
|
+
and hasattr(item, "name")
|
|
390
|
+
and hasattr(item, "description")
|
|
391
|
+
and hasattr(item, "parameters")
|
|
392
|
+
and hasattr(item, "execute")
|
|
393
|
+
and item.name == module_name
|
|
394
|
+
):
|
|
321
395
|
|
|
322
396
|
if hasattr(item, "check"):
|
|
323
397
|
if not item.check():
|
|
@@ -331,7 +405,7 @@ class ToolRegistry(OutputHandler):
|
|
|
331
405
|
name=tool_instance.name,
|
|
332
406
|
description=tool_instance.description,
|
|
333
407
|
parameters=tool_instance.parameters,
|
|
334
|
-
func=tool_instance.execute
|
|
408
|
+
func=tool_instance.execute,
|
|
335
409
|
)
|
|
336
410
|
tool_found = True
|
|
337
411
|
break
|
|
@@ -346,14 +420,19 @@ class ToolRegistry(OutputHandler):
|
|
|
346
420
|
sys.path.remove(parent_dir)
|
|
347
421
|
|
|
348
422
|
except Exception as e:
|
|
349
|
-
PrettyOutput.print(
|
|
423
|
+
PrettyOutput.print(
|
|
424
|
+
f"从 {Path(file_path).name} 加载工具失败: {str(e)}", OutputType.ERROR
|
|
425
|
+
)
|
|
350
426
|
return False
|
|
351
|
-
|
|
427
|
+
|
|
352
428
|
@staticmethod
|
|
353
429
|
def _has_tool_calls_block(content: str) -> bool:
|
|
354
430
|
"""从内容中提取工具调用块"""
|
|
355
|
-
return
|
|
356
|
-
|
|
431
|
+
return (
|
|
432
|
+
re.search(ot("TOOL_CALL") + r"(.*?)" + ct("TOOL_CALL"), content, re.DOTALL)
|
|
433
|
+
is not None
|
|
434
|
+
)
|
|
435
|
+
|
|
357
436
|
@staticmethod
|
|
358
437
|
def _extract_tool_calls(content: str) -> Tuple[Dict[str, Dict[str, Any]], str]:
|
|
359
438
|
"""从内容中提取工具调用。
|
|
@@ -368,28 +447,42 @@ class ToolRegistry(OutputHandler):
|
|
|
368
447
|
Exception: 如果工具调用缺少必要字段
|
|
369
448
|
"""
|
|
370
449
|
# 将内容拆分为行
|
|
371
|
-
data = re.findall(
|
|
450
|
+
data = re.findall(
|
|
451
|
+
ot("TOOL_CALL") + r"(.*?)" + ct("TOOL_CALL"), content, re.DOTALL
|
|
452
|
+
)
|
|
372
453
|
ret = []
|
|
373
454
|
for item in data:
|
|
374
455
|
try:
|
|
375
456
|
msg = yaml.safe_load(item)
|
|
376
|
-
if
|
|
457
|
+
if "name" in msg and "arguments" in msg and "want" in msg:
|
|
377
458
|
ret.append(msg)
|
|
378
459
|
else:
|
|
379
|
-
return
|
|
380
|
-
|
|
381
|
-
|
|
460
|
+
return (
|
|
461
|
+
{},
|
|
462
|
+
f"""工具调用格式错误,请检查工具调用格式。
|
|
463
|
+
|
|
464
|
+
{tool_call_help}""",
|
|
465
|
+
)
|
|
382
466
|
except Exception as e:
|
|
383
|
-
return
|
|
384
|
-
|
|
385
|
-
|
|
467
|
+
return (
|
|
468
|
+
{},
|
|
469
|
+
f"""工具调用格式错误,请检查工具调用格式。
|
|
470
|
+
|
|
471
|
+
{tool_call_help}""",
|
|
472
|
+
)
|
|
386
473
|
if len(ret) > 1:
|
|
387
474
|
return {}, "检测到多个工具调用,请一次只处理一个工具调用。"
|
|
388
475
|
return ret[0] if ret else {}, ""
|
|
389
476
|
|
|
390
|
-
def register_tool(
|
|
477
|
+
def register_tool(
|
|
478
|
+
self,
|
|
479
|
+
name: str,
|
|
480
|
+
description: str,
|
|
481
|
+
parameters: Any,
|
|
482
|
+
func: Callable[..., Dict[str, Any]],
|
|
483
|
+
) -> None:
|
|
391
484
|
"""注册新工具
|
|
392
|
-
|
|
485
|
+
|
|
393
486
|
参数:
|
|
394
487
|
name: 工具名称
|
|
395
488
|
description: 工具描述
|
|
@@ -400,10 +493,10 @@ class ToolRegistry(OutputHandler):
|
|
|
400
493
|
|
|
401
494
|
def get_tool(self, name: str) -> Optional[Tool]:
|
|
402
495
|
"""获取工具
|
|
403
|
-
|
|
496
|
+
|
|
404
497
|
参数:
|
|
405
498
|
name: 工具名称
|
|
406
|
-
|
|
499
|
+
|
|
407
500
|
返回:
|
|
408
501
|
Optional[Tool]: 找到的工具实例,如果不存在则返回None
|
|
409
502
|
"""
|
|
@@ -411,7 +504,7 @@ class ToolRegistry(OutputHandler):
|
|
|
411
504
|
|
|
412
505
|
def get_all_tools(self) -> List[Dict[str, Any]]:
|
|
413
506
|
"""获取所有工具(Ollama格式定义)
|
|
414
|
-
|
|
507
|
+
|
|
415
508
|
返回:
|
|
416
509
|
List[Dict[str, Any]]: 包含所有工具信息的列表
|
|
417
510
|
"""
|
|
@@ -419,17 +512,21 @@ class ToolRegistry(OutputHandler):
|
|
|
419
512
|
|
|
420
513
|
def execute_tool(self, name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
421
514
|
"""执行指定工具
|
|
422
|
-
|
|
515
|
+
|
|
423
516
|
参数:
|
|
424
517
|
name: 工具名称
|
|
425
518
|
arguments: 工具参数
|
|
426
|
-
|
|
519
|
+
|
|
427
520
|
返回:
|
|
428
521
|
Dict[str, Any]: 包含执行结果的字典,包含success、stdout和stderr字段
|
|
429
522
|
"""
|
|
430
523
|
tool = self.get_tool(name)
|
|
431
524
|
if tool is None:
|
|
432
|
-
return {
|
|
525
|
+
return {
|
|
526
|
+
"success": False,
|
|
527
|
+
"stderr": f"工具 {name} 不存在,可用的工具有: {', '.join(self.tools.keys())}",
|
|
528
|
+
"stdout": "",
|
|
529
|
+
}
|
|
433
530
|
return tool.execute(arguments)
|
|
434
531
|
|
|
435
532
|
def _format_tool_output(self, stdout: str, stderr: str) -> str:
|
|
@@ -450,7 +547,6 @@ class ToolRegistry(OutputHandler):
|
|
|
450
547
|
output = "\n\n".join(output_parts)
|
|
451
548
|
return "无输出和错误" if not output else output
|
|
452
549
|
|
|
453
|
-
|
|
454
550
|
def handle_tool_calls(self, tool_call: Dict[str, Any], agent: Any) -> str:
|
|
455
551
|
try:
|
|
456
552
|
name = tool_call["name"] # 确保name是str类型
|
|
@@ -462,30 +558,36 @@ class ToolRegistry(OutputHandler):
|
|
|
462
558
|
try:
|
|
463
559
|
args = json.loads(args)
|
|
464
560
|
except json.JSONDecodeError:
|
|
465
|
-
PrettyOutput.print(
|
|
561
|
+
PrettyOutput.print(
|
|
562
|
+
f"工具参数格式无效: {name} {tool_call_help}", OutputType.ERROR
|
|
563
|
+
)
|
|
466
564
|
return ""
|
|
467
565
|
|
|
468
566
|
# 执行工具调用
|
|
469
567
|
result = self.execute_tool(name, args) # 修正参数传递
|
|
470
568
|
|
|
471
569
|
# 格式化输出
|
|
472
|
-
output = self._format_tool_output(
|
|
570
|
+
output = self._format_tool_output(
|
|
571
|
+
result["stdout"], result.get("stderr", "")
|
|
572
|
+
)
|
|
473
573
|
|
|
474
574
|
# 处理结果
|
|
475
575
|
if get_context_token_count(output) > self.max_input_token_count:
|
|
476
|
-
with tempfile.NamedTemporaryFile(
|
|
576
|
+
with tempfile.NamedTemporaryFile(
|
|
577
|
+
mode="w", suffix=".txt", delete=False
|
|
578
|
+
) as tmp_file:
|
|
477
579
|
output_file = tmp_file.name
|
|
478
580
|
tmp_file.write(output)
|
|
479
581
|
tmp_file.flush()
|
|
480
582
|
platform: Any = PlatformRegistry().get_normal_platform()
|
|
481
583
|
if platform:
|
|
482
584
|
platform.set_suppress_output(False)
|
|
483
|
-
platform.upload_files([output_file])
|
|
585
|
+
platform.upload_files([output_file]) # TODO 处理错误
|
|
484
586
|
prompt = f"该文件为工具执行结果,请阅读文件内容,并根据文件提取出以下信息:{want}"
|
|
485
587
|
return f"""工具调用原始输出过长,以下是根据输出提出的信息:
|
|
486
588
|
|
|
487
589
|
{platform.chat_until_success(prompt)}"""
|
|
488
|
-
|
|
590
|
+
|
|
489
591
|
return output
|
|
490
592
|
|
|
491
593
|
except Exception as e:
|
|
@@ -500,33 +602,37 @@ def main() -> int:
|
|
|
500
602
|
|
|
501
603
|
init_env()
|
|
502
604
|
|
|
503
|
-
parser = argparse.ArgumentParser(description=
|
|
504
|
-
subparsers = parser.add_subparsers(dest=
|
|
605
|
+
parser = argparse.ArgumentParser(description="Jarvis 工具系统命令行界面")
|
|
606
|
+
subparsers = parser.add_subparsers(dest="command", help="命令")
|
|
505
607
|
|
|
506
608
|
# 列出工具子命令
|
|
507
|
-
list_parser = subparsers.add_parser(
|
|
508
|
-
list_parser.add_argument(
|
|
509
|
-
list_parser.add_argument(
|
|
609
|
+
list_parser = subparsers.add_parser("list", help="列出所有可用工具")
|
|
610
|
+
list_parser.add_argument("--json", action="store_true", help="以JSON格式输出")
|
|
611
|
+
list_parser.add_argument("--detailed", action="store_true", help="显示详细信息")
|
|
510
612
|
|
|
511
613
|
# 调用工具子命令
|
|
512
|
-
call_parser = subparsers.add_parser(
|
|
513
|
-
call_parser.add_argument(
|
|
514
|
-
call_parser.add_argument(
|
|
515
|
-
call_parser.add_argument(
|
|
614
|
+
call_parser = subparsers.add_parser("call", help="调用指定工具")
|
|
615
|
+
call_parser.add_argument("tool_name", help="要调用的工具名称")
|
|
616
|
+
call_parser.add_argument("--args", type=str, help="工具参数 (JSON格式)")
|
|
617
|
+
call_parser.add_argument(
|
|
618
|
+
"--args-file", type=str, help="从文件加载工具参数 (JSON格式)"
|
|
619
|
+
)
|
|
516
620
|
|
|
517
621
|
args = parser.parse_args()
|
|
518
622
|
|
|
519
623
|
# 初始化工具注册表
|
|
520
624
|
registry = ToolRegistry()
|
|
521
625
|
|
|
522
|
-
if args.command ==
|
|
626
|
+
if args.command == "list":
|
|
523
627
|
tools = registry.get_all_tools()
|
|
524
628
|
|
|
525
629
|
if args.json:
|
|
526
630
|
if args.detailed:
|
|
527
631
|
print(json.dumps(tools, indent=2, ensure_ascii=False))
|
|
528
632
|
else:
|
|
529
|
-
simple_tools = [
|
|
633
|
+
simple_tools = [
|
|
634
|
+
{"name": t["name"], "description": t["description"]} for t in tools
|
|
635
|
+
]
|
|
530
636
|
print(json.dumps(simple_tools, indent=2, ensure_ascii=False))
|
|
531
637
|
else:
|
|
532
638
|
PrettyOutput.section("可用工具列表", OutputType.SYSTEM)
|
|
@@ -535,14 +641,9 @@ def main() -> int:
|
|
|
535
641
|
print(f" 描述: {tool['description']}")
|
|
536
642
|
if args.detailed:
|
|
537
643
|
print(f" 参数:")
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
req_mark = "*" if param_name in required else ""
|
|
542
|
-
desc = param_info.get('description', '无描述')
|
|
543
|
-
print(f" - {param_name}{req_mark}: {desc}")
|
|
544
|
-
|
|
545
|
-
elif args.command == 'call':
|
|
644
|
+
print(tool["parameters"])
|
|
645
|
+
|
|
646
|
+
elif args.command == "call":
|
|
546
647
|
tool_name = args.tool_name
|
|
547
648
|
tool_obj = registry.get_tool(tool_name)
|
|
548
649
|
|
|
@@ -563,23 +664,27 @@ def main() -> int:
|
|
|
563
664
|
|
|
564
665
|
elif args.args_file:
|
|
565
666
|
try:
|
|
566
|
-
with open(args.args_file,
|
|
667
|
+
with open(args.args_file, "r", encoding="utf-8") as f:
|
|
567
668
|
tool_args = json.load(f)
|
|
568
669
|
except (json.JSONDecodeError, FileNotFoundError) as e:
|
|
569
|
-
PrettyOutput.print(
|
|
670
|
+
PrettyOutput.print(
|
|
671
|
+
f"错误: 无法从文件加载参数: {str(e)}", OutputType.ERROR
|
|
672
|
+
)
|
|
570
673
|
return 1
|
|
571
674
|
|
|
572
675
|
# 检查必需参数
|
|
573
|
-
required_params = tool_obj.parameters.get(
|
|
676
|
+
required_params = tool_obj.parameters.get("required", [])
|
|
574
677
|
missing_params = [p for p in required_params if p not in tool_args]
|
|
575
678
|
|
|
576
679
|
if missing_params:
|
|
577
|
-
PrettyOutput.print(
|
|
680
|
+
PrettyOutput.print(
|
|
681
|
+
f"错误: 缺少必需参数: {', '.join(missing_params)}", OutputType.ERROR
|
|
682
|
+
)
|
|
578
683
|
print("\n参数说明:")
|
|
579
|
-
params = tool_obj.parameters.get(
|
|
684
|
+
params = tool_obj.parameters.get("properties", {})
|
|
580
685
|
for param_name in required_params:
|
|
581
686
|
param_info = params.get(param_name, {})
|
|
582
|
-
desc = param_info.get(
|
|
687
|
+
desc = param_info.get("description", "无描述")
|
|
583
688
|
print(f" - {param_name}: {desc}")
|
|
584
689
|
return 1
|
|
585
690
|
|
jarvis/jarvis_utils/config.py
CHANGED
|
@@ -141,13 +141,13 @@ def load_methodology(user_input: str) -> str:
|
|
|
141
141
|
|
|
142
142
|
platform.set_suppress_output(False)
|
|
143
143
|
# 构建提示信息
|
|
144
|
-
prompt = f"""
|
|
144
|
+
prompt = f"""根据用户需求和已有的方法论内容,总结出与该任务/需求相关的方法论: {user_input}
|
|
145
145
|
|
|
146
146
|
请按以下格式回复:
|
|
147
|
-
###
|
|
147
|
+
### 与该任务/需求相关的方法论
|
|
148
148
|
1. [方法论名字]
|
|
149
149
|
2. [方法论名字]
|
|
150
|
-
###
|
|
150
|
+
### 根据以上方法论,总结出方法论内容
|
|
151
151
|
[总结的方法论内容]
|
|
152
152
|
|
|
153
153
|
如果没有匹配的方法论,请输出:没有历史方法论可参考
|