jarvis-ai-assistant 0.1.222__py3-none-any.whl → 0.7.0__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +1143 -245
- jarvis/jarvis_agent/agent_manager.py +97 -0
- jarvis/jarvis_agent/builtin_input_handler.py +12 -10
- jarvis/jarvis_agent/config_editor.py +57 -0
- jarvis/jarvis_agent/edit_file_handler.py +392 -99
- jarvis/jarvis_agent/event_bus.py +48 -0
- jarvis/jarvis_agent/events.py +157 -0
- jarvis/jarvis_agent/file_context_handler.py +79 -0
- jarvis/jarvis_agent/file_methodology_manager.py +117 -0
- jarvis/jarvis_agent/jarvis.py +1117 -147
- jarvis/jarvis_agent/main.py +78 -34
- jarvis/jarvis_agent/memory_manager.py +195 -0
- jarvis/jarvis_agent/methodology_share_manager.py +174 -0
- jarvis/jarvis_agent/prompt_manager.py +82 -0
- jarvis/jarvis_agent/prompts.py +46 -9
- jarvis/jarvis_agent/protocols.py +4 -1
- jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
- jarvis/jarvis_agent/run_loop.py +146 -0
- jarvis/jarvis_agent/session_manager.py +9 -9
- jarvis/jarvis_agent/share_manager.py +228 -0
- jarvis/jarvis_agent/shell_input_handler.py +23 -3
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +212 -0
- jarvis/jarvis_agent/task_manager.py +154 -0
- jarvis/jarvis_agent/task_planner.py +496 -0
- jarvis/jarvis_agent/tool_executor.py +8 -4
- jarvis/jarvis_agent/tool_share_manager.py +139 -0
- jarvis/jarvis_agent/user_interaction.py +42 -0
- jarvis/jarvis_agent/utils.py +54 -0
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +751 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +613 -0
- jarvis/jarvis_c2rust/collector.py +258 -0
- jarvis/jarvis_c2rust/library_replacer.py +1122 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
- jarvis/jarvis_c2rust/optimizer.py +960 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2325 -0
- jarvis/jarvis_code_agent/build_validation_config.py +133 -0
- jarvis/jarvis_code_agent/code_agent.py +1605 -178
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
- jarvis/jarvis_code_agent/lint.py +275 -13
- jarvis/jarvis_code_agent/utils.py +142 -0
- jarvis/jarvis_code_analysis/checklists/loader.py +20 -6
- jarvis/jarvis_code_analysis/code_review.py +583 -548
- jarvis/jarvis_data/config_schema.json +339 -28
- jarvis/jarvis_git_squash/main.py +22 -13
- jarvis/jarvis_git_utils/git_commiter.py +171 -55
- jarvis/jarvis_mcp/sse_mcp_client.py +22 -15
- jarvis/jarvis_mcp/stdio_mcp_client.py +4 -4
- jarvis/jarvis_mcp/streamable_mcp_client.py +36 -16
- jarvis/jarvis_memory_organizer/memory_organizer.py +753 -0
- jarvis/jarvis_methodology/main.py +48 -63
- jarvis/jarvis_multi_agent/__init__.py +302 -43
- jarvis/jarvis_multi_agent/main.py +70 -24
- jarvis/jarvis_platform/ai8.py +40 -23
- jarvis/jarvis_platform/base.py +210 -49
- jarvis/jarvis_platform/human.py +11 -1
- jarvis/jarvis_platform/kimi.py +82 -76
- jarvis/jarvis_platform/openai.py +73 -1
- jarvis/jarvis_platform/registry.py +8 -15
- jarvis/jarvis_platform/tongyi.py +115 -101
- jarvis/jarvis_platform/yuanbao.py +89 -63
- jarvis/jarvis_platform_manager/main.py +194 -132
- jarvis/jarvis_platform_manager/service.py +122 -86
- jarvis/jarvis_rag/cli.py +156 -53
- jarvis/jarvis_rag/embedding_manager.py +155 -12
- jarvis/jarvis_rag/llm_interface.py +10 -13
- jarvis/jarvis_rag/query_rewriter.py +63 -12
- jarvis/jarvis_rag/rag_pipeline.py +222 -40
- jarvis/jarvis_rag/reranker.py +26 -3
- jarvis/jarvis_rag/retriever.py +270 -14
- jarvis/jarvis_sec/__init__.py +3605 -0
- jarvis/jarvis_sec/checkers/__init__.py +32 -0
- jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
- jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
- jarvis/jarvis_sec/cli.py +116 -0
- jarvis/jarvis_sec/report.py +257 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/workflow.py +219 -0
- jarvis/jarvis_smart_shell/main.py +405 -137
- jarvis/jarvis_stats/__init__.py +13 -0
- jarvis/jarvis_stats/cli.py +387 -0
- jarvis/jarvis_stats/stats.py +711 -0
- jarvis/jarvis_stats/storage.py +612 -0
- jarvis/jarvis_stats/visualizer.py +282 -0
- jarvis/jarvis_tools/ask_user.py +1 -0
- jarvis/jarvis_tools/base.py +18 -2
- jarvis/jarvis_tools/clear_memory.py +239 -0
- jarvis/jarvis_tools/cli/main.py +220 -144
- jarvis/jarvis_tools/execute_script.py +52 -12
- jarvis/jarvis_tools/file_analyzer.py +17 -12
- jarvis/jarvis_tools/generate_new_tool.py +46 -24
- jarvis/jarvis_tools/read_code.py +277 -18
- jarvis/jarvis_tools/read_symbols.py +141 -0
- jarvis/jarvis_tools/read_webpage.py +86 -13
- jarvis/jarvis_tools/registry.py +294 -90
- jarvis/jarvis_tools/retrieve_memory.py +227 -0
- jarvis/jarvis_tools/save_memory.py +194 -0
- jarvis/jarvis_tools/search_web.py +62 -28
- jarvis/jarvis_tools/sub_agent.py +205 -0
- jarvis/jarvis_tools/sub_code_agent.py +217 -0
- jarvis/jarvis_tools/virtual_tty.py +330 -62
- jarvis/jarvis_utils/builtin_replace_map.py +4 -5
- jarvis/jarvis_utils/clipboard.py +90 -0
- jarvis/jarvis_utils/config.py +607 -50
- jarvis/jarvis_utils/embedding.py +3 -0
- jarvis/jarvis_utils/fzf.py +57 -0
- jarvis/jarvis_utils/git_utils.py +251 -29
- jarvis/jarvis_utils/globals.py +174 -17
- jarvis/jarvis_utils/http.py +58 -79
- jarvis/jarvis_utils/input.py +899 -153
- jarvis/jarvis_utils/methodology.py +210 -83
- jarvis/jarvis_utils/output.py +220 -137
- jarvis/jarvis_utils/utils.py +1906 -135
- jarvis_ai_assistant-0.7.0.dist-info/METADATA +465 -0
- jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +8 -2
- jarvis/jarvis_git_details/main.py +0 -265
- jarvis/jarvis_platform/oyi.py +0 -357
- jarvis/jarvis_tools/edit_file.py +0 -255
- jarvis/jarvis_tools/rewrite_file.py +0 -195
- jarvis_ai_assistant-0.1.222.dist-info/METADATA +0 -767
- jarvis_ai_assistant-0.1.222.dist-info/RECORD +0 -110
- /jarvis/{jarvis_git_details → jarvis_memory_organizer}/__init__.py +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
jarvis/jarvis_tools/registry.py
CHANGED
|
@@ -7,17 +7,17 @@ import tempfile
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any, Callable, Dict, List, Optional, Protocol, Tuple
|
|
9
9
|
|
|
10
|
-
import yaml
|
|
10
|
+
import yaml # type: ignore[import-untyped]
|
|
11
11
|
|
|
12
12
|
from jarvis.jarvis_mcp import McpClient
|
|
13
13
|
from jarvis.jarvis_mcp.sse_mcp_client import SSEMcpClient
|
|
14
14
|
from jarvis.jarvis_mcp.stdio_mcp_client import StdioMcpClient
|
|
15
15
|
from jarvis.jarvis_mcp.streamable_mcp_client import StreamableMcpClient
|
|
16
16
|
from jarvis.jarvis_tools.base import Tool
|
|
17
|
-
from jarvis.jarvis_utils.config import get_data_dir
|
|
17
|
+
from jarvis.jarvis_utils.config import get_data_dir, get_tool_load_dirs
|
|
18
18
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
19
19
|
from jarvis.jarvis_utils.tag import ct, ot
|
|
20
|
-
from jarvis.jarvis_utils.utils import is_context_overflow
|
|
20
|
+
from jarvis.jarvis_utils.utils import is_context_overflow, daily_check_git_updates
|
|
21
21
|
|
|
22
22
|
tool_call_help = f"""
|
|
23
23
|
<tool_system_guide>
|
|
@@ -31,6 +31,7 @@ tool_call_help = f"""
|
|
|
31
31
|
{ot("TOOL_CALL")}
|
|
32
32
|
want: 想要从执行结果中获取到的信息,如果工具输出内容过长,会根据此字段尝试提取有效信息
|
|
33
33
|
name: 工具名称
|
|
34
|
+
|
|
34
35
|
arguments:
|
|
35
36
|
param1: 值1
|
|
36
37
|
param2: 值2
|
|
@@ -50,6 +51,7 @@ arguments:
|
|
|
50
51
|
- 完全按照上述格式
|
|
51
52
|
- 使用正确的YAML格式,2个空格作为缩进
|
|
52
53
|
- 包含所有必需参数
|
|
54
|
+
- {ot("TOOL_CALL")} 和 {ct("TOOL_CALL")} 必须出现在行首
|
|
53
55
|
</rule>
|
|
54
56
|
|
|
55
57
|
<rule>
|
|
@@ -71,14 +73,15 @@ arguments:
|
|
|
71
73
|
|
|
72
74
|
<string_format>
|
|
73
75
|
# 📝 字符串参数格式
|
|
74
|
-
|
|
76
|
+
使用 |2 语法表示字符串参数,防止多行字符串行首空格引起歧义。
|
|
75
77
|
|
|
76
78
|
{ot("TOOL_CALL")}
|
|
77
79
|
want: 当前的git状态,期望获取xxx的提交记录
|
|
78
80
|
name: execute_script
|
|
81
|
+
|
|
79
82
|
arguments:
|
|
80
83
|
interpreter: bash
|
|
81
|
-
|
|
84
|
+
script_content: |
|
|
82
85
|
git status --porcelain
|
|
83
86
|
{ct("TOOL_CALL")}
|
|
84
87
|
</string_format>
|
|
@@ -95,11 +98,11 @@ arguments:
|
|
|
95
98
|
<common_errors>
|
|
96
99
|
# ⚠️ 常见错误
|
|
97
100
|
- 同时调用多个工具
|
|
98
|
-
- 字符串参数缺少 |2
|
|
99
101
|
- 假设工具结果
|
|
100
102
|
- 创建虚构对话
|
|
101
103
|
- 在没有所需信息的情况下继续
|
|
102
104
|
- yaml 格式错误
|
|
105
|
+
- {ot("TOOL_CALL")} 和 {ct("TOOL_CALL")} 没有出现在行首
|
|
103
106
|
</common_errors>
|
|
104
107
|
</tool_system_guide>
|
|
105
108
|
"""
|
|
@@ -107,18 +110,21 @@ arguments:
|
|
|
107
110
|
|
|
108
111
|
class OutputHandlerProtocol(Protocol):
|
|
109
112
|
def name(self) -> str: ...
|
|
113
|
+
|
|
110
114
|
def can_handle(self, response: str) -> bool: ...
|
|
115
|
+
|
|
111
116
|
def prompt(self) -> str: ...
|
|
117
|
+
|
|
112
118
|
def handle(self, response: str, agent: Any) -> Tuple[bool, Any]: ...
|
|
113
119
|
|
|
114
120
|
|
|
115
121
|
class ToolRegistry(OutputHandlerProtocol):
|
|
116
|
-
|
|
117
122
|
def name(self) -> str:
|
|
118
123
|
return "TOOL_CALL"
|
|
119
124
|
|
|
120
125
|
def can_handle(self, response: str) -> bool:
|
|
121
|
-
|
|
126
|
+
# 仅当 {ot("TOOL_CALL")} 出现在行首时才认为可以处理
|
|
127
|
+
return re.search(rf'(?m){re.escape(ot("TOOL_CALL"))}', response) is not None
|
|
122
128
|
|
|
123
129
|
def prompt(self) -> str:
|
|
124
130
|
"""加载工具"""
|
|
@@ -165,11 +171,33 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
165
171
|
return tools_prompt
|
|
166
172
|
return ""
|
|
167
173
|
|
|
168
|
-
def handle(self, response: str,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
174
|
+
def handle(self, response: str, agent_: Any) -> Tuple[bool, Any]:
|
|
175
|
+
try:
|
|
176
|
+
tool_call, err_msg, auto_completed = self._extract_tool_calls(response)
|
|
177
|
+
if err_msg:
|
|
178
|
+
# 只要工具解析错误,追加工具使用帮助信息(相当于一次 <ToolUsage>)
|
|
179
|
+
try:
|
|
180
|
+
from jarvis.jarvis_agent import Agent
|
|
181
|
+
agent: Agent = agent_
|
|
182
|
+
tool_usage = agent.get_tool_usage_prompt()
|
|
183
|
+
return False, f"{err_msg}\n\n{tool_usage}"
|
|
184
|
+
except Exception:
|
|
185
|
+
# 兼容处理:无法获取Agent或ToolUsage时,至少返回工具系统帮助信息
|
|
186
|
+
return False, f"{err_msg}\n\n{tool_call_help}"
|
|
187
|
+
result = self.handle_tool_calls(tool_call, agent_)
|
|
188
|
+
if auto_completed:
|
|
189
|
+
# 如果自动补全了结束标签,在结果中添加说明信息
|
|
190
|
+
result = f"检测到工具调用缺少结束标签,已自动补全{ct('TOOL_CALL')}。请确保后续工具调用包含完整的开始和结束标签。\n\n{result}"
|
|
191
|
+
return False, result
|
|
192
|
+
except Exception as e:
|
|
193
|
+
PrettyOutput.print(f"工具调用处理失败: {str(e)}", OutputType.ERROR)
|
|
194
|
+
from jarvis.jarvis_agent import Agent
|
|
195
|
+
|
|
196
|
+
agent: Agent = agent_
|
|
197
|
+
return (
|
|
198
|
+
False,
|
|
199
|
+
f"工具调用处理失败: {str(e)}\n\n{agent.get_tool_usage_prompt()}",
|
|
200
|
+
)
|
|
173
201
|
|
|
174
202
|
def __init__(self) -> None:
|
|
175
203
|
"""初始化工具注册表"""
|
|
@@ -178,30 +206,46 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
178
206
|
self._load_builtin_tools()
|
|
179
207
|
self._load_external_tools()
|
|
180
208
|
self._load_mcp_tools()
|
|
209
|
+
# 应用工具配置组过滤
|
|
210
|
+
self._apply_tool_config_filter()
|
|
181
211
|
|
|
182
212
|
def _get_tool_stats(self) -> Dict[str, int]:
|
|
183
213
|
"""从数据目录获取工具调用统计"""
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
214
|
+
from jarvis.jarvis_stats.stats import StatsManager
|
|
215
|
+
from datetime import datetime
|
|
216
|
+
|
|
217
|
+
# 获取所有工具的统计数据
|
|
218
|
+
tool_stats = {}
|
|
219
|
+
tools = self.get_all_tools()
|
|
220
|
+
|
|
221
|
+
# 获取所有历史数据(从很早的时间开始)
|
|
222
|
+
end_time = datetime.now()
|
|
223
|
+
start_time = datetime(2000, 1, 1) # 使用一个足够早的时间
|
|
224
|
+
|
|
225
|
+
for tool in tools:
|
|
226
|
+
tool_name = tool["name"]
|
|
227
|
+
# 获取该工具的统计数据
|
|
228
|
+
stats_data = StatsManager.get_stats(
|
|
229
|
+
metric_name=tool_name,
|
|
230
|
+
start_time=start_time,
|
|
231
|
+
end_time=end_time,
|
|
232
|
+
tags={"group": "tool"},
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# 计算总调用次数
|
|
236
|
+
if stats_data and "records" in stats_data:
|
|
237
|
+
total_count = sum(record["value"] for record in stats_data["records"])
|
|
238
|
+
tool_stats[tool_name] = int(total_count)
|
|
239
|
+
else:
|
|
240
|
+
tool_stats[tool_name] = 0
|
|
241
|
+
|
|
242
|
+
return tool_stats
|
|
194
243
|
|
|
195
244
|
def _update_tool_stats(self, name: str) -> None:
|
|
196
245
|
"""更新工具调用统计"""
|
|
197
|
-
stats
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
try:
|
|
201
|
-
with open(stats_file, "w", encoding="utf-8") as f:
|
|
202
|
-
yaml.safe_dump(stats, f, allow_unicode=True)
|
|
203
|
-
except Exception as e:
|
|
204
|
-
PrettyOutput.print(f"保存工具调用统计失败: {str(e)}", OutputType.WARNING)
|
|
246
|
+
from jarvis.jarvis_stats.stats import StatsManager
|
|
247
|
+
|
|
248
|
+
StatsManager.increment(name, group="tool")
|
|
205
249
|
|
|
206
250
|
def use_tools(self, name: List[str]) -> None:
|
|
207
251
|
"""使用指定工具
|
|
@@ -231,6 +275,35 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
231
275
|
name: tool for name, tool in self.tools.items() if name not in names
|
|
232
276
|
}
|
|
233
277
|
|
|
278
|
+
def _apply_tool_config_filter(self) -> None:
|
|
279
|
+
"""应用工具配置组的过滤规则"""
|
|
280
|
+
from jarvis.jarvis_utils.config import get_tool_use_list, get_tool_dont_use_list
|
|
281
|
+
|
|
282
|
+
use_list = get_tool_use_list()
|
|
283
|
+
dont_use_list = get_tool_dont_use_list()
|
|
284
|
+
|
|
285
|
+
# 如果配置了 use 列表,只保留列表中的工具
|
|
286
|
+
if use_list:
|
|
287
|
+
filtered_tools = {}
|
|
288
|
+
missing = []
|
|
289
|
+
for tool_name in use_list:
|
|
290
|
+
if tool_name in self.tools:
|
|
291
|
+
filtered_tools[tool_name] = self.tools[tool_name]
|
|
292
|
+
else:
|
|
293
|
+
missing.append(tool_name)
|
|
294
|
+
if missing:
|
|
295
|
+
PrettyOutput.print(
|
|
296
|
+
"警告: 配置的工具不存在: " + ", ".join(f"'{name}'" for name in missing),
|
|
297
|
+
OutputType.WARNING,
|
|
298
|
+
)
|
|
299
|
+
self.tools = filtered_tools
|
|
300
|
+
|
|
301
|
+
# 如果配置了 dont_use 列表,排除列表中的工具
|
|
302
|
+
if dont_use_list:
|
|
303
|
+
for tool_name in dont_use_list:
|
|
304
|
+
if tool_name in self.tools:
|
|
305
|
+
del self.tools[tool_name]
|
|
306
|
+
|
|
234
307
|
def _load_mcp_tools(self) -> None:
|
|
235
308
|
"""加载MCP工具,优先从配置获取,其次从目录扫描"""
|
|
236
309
|
from jarvis.jarvis_utils.config import get_mcp_config
|
|
@@ -254,14 +327,15 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
254
327
|
)
|
|
255
328
|
|
|
256
329
|
# 遍历目录中的所有.yaml文件
|
|
330
|
+
error_lines = []
|
|
257
331
|
for file_path in mcp_tools_dir.glob("*.yaml"):
|
|
258
332
|
try:
|
|
259
333
|
config = yaml.safe_load(open(file_path, "r", encoding="utf-8"))
|
|
260
334
|
self.register_mcp_tool_by_config(config)
|
|
261
335
|
except Exception as e:
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
336
|
+
error_lines.append(f"文件 {file_path} 加载失败: {str(e)}")
|
|
337
|
+
if error_lines:
|
|
338
|
+
PrettyOutput.print("\n".join(error_lines), OutputType.WARNING)
|
|
265
339
|
|
|
266
340
|
def _load_builtin_tools(self) -> None:
|
|
267
341
|
"""从内置工具目录加载工具"""
|
|
@@ -276,18 +350,52 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
276
350
|
self.register_tool_by_file(str(file_path))
|
|
277
351
|
|
|
278
352
|
def _load_external_tools(self) -> None:
|
|
279
|
-
"""从jarvis_data/tools
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
353
|
+
"""从jarvis_data/tools和配置的目录加载外部工具"""
|
|
354
|
+
from jarvis.jarvis_utils.config import get_central_tool_repo
|
|
355
|
+
|
|
356
|
+
tool_dirs = [str(Path(get_data_dir()) / "tools")] + get_tool_load_dirs()
|
|
357
|
+
|
|
358
|
+
# 如果配置了中心工具仓库,将其添加到加载路径
|
|
359
|
+
central_repo = get_central_tool_repo()
|
|
360
|
+
if central_repo:
|
|
361
|
+
# 支持本地目录路径或Git仓库URL
|
|
362
|
+
expanded = os.path.expanduser(os.path.expandvars(central_repo))
|
|
363
|
+
if os.path.isdir(expanded):
|
|
364
|
+
# 直接使用本地目录(支持Git仓库的子目录)
|
|
365
|
+
tool_dirs.append(expanded)
|
|
366
|
+
else:
|
|
367
|
+
# 中心工具仓库存储在数据目录下的特定位置
|
|
368
|
+
central_repo_path = os.path.join(get_data_dir(), "central_tool_repo")
|
|
369
|
+
tool_dirs.append(central_repo_path)
|
|
283
370
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
371
|
+
# 确保中心工具仓库被克隆/更新
|
|
372
|
+
if not os.path.exists(central_repo_path):
|
|
373
|
+
try:
|
|
374
|
+
import subprocess
|
|
375
|
+
|
|
376
|
+
subprocess.run(
|
|
377
|
+
["git", "clone", central_repo, central_repo_path], check=True
|
|
378
|
+
)
|
|
379
|
+
except Exception as e:
|
|
380
|
+
PrettyOutput.print(
|
|
381
|
+
f"克隆中心工具仓库失败: {str(e)}", OutputType.ERROR
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# --- 全局每日更新检查 ---
|
|
385
|
+
daily_check_git_updates(tool_dirs, "tools")
|
|
386
|
+
|
|
387
|
+
for tool_dir in tool_dirs:
|
|
388
|
+
p_tool_dir = Path(tool_dir)
|
|
389
|
+
if not p_tool_dir.exists() or not p_tool_dir.is_dir():
|
|
288
390
|
continue
|
|
289
391
|
|
|
290
|
-
|
|
392
|
+
# 遍历目录中的所有.py文件
|
|
393
|
+
for file_path in p_tool_dir.glob("*.py"):
|
|
394
|
+
# 跳过__init__.py
|
|
395
|
+
if file_path.name == "__init__.py":
|
|
396
|
+
continue
|
|
397
|
+
|
|
398
|
+
self.register_tool_by_file(str(file_path))
|
|
291
399
|
|
|
292
400
|
def register_mcp_tool_by_config(self, config: Dict[str, Any]) -> bool:
|
|
293
401
|
"""从配置字典加载并注册工具
|
|
@@ -307,10 +415,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
307
415
|
|
|
308
416
|
# 检查enable标志
|
|
309
417
|
if not config.get("enable", True):
|
|
310
|
-
|
|
311
|
-
f"MCP配置{config.get('name', '')}已禁用(enable=false),跳过注册",
|
|
312
|
-
OutputType.INFO,
|
|
313
|
-
)
|
|
418
|
+
|
|
314
419
|
return False
|
|
315
420
|
|
|
316
421
|
name = config.get("name", "mcp")
|
|
@@ -322,10 +427,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
322
427
|
args.pop("agent", None)
|
|
323
428
|
args.pop("want", None)
|
|
324
429
|
ret = client.get_resource_list()
|
|
325
|
-
|
|
326
|
-
f"MCP {name} 资源列表:\n{yaml.safe_dump(ret, allow_unicode=True)}",
|
|
327
|
-
OutputType.TOOL,
|
|
328
|
-
)
|
|
430
|
+
|
|
329
431
|
return {
|
|
330
432
|
"success": True,
|
|
331
433
|
"stdout": yaml.safe_dump(ret, allow_unicode=True),
|
|
@@ -346,10 +448,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
346
448
|
"stderr": "缺少必需的uri参数",
|
|
347
449
|
}
|
|
348
450
|
ret = client.get_resource(args["uri"])
|
|
349
|
-
|
|
350
|
-
f"MCP {name} 获取资源:\n{yaml.safe_dump(ret, allow_unicode=True)}",
|
|
351
|
-
OutputType.TOOL,
|
|
352
|
-
)
|
|
451
|
+
|
|
353
452
|
return ret
|
|
354
453
|
|
|
355
454
|
return execute
|
|
@@ -360,10 +459,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
360
459
|
args.pop("agent", None)
|
|
361
460
|
args.pop("want", None)
|
|
362
461
|
ret = client.execute(tool_name, args)
|
|
363
|
-
|
|
364
|
-
f"MCP {name} {tool_name} 执行结果:\n{yaml.safe_dump(ret, allow_unicode=True)}",
|
|
365
|
-
OutputType.TOOL,
|
|
366
|
-
)
|
|
462
|
+
|
|
367
463
|
return ret
|
|
368
464
|
|
|
369
465
|
return execute
|
|
@@ -396,12 +492,13 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
396
492
|
return False
|
|
397
493
|
|
|
398
494
|
# 创建MCP客户端
|
|
495
|
+
mcp_client: McpClient
|
|
399
496
|
if config["type"] == "stdio":
|
|
400
|
-
mcp_client
|
|
497
|
+
mcp_client = StdioMcpClient(config)
|
|
401
498
|
elif config["type"] == "sse":
|
|
402
|
-
mcp_client
|
|
499
|
+
mcp_client = SSEMcpClient(config)
|
|
403
500
|
elif config["type"] == "streamable":
|
|
404
|
-
mcp_client
|
|
501
|
+
mcp_client = StreamableMcpClient(config)
|
|
405
502
|
else:
|
|
406
503
|
raise ValueError(f"不支持的MCP客户端类型: {config['type']}")
|
|
407
504
|
|
|
@@ -491,7 +588,6 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
491
588
|
and hasattr(item, "execute")
|
|
492
589
|
and item.name == module_name
|
|
493
590
|
):
|
|
494
|
-
|
|
495
591
|
if hasattr(item, "check"):
|
|
496
592
|
if not item.check():
|
|
497
593
|
continue
|
|
@@ -505,6 +601,9 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
505
601
|
description=tool_instance.description,
|
|
506
602
|
parameters=tool_instance.parameters,
|
|
507
603
|
func=tool_instance.execute,
|
|
604
|
+
protocol_version=getattr(
|
|
605
|
+
tool_instance, "protocol_version", "1.0"
|
|
606
|
+
),
|
|
508
607
|
)
|
|
509
608
|
tool_found = True
|
|
510
609
|
break
|
|
@@ -526,29 +625,72 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
526
625
|
|
|
527
626
|
@staticmethod
|
|
528
627
|
def _has_tool_calls_block(content: str) -> bool:
|
|
529
|
-
"""
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
is not None
|
|
533
|
-
)
|
|
628
|
+
"""从内容中提取工具调用块(仅匹配行首标签)"""
|
|
629
|
+
pattern = rf'(?ms){re.escape(ot("TOOL_CALL"))}(.*?)^{re.escape(ct("TOOL_CALL"))}'
|
|
630
|
+
return re.search(pattern, content) is not None
|
|
534
631
|
|
|
535
632
|
@staticmethod
|
|
536
|
-
def _extract_tool_calls(
|
|
633
|
+
def _extract_tool_calls(
|
|
634
|
+
content: str,
|
|
635
|
+
) -> Tuple[Dict[str, Dict[str, Any]], str, bool]:
|
|
537
636
|
"""从内容中提取工具调用。
|
|
538
637
|
|
|
539
638
|
参数:
|
|
540
639
|
content: 包含工具调用的内容
|
|
541
640
|
|
|
542
641
|
返回:
|
|
543
|
-
|
|
642
|
+
Tuple[Dict[str, Dict[str, Any]], str, bool]:
|
|
643
|
+
- 第一个元素是提取的工具调用字典
|
|
644
|
+
- 第二个元素是错误消息字符串(成功时为"")
|
|
645
|
+
- 第三个元素是是否自动补全了结束标签
|
|
544
646
|
|
|
545
647
|
异常:
|
|
546
648
|
Exception: 如果工具调用缺少必要字段
|
|
547
649
|
"""
|
|
650
|
+
# 如果</TOOL_CALL>出现在响应的末尾,但是前面没有换行符,自动插入一个换行符进行修复
|
|
651
|
+
if content.rstrip().endswith(ct("TOOL_CALL")):
|
|
652
|
+
pos = content.rfind(ct("TOOL_CALL"))
|
|
653
|
+
if pos > 0 and content[pos - 1] not in ("\n", "\r"):
|
|
654
|
+
content = content[:pos] + "\n" + content[pos:]
|
|
655
|
+
|
|
548
656
|
# 将内容拆分为行
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
657
|
+
pattern = rf'(?ms){re.escape(ot("TOOL_CALL"))}(.*?)^{re.escape(ct("TOOL_CALL"))}'
|
|
658
|
+
data = re.findall(pattern, content)
|
|
659
|
+
auto_completed = False
|
|
660
|
+
if not data:
|
|
661
|
+
# can_handle 确保 ot("TOOL_CALL") 在内容中(行首)。
|
|
662
|
+
# 如果数据为空,则表示行首的 ct("TOOL_CALL") 可能丢失。
|
|
663
|
+
has_open_at_bol = re.search(rf'(?m){re.escape(ot("TOOL_CALL"))}', content) is not None
|
|
664
|
+
has_close_at_bol = re.search(rf'(?m)^{re.escape(ct("TOOL_CALL"))}', content) is not None
|
|
665
|
+
if has_open_at_bol and not has_close_at_bol:
|
|
666
|
+
# 尝试通过附加结束标签来修复它(确保结束标签位于行首)
|
|
667
|
+
fixed_content = content.strip() + f"\n{ct('TOOL_CALL')}"
|
|
668
|
+
|
|
669
|
+
# 再次提取,并检查YAML是否有效
|
|
670
|
+
temp_data = re.findall(
|
|
671
|
+
pattern,
|
|
672
|
+
fixed_content,
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
if temp_data:
|
|
676
|
+
try:
|
|
677
|
+
yaml.safe_load(temp_data[0]) # Check if valid YAML
|
|
678
|
+
|
|
679
|
+
# Ask user for confirmation
|
|
680
|
+
|
|
681
|
+
data = temp_data
|
|
682
|
+
auto_completed = True
|
|
683
|
+
except (yaml.YAMLError, EOFError, KeyboardInterrupt):
|
|
684
|
+
# Even after fixing, it's not valid YAML, or user cancelled.
|
|
685
|
+
# Fall through to the original error.
|
|
686
|
+
pass
|
|
687
|
+
|
|
688
|
+
if not data:
|
|
689
|
+
return (
|
|
690
|
+
{},
|
|
691
|
+
f"只有{ot('TOOL_CALL')}标签,未找到{ct('TOOL_CALL')}标签,调用格式错误,请检查工具调用格式。\n{tool_call_help}",
|
|
692
|
+
False,
|
|
693
|
+
)
|
|
552
694
|
ret = []
|
|
553
695
|
for item in data:
|
|
554
696
|
try:
|
|
@@ -560,6 +702,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
560
702
|
{e}
|
|
561
703
|
|
|
562
704
|
{tool_call_help}""",
|
|
705
|
+
False,
|
|
563
706
|
)
|
|
564
707
|
|
|
565
708
|
if "name" in msg and "arguments" in msg and "want" in msg:
|
|
@@ -570,10 +713,11 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
570
713
|
f"""工具调用格式错误,请检查工具调用格式(缺少name、arguments、want字段)。
|
|
571
714
|
|
|
572
715
|
{tool_call_help}""",
|
|
716
|
+
False,
|
|
573
717
|
)
|
|
574
718
|
if len(ret) > 1:
|
|
575
|
-
return {}, "检测到多个工具调用,请一次只处理一个工具调用。"
|
|
576
|
-
return ret[0] if ret else {}, ""
|
|
719
|
+
return {}, "检测到多个工具调用,请一次只处理一个工具调用。", False
|
|
720
|
+
return ret[0] if ret else {}, "", auto_completed
|
|
577
721
|
|
|
578
722
|
def register_tool(
|
|
579
723
|
self,
|
|
@@ -581,6 +725,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
581
725
|
description: str,
|
|
582
726
|
parameters: Any,
|
|
583
727
|
func: Callable[..., Dict[str, Any]],
|
|
728
|
+
protocol_version: str = "1.0",
|
|
584
729
|
) -> None:
|
|
585
730
|
"""注册新工具
|
|
586
731
|
|
|
@@ -590,7 +735,11 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
590
735
|
parameters: 工具参数定义
|
|
591
736
|
func: 工具执行函数
|
|
592
737
|
"""
|
|
593
|
-
|
|
738
|
+
if name in self.tools:
|
|
739
|
+
PrettyOutput.print(
|
|
740
|
+
f"警告: 工具 '{name}' 已存在,将被覆盖", OutputType.WARNING
|
|
741
|
+
)
|
|
742
|
+
self.tools[name] = Tool(name, description, parameters, func, protocol_version)
|
|
594
743
|
|
|
595
744
|
def get_tool(self, name: str) -> Optional[Tool]:
|
|
596
745
|
"""获取工具
|
|
@@ -611,12 +760,15 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
611
760
|
"""
|
|
612
761
|
return [tool.to_dict() for tool in self.tools.values()]
|
|
613
762
|
|
|
614
|
-
def execute_tool(
|
|
763
|
+
def execute_tool(
|
|
764
|
+
self, name: str, arguments: Dict[str, Any], agent: Optional[Any] = None
|
|
765
|
+
) -> Dict[str, Any]:
|
|
615
766
|
"""执行指定工具
|
|
616
767
|
|
|
617
768
|
参数:
|
|
618
769
|
name: 工具名称
|
|
619
770
|
arguments: 工具参数
|
|
771
|
+
agent: 智能体实例(由系统内部传递,用于v2.0分离agent与参数)
|
|
620
772
|
|
|
621
773
|
返回:
|
|
622
774
|
Dict[str, Any]: 包含执行结果的字典,包含success、stdout和stderr字段
|
|
@@ -632,7 +784,23 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
632
784
|
# 更新工具调用统计
|
|
633
785
|
self._update_tool_stats(name)
|
|
634
786
|
|
|
635
|
-
|
|
787
|
+
# 根据工具实现声明的协议版本分发调用方式
|
|
788
|
+
try:
|
|
789
|
+
if getattr(tool, "protocol_version", "1.0") == "2.0":
|
|
790
|
+
# v2.0: agent与参数分离传递
|
|
791
|
+
return tool.func(arguments, agent) # type: ignore[misc]
|
|
792
|
+
else:
|
|
793
|
+
# v1.0: 兼容旧实现,将agent注入到arguments(如果提供)
|
|
794
|
+
args_to_call = arguments.copy() if isinstance(arguments, dict) else {}
|
|
795
|
+
if agent is not None:
|
|
796
|
+
args_to_call["agent"] = agent
|
|
797
|
+
return tool.execute(args_to_call)
|
|
798
|
+
except TypeError:
|
|
799
|
+
# 兼容处理:如果函数签名不匹配,回退到旧方式
|
|
800
|
+
args_to_call = arguments.copy() if isinstance(arguments, dict) else {}
|
|
801
|
+
if agent is not None:
|
|
802
|
+
args_to_call["agent"] = agent
|
|
803
|
+
return tool.execute(args_to_call)
|
|
636
804
|
|
|
637
805
|
def _format_tool_output(self, stdout: str, stderr: str) -> str:
|
|
638
806
|
"""格式化工具输出为可读字符串
|
|
@@ -671,25 +839,51 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
671
839
|
def handle_tool_calls(self, tool_call: Dict[str, Any], agent: Any) -> str:
|
|
672
840
|
try:
|
|
673
841
|
name = tool_call["name"] # 确保name是str类型
|
|
674
|
-
args = tool_call["arguments"] #
|
|
842
|
+
args = tool_call["arguments"] # 原始参数(来自外部协议)
|
|
675
843
|
want = tool_call["want"]
|
|
676
|
-
args["agent"] = agent
|
|
677
844
|
|
|
678
845
|
from jarvis.jarvis_agent import Agent
|
|
679
846
|
|
|
680
847
|
agent_instance: Agent = agent
|
|
681
848
|
|
|
849
|
+
# 如果args是字符串,尝试解析为JSON
|
|
682
850
|
if isinstance(args, str):
|
|
683
851
|
try:
|
|
684
852
|
args = json.loads(args)
|
|
685
853
|
except json.JSONDecodeError:
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
854
|
+
# 返回错误并附带完整的工具使用提示,指导下一次正确调用
|
|
855
|
+
try:
|
|
856
|
+
usage_prompt = agent_instance.get_tool_usage_prompt()
|
|
857
|
+
except Exception:
|
|
858
|
+
usage_prompt = tool_call_help
|
|
859
|
+
return f"工具参数格式无效: {name}。arguments 应为可解析的 JSON 或对象,请按工具调用格式提供。\n\n{usage_prompt}"
|
|
690
860
|
|
|
691
|
-
#
|
|
692
|
-
result = self.execute_tool(name, args)
|
|
861
|
+
# 执行工具调用(根据工具实现的协议版本,由系统在内部决定agent的传递方式)
|
|
862
|
+
result = self.execute_tool(name, args, agent)
|
|
863
|
+
|
|
864
|
+
# 记录本轮实际执行的工具,供上层逻辑(如记忆保存判定)使用
|
|
865
|
+
try:
|
|
866
|
+
from jarvis.jarvis_agent import Agent # 延迟导入避免循环依赖
|
|
867
|
+
agent_instance_for_record: Agent = agent_instance
|
|
868
|
+
# 记录最后一次执行的工具
|
|
869
|
+
agent_instance_for_record.set_user_data("__last_executed_tool__", name) # type: ignore
|
|
870
|
+
# 记录本轮累计执行的工具列表
|
|
871
|
+
executed_list = agent_instance_for_record.get_user_data("__executed_tools__") # type: ignore
|
|
872
|
+
if not isinstance(executed_list, list):
|
|
873
|
+
executed_list = []
|
|
874
|
+
executed_list.append(name)
|
|
875
|
+
agent_instance_for_record.set_user_data("__executed_tools__", executed_list) # type: ignore
|
|
876
|
+
except Exception:
|
|
877
|
+
pass
|
|
878
|
+
|
|
879
|
+
# 如果执行失败,附带工具使用提示返回
|
|
880
|
+
if not result.get("success", False):
|
|
881
|
+
try:
|
|
882
|
+
usage_prompt = agent_instance.get_tool_usage_prompt()
|
|
883
|
+
except Exception:
|
|
884
|
+
usage_prompt = tool_call_help
|
|
885
|
+
err_output = self._format_tool_output(result.get("stdout", ""), result.get("stderr", ""))
|
|
886
|
+
return f"{err_output}\n\n{usage_prompt}"
|
|
693
887
|
|
|
694
888
|
# 格式化输出
|
|
695
889
|
output = self._format_tool_output(
|
|
@@ -697,7 +891,10 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
697
891
|
)
|
|
698
892
|
|
|
699
893
|
# 检查内容是否过大
|
|
700
|
-
|
|
894
|
+
model_group = None
|
|
895
|
+
if agent_instance.model:
|
|
896
|
+
model_group = agent_instance.model.model_group
|
|
897
|
+
is_large_content = is_context_overflow(output, model_group)
|
|
701
898
|
|
|
702
899
|
if is_large_content:
|
|
703
900
|
# 创建临时文件
|
|
@@ -719,8 +916,9 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
719
916
|
[output_file]
|
|
720
917
|
)
|
|
721
918
|
if upload_success:
|
|
722
|
-
# 删除args的agent
|
|
723
|
-
args
|
|
919
|
+
# 删除args的agent键(保持协议v2.0的“参数与agent分离”在可视化中的一致性)
|
|
920
|
+
if isinstance(args, dict):
|
|
921
|
+
args.pop("agent", None)
|
|
724
922
|
prompt = f"""
|
|
725
923
|
以下是之前对话的关键信息总结:
|
|
726
924
|
|
|
@@ -747,4 +945,10 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
747
945
|
|
|
748
946
|
except Exception as e:
|
|
749
947
|
PrettyOutput.print(f"工具执行失败:{str(e)}", OutputType.ERROR)
|
|
750
|
-
|
|
948
|
+
try:
|
|
949
|
+
from jarvis.jarvis_agent import Agent # 延迟导入避免循环依赖
|
|
950
|
+
agent_instance_for_prompt: Agent = agent # type: ignore
|
|
951
|
+
usage_prompt = agent_instance_for_prompt.get_tool_usage_prompt()
|
|
952
|
+
except Exception:
|
|
953
|
+
usage_prompt = tool_call_help
|
|
954
|
+
return f"工具调用失败: {str(e)}\n\n{usage_prompt}"
|