jarvis-ai-assistant 0.3.30__py3-none-any.whl → 0.7.6__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 +458 -152
- jarvis/jarvis_agent/agent_manager.py +17 -13
- jarvis/jarvis_agent/builtin_input_handler.py +2 -6
- jarvis/jarvis_agent/config_editor.py +2 -7
- jarvis/jarvis_agent/event_bus.py +82 -12
- jarvis/jarvis_agent/file_context_handler.py +329 -0
- jarvis/jarvis_agent/file_methodology_manager.py +3 -4
- jarvis/jarvis_agent/jarvis.py +628 -55
- jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
- jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
- jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
- jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
- jarvis/jarvis_agent/language_support_info.py +486 -0
- jarvis/jarvis_agent/main.py +34 -10
- jarvis/jarvis_agent/memory_manager.py +7 -16
- jarvis/jarvis_agent/methodology_share_manager.py +10 -16
- jarvis/jarvis_agent/prompt_manager.py +1 -1
- jarvis/jarvis_agent/prompts.py +193 -171
- jarvis/jarvis_agent/protocols.py +8 -12
- jarvis/jarvis_agent/run_loop.py +105 -9
- jarvis/jarvis_agent/session_manager.py +2 -3
- jarvis/jarvis_agent/share_manager.py +20 -22
- jarvis/jarvis_agent/shell_input_handler.py +1 -2
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +31 -6
- jarvis/jarvis_agent/task_manager.py +11 -27
- jarvis/jarvis_agent/tool_executor.py +2 -3
- jarvis/jarvis_agent/tool_share_manager.py +12 -24
- jarvis/jarvis_agent/utils.py +5 -1
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +786 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +575 -0
- jarvis/jarvis_c2rust/collector.py +250 -0
- jarvis/jarvis_c2rust/constants.py +26 -0
- jarvis/jarvis_c2rust/library_replacer.py +1254 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1272 -0
- jarvis/jarvis_c2rust/loaders.py +207 -0
- jarvis/jarvis_c2rust/models.py +28 -0
- jarvis/jarvis_c2rust/optimizer.py +2157 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2983 -0
- jarvis/jarvis_c2rust/utils.py +385 -0
- jarvis/jarvis_code_agent/build_validation_config.py +132 -0
- jarvis/jarvis_code_agent/code_agent.py +1371 -220
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +65 -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 +106 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +72 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +70 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +53 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +47 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +61 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +153 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +648 -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 +110 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +49 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +299 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +215 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +269 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +281 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +605 -0
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +252 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +58 -0
- jarvis/jarvis_code_agent/lint.py +501 -8
- jarvis/jarvis_code_agent/utils.py +141 -0
- jarvis/jarvis_code_analysis/code_review.py +493 -584
- jarvis/jarvis_data/config_schema.json +128 -12
- jarvis/jarvis_git_squash/main.py +4 -5
- jarvis/jarvis_git_utils/git_commiter.py +82 -75
- jarvis/jarvis_mcp/sse_mcp_client.py +22 -29
- jarvis/jarvis_mcp/stdio_mcp_client.py +12 -13
- jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
- jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
- jarvis/jarvis_methodology/main.py +32 -48
- jarvis/jarvis_multi_agent/__init__.py +287 -55
- jarvis/jarvis_multi_agent/main.py +36 -4
- jarvis/jarvis_platform/base.py +524 -202
- jarvis/jarvis_platform/human.py +7 -8
- jarvis/jarvis_platform/kimi.py +30 -36
- jarvis/jarvis_platform/openai.py +88 -25
- jarvis/jarvis_platform/registry.py +26 -10
- jarvis/jarvis_platform/tongyi.py +24 -25
- jarvis/jarvis_platform/yuanbao.py +32 -43
- jarvis/jarvis_platform_manager/main.py +66 -77
- jarvis/jarvis_platform_manager/service.py +8 -13
- jarvis/jarvis_rag/cli.py +53 -55
- jarvis/jarvis_rag/embedding_manager.py +13 -18
- jarvis/jarvis_rag/llm_interface.py +8 -9
- jarvis/jarvis_rag/query_rewriter.py +10 -21
- jarvis/jarvis_rag/rag_pipeline.py +24 -27
- jarvis/jarvis_rag/reranker.py +4 -5
- jarvis/jarvis_rag/retriever.py +28 -30
- jarvis/jarvis_sec/__init__.py +305 -0
- jarvis/jarvis_sec/agents.py +143 -0
- jarvis/jarvis_sec/analysis.py +276 -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 +139 -0
- jarvis/jarvis_sec/clustering.py +1439 -0
- jarvis/jarvis_sec/file_manager.py +427 -0
- jarvis/jarvis_sec/parsers.py +73 -0
- jarvis/jarvis_sec/prompts.py +268 -0
- jarvis/jarvis_sec/report.py +336 -0
- jarvis/jarvis_sec/review.py +453 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/utils.py +499 -0
- jarvis/jarvis_sec/verification.py +848 -0
- jarvis/jarvis_sec/workflow.py +226 -0
- jarvis/jarvis_smart_shell/main.py +38 -87
- jarvis/jarvis_stats/cli.py +2 -2
- jarvis/jarvis_stats/stats.py +8 -8
- jarvis/jarvis_stats/storage.py +15 -21
- jarvis/jarvis_stats/visualizer.py +1 -1
- jarvis/jarvis_tools/clear_memory.py +3 -20
- jarvis/jarvis_tools/cli/main.py +21 -23
- jarvis/jarvis_tools/edit_file.py +1019 -132
- jarvis/jarvis_tools/execute_script.py +83 -25
- jarvis/jarvis_tools/file_analyzer.py +6 -9
- jarvis/jarvis_tools/generate_new_tool.py +14 -21
- jarvis/jarvis_tools/lsp_client.py +1552 -0
- jarvis/jarvis_tools/methodology.py +2 -3
- jarvis/jarvis_tools/read_code.py +1736 -35
- jarvis/jarvis_tools/read_symbols.py +140 -0
- jarvis/jarvis_tools/read_webpage.py +12 -13
- jarvis/jarvis_tools/registry.py +427 -200
- jarvis/jarvis_tools/retrieve_memory.py +20 -19
- jarvis/jarvis_tools/rewrite_file.py +72 -158
- jarvis/jarvis_tools/save_memory.py +3 -15
- jarvis/jarvis_tools/search_web.py +18 -18
- jarvis/jarvis_tools/sub_agent.py +36 -43
- jarvis/jarvis_tools/sub_code_agent.py +25 -26
- jarvis/jarvis_tools/virtual_tty.py +55 -33
- jarvis/jarvis_utils/clipboard.py +7 -10
- jarvis/jarvis_utils/config.py +232 -45
- jarvis/jarvis_utils/embedding.py +8 -5
- jarvis/jarvis_utils/fzf.py +8 -8
- jarvis/jarvis_utils/git_utils.py +225 -36
- jarvis/jarvis_utils/globals.py +3 -3
- jarvis/jarvis_utils/http.py +1 -1
- jarvis/jarvis_utils/input.py +99 -48
- jarvis/jarvis_utils/jsonnet_compat.py +465 -0
- jarvis/jarvis_utils/methodology.py +52 -48
- jarvis/jarvis_utils/utils.py +819 -491
- jarvis_ai_assistant-0.7.6.dist-info/METADATA +600 -0
- jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +4 -0
- jarvis/jarvis_agent/config.py +0 -92
- jarvis/jarvis_agent/edit_file_handler.py +0 -296
- jarvis/jarvis_platform/ai8.py +0 -332
- jarvis/jarvis_tools/ask_user.py +0 -54
- jarvis_ai_assistant-0.3.30.dist-info/METADATA +0 -381
- jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
jarvis/jarvis_tools/registry.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
from jarvis.jarvis_utils.jsonnet_compat import loads as json_loads
|
|
2
3
|
import json
|
|
3
4
|
import os
|
|
4
5
|
import re
|
|
@@ -15,93 +16,58 @@ from jarvis.jarvis_mcp.stdio_mcp_client import StdioMcpClient
|
|
|
15
16
|
from jarvis.jarvis_mcp.streamable_mcp_client import StreamableMcpClient
|
|
16
17
|
from jarvis.jarvis_tools.base import Tool
|
|
17
18
|
from jarvis.jarvis_utils.config import get_data_dir, get_tool_load_dirs
|
|
18
|
-
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
19
19
|
from jarvis.jarvis_utils.tag import ct, ot
|
|
20
20
|
from jarvis.jarvis_utils.utils import is_context_overflow, daily_check_git_updates
|
|
21
21
|
|
|
22
|
+
_multiline_example = """ {
|
|
23
|
+
"multiline_str": |||
|
|
24
|
+
第一行:直接换行,无需 \\n
|
|
25
|
+
第二行:包含"双引号",无需转义
|
|
26
|
+
第三行:包含'单引号',直接写
|
|
27
|
+
第四行:支持缩进保留
|
|
28
|
+
|||
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
或使用 ``` 代替 |||:
|
|
32
|
+
{
|
|
33
|
+
"multiline_str": ```
|
|
34
|
+
第一行:直接换行,无需 \\n
|
|
35
|
+
第二行:包含"双引号",无需转义
|
|
36
|
+
第三行:包含'单引号',直接写
|
|
37
|
+
第四行:支持缩进保留
|
|
38
|
+
```
|
|
39
|
+
}"""
|
|
40
|
+
|
|
22
41
|
tool_call_help = f"""
|
|
23
42
|
<tool_system_guide>
|
|
24
|
-
|
|
25
|
-
# 🛠️ 工具使用系统
|
|
26
|
-
您正在使用一个需要精确格式和严格规则的工具执行系统。
|
|
27
|
-
</introduction>
|
|
28
|
-
|
|
29
|
-
<format>
|
|
30
|
-
# 📋 工具调用格式
|
|
43
|
+
工具调用格式(Jsonnet):
|
|
31
44
|
{ot("TOOL_CALL")}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
arguments:
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
{{
|
|
46
|
+
"want": "想要从执行结果中获取到的信息",
|
|
47
|
+
"name": "工具名称",
|
|
48
|
+
"arguments": {{
|
|
49
|
+
"param1": "值1",
|
|
50
|
+
"param2": "值2"
|
|
51
|
+
}}
|
|
52
|
+
}}
|
|
38
53
|
{ct("TOOL_CALL")}
|
|
39
|
-
</format>
|
|
40
|
-
|
|
41
|
-
<rules>
|
|
42
|
-
# ❗ 关键规则
|
|
43
|
-
<rule>
|
|
44
|
-
### 1. 每次只使用一个工具
|
|
45
|
-
- 一次只执行一个工具
|
|
46
|
-
- 等待结果后再进行下一步
|
|
47
|
-
</rule>
|
|
48
|
-
|
|
49
|
-
<rule>
|
|
50
|
-
### 2. 严格遵守格式
|
|
51
|
-
- 完全按照上述格式
|
|
52
|
-
- 使用正确的YAML格式,2个空格作为缩进
|
|
53
|
-
- 包含所有必需参数
|
|
54
|
-
</rule>
|
|
55
|
-
|
|
56
|
-
<rule>
|
|
57
|
-
### 3. 结果处理
|
|
58
|
-
- 等待执行结果
|
|
59
|
-
- 不要假设结果
|
|
60
|
-
- 不要创建虚假响应
|
|
61
|
-
- 不要想象对话
|
|
62
|
-
</rule>
|
|
63
|
-
|
|
64
|
-
<rule>
|
|
65
|
-
### 4. 信息管理
|
|
66
|
-
- 如果信息不足,询问用户
|
|
67
|
-
- 跳过不必要的步骤
|
|
68
|
-
- 如果卡住,请求指导
|
|
69
|
-
- 不要在没有完整信息的情况下继续
|
|
70
|
-
</rule>
|
|
71
|
-
</rules>
|
|
72
|
-
|
|
73
|
-
<string_format>
|
|
74
|
-
# 📝 字符串参数格式
|
|
75
|
-
使用 |2 语法表示字符串参数,防止多行字符串行首空格引起歧义。
|
|
76
|
-
|
|
77
|
-
{ot("TOOL_CALL")}
|
|
78
|
-
want: 当前的git状态,期望获取xxx的提交记录
|
|
79
|
-
name: execute_script
|
|
80
54
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
<common_errors>
|
|
98
|
-
# ⚠️ 常见错误
|
|
99
|
-
- 同时调用多个工具
|
|
100
|
-
- 假设工具结果
|
|
101
|
-
- 创建虚构对话
|
|
102
|
-
- 在没有所需信息的情况下继续
|
|
103
|
-
- yaml 格式错误
|
|
104
|
-
</common_errors>
|
|
55
|
+
Jsonnet格式特性:
|
|
56
|
+
- 字符串引号:可使用双引号或单引号
|
|
57
|
+
- 多行字符串:推荐使用 ||| 或 ``` 分隔符包裹多行字符串,直接换行无需转义,支持保留缩进
|
|
58
|
+
示例:
|
|
59
|
+
{_multiline_example}
|
|
60
|
+
- 尾随逗号:对象/数组最后一个元素后可添加逗号
|
|
61
|
+
- 注释:支持 // 单行或 /* */ 多行注释
|
|
62
|
+
|
|
63
|
+
关键规则:
|
|
64
|
+
1. 每次只使用一个工具,等待结果后再进行下一步
|
|
65
|
+
2. {ot("TOOL_CALL")} 和 {ct("TOOL_CALL")} 必须出现在行首
|
|
66
|
+
3. 多行字符串参数推荐使用 ||| 或 ``` 分隔符包裹,直接换行无需转义,支持保留缩进
|
|
67
|
+
4. 等待执行结果,不要假设或创建虚假响应
|
|
68
|
+
5. 信息不足时询问用户,不要在没有完整信息的情况下继续
|
|
69
|
+
|
|
70
|
+
常见错误:同时调用多个工具、假设工具结果、Jsonnet格式错误、标签未出现在行首
|
|
105
71
|
</tool_system_guide>
|
|
106
72
|
"""
|
|
107
73
|
|
|
@@ -121,7 +87,11 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
121
87
|
return "TOOL_CALL"
|
|
122
88
|
|
|
123
89
|
def can_handle(self, response: str) -> bool:
|
|
124
|
-
|
|
90
|
+
# 仅当 {ot("TOOL_CALL")} 出现在行首时才认为可以处理(忽略大小写)
|
|
91
|
+
has_tool_call = re.search(rf'(?mi){re.escape(ot("TOOL_CALL"))}', response) is not None
|
|
92
|
+
if has_tool_call:
|
|
93
|
+
print("🛠️ 检测到工具调用") # 增加工具emoji
|
|
94
|
+
return has_tool_call
|
|
125
95
|
|
|
126
96
|
def prompt(self) -> str:
|
|
127
97
|
"""加载工具"""
|
|
@@ -136,30 +106,26 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
136
106
|
tools_prompt += f" <name>名称: {tool['name']}</name>\n"
|
|
137
107
|
tools_prompt += f" <description>描述: {tool['description']}</description>\n"
|
|
138
108
|
tools_prompt += " <parameters>\n"
|
|
139
|
-
tools_prompt += " <
|
|
109
|
+
tools_prompt += " <json>|\n"
|
|
140
110
|
|
|
141
|
-
# 生成格式化的
|
|
142
|
-
|
|
111
|
+
# 生成格式化的JSON参数
|
|
112
|
+
json_params = json.dumps(
|
|
143
113
|
tool["parameters"],
|
|
144
|
-
|
|
145
|
-
indent=
|
|
114
|
+
ensure_ascii=False,
|
|
115
|
+
indent=2,
|
|
146
116
|
sort_keys=False,
|
|
147
|
-
width=120, # 增加行宽限制
|
|
148
117
|
)
|
|
149
118
|
|
|
150
119
|
# 添加缩进并移除尾部空格
|
|
151
|
-
for line in
|
|
120
|
+
for line in json_params.split("\n"):
|
|
152
121
|
tools_prompt += f" {line.rstrip()}\n"
|
|
153
122
|
|
|
154
|
-
tools_prompt += " </
|
|
123
|
+
tools_prompt += " </json>\n"
|
|
155
124
|
tools_prompt += " </parameters>\n"
|
|
156
125
|
tools_prompt += " </tool>\n"
|
|
157
126
|
|
|
158
|
-
except
|
|
159
|
-
|
|
160
|
-
f"工具 {tool['name']} 参数序列化失败: {str(e)}",
|
|
161
|
-
OutputType.ERROR,
|
|
162
|
-
)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
print(f"❌ 工具 {tool['name']} 参数序列化失败: {str(e)}")
|
|
163
129
|
continue
|
|
164
130
|
|
|
165
131
|
tools_prompt += " </tools_list>\n"
|
|
@@ -170,16 +136,25 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
170
136
|
|
|
171
137
|
def handle(self, response: str, agent_: Any) -> Tuple[bool, Any]:
|
|
172
138
|
try:
|
|
173
|
-
|
|
139
|
+
# 传递agent给_extract_tool_calls,以便在解析失败时调用大模型修复
|
|
140
|
+
tool_call, err_msg, auto_completed = self._extract_tool_calls(response, agent_)
|
|
174
141
|
if err_msg:
|
|
175
|
-
|
|
142
|
+
# 只要工具解析错误,追加工具使用帮助信息(相当于一次 <ToolUsage>)
|
|
143
|
+
try:
|
|
144
|
+
from jarvis.jarvis_agent import Agent
|
|
145
|
+
agent: Agent = agent_
|
|
146
|
+
tool_usage = agent.get_tool_usage_prompt()
|
|
147
|
+
return False, f"{err_msg}\n\n{tool_usage}"
|
|
148
|
+
except Exception:
|
|
149
|
+
# 兼容处理:无法获取Agent或ToolUsage时,至少返回工具系统帮助信息
|
|
150
|
+
return False, f"{err_msg}\n\n{tool_call_help}"
|
|
176
151
|
result = self.handle_tool_calls(tool_call, agent_)
|
|
177
152
|
if auto_completed:
|
|
178
153
|
# 如果自动补全了结束标签,在结果中添加说明信息
|
|
179
154
|
result = f"检测到工具调用缺少结束标签,已自动补全{ct('TOOL_CALL')}。请确保后续工具调用包含完整的开始和结束标签。\n\n{result}"
|
|
180
155
|
return False, result
|
|
181
156
|
except Exception as e:
|
|
182
|
-
|
|
157
|
+
print(f"❌ 工具调用处理失败: {str(e)}")
|
|
183
158
|
from jarvis.jarvis_agent import Agent
|
|
184
159
|
|
|
185
160
|
agent: Agent = agent_
|
|
@@ -191,6 +166,8 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
191
166
|
def __init__(self) -> None:
|
|
192
167
|
"""初始化工具注册表"""
|
|
193
168
|
self.tools: Dict[str, Tool] = {}
|
|
169
|
+
# 记录内置工具名称,用于区分内置工具和用户自定义工具
|
|
170
|
+
self._builtin_tool_names: set = set()
|
|
194
171
|
# 加载内置工具和外部工具
|
|
195
172
|
self._load_builtin_tools()
|
|
196
173
|
self._load_external_tools()
|
|
@@ -244,10 +221,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
244
221
|
"""
|
|
245
222
|
missing_tools = [tool_name for tool_name in name if tool_name not in self.tools]
|
|
246
223
|
if missing_tools:
|
|
247
|
-
|
|
248
|
-
f"工具 {missing_tools} 不存在,可用的工具有: {', '.join(self.tools.keys())}",
|
|
249
|
-
OutputType.WARNING,
|
|
250
|
-
)
|
|
224
|
+
print(f"⚠️ 工具 {missing_tools} 不存在,可用的工具有: {', '.join(self.tools.keys())}")
|
|
251
225
|
self.tools = {
|
|
252
226
|
tool_name: self.tools[tool_name]
|
|
253
227
|
for tool_name in name
|
|
@@ -281,10 +255,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
281
255
|
else:
|
|
282
256
|
missing.append(tool_name)
|
|
283
257
|
if missing:
|
|
284
|
-
|
|
285
|
-
"警告: 配置的工具不存在: " + ", ".join(f"'{name}'" for name in missing),
|
|
286
|
-
OutputType.WARNING,
|
|
287
|
-
)
|
|
258
|
+
print("⚠️ 警告: 配置的工具不存在: " + ", ".join(f"'{name}'" for name in missing))
|
|
288
259
|
self.tools = filtered_tools
|
|
289
260
|
|
|
290
261
|
# 如果配置了 dont_use 列表,排除列表中的工具
|
|
@@ -310,21 +281,19 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
310
281
|
return
|
|
311
282
|
|
|
312
283
|
# 添加警告信息
|
|
313
|
-
|
|
314
|
-
"警告: 从文件目录加载MCP工具的方式将在未来版本中废弃,请尽快迁移到JARVIS_MCP配置方式",
|
|
315
|
-
OutputType.WARNING,
|
|
316
|
-
)
|
|
284
|
+
print("⚠️ 警告: 从文件目录加载MCP工具的方式将在未来版本中废弃,请尽快迁移到JARVIS_MCP配置方式")
|
|
317
285
|
|
|
318
286
|
# 遍历目录中的所有.yaml文件
|
|
319
287
|
error_lines = []
|
|
320
288
|
for file_path in mcp_tools_dir.glob("*.yaml"):
|
|
321
289
|
try:
|
|
322
|
-
|
|
290
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
291
|
+
config = yaml.safe_load(f)
|
|
323
292
|
self.register_mcp_tool_by_config(config)
|
|
324
293
|
except Exception as e:
|
|
325
294
|
error_lines.append(f"文件 {file_path} 加载失败: {str(e)}")
|
|
326
295
|
if error_lines:
|
|
327
|
-
|
|
296
|
+
print("⚠️ " + "\n⚠️ ".join(error_lines))
|
|
328
297
|
|
|
329
298
|
def _load_builtin_tools(self) -> None:
|
|
330
299
|
"""从内置工具目录加载工具"""
|
|
@@ -337,6 +306,9 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
337
306
|
continue
|
|
338
307
|
|
|
339
308
|
self.register_tool_by_file(str(file_path))
|
|
309
|
+
|
|
310
|
+
# 记录当前已加载的工具名称为内置工具
|
|
311
|
+
self._builtin_tool_names = set(self.tools.keys())
|
|
340
312
|
|
|
341
313
|
def _load_external_tools(self) -> None:
|
|
342
314
|
"""从jarvis_data/tools和配置的目录加载外部工具"""
|
|
@@ -347,22 +319,26 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
347
319
|
# 如果配置了中心工具仓库,将其添加到加载路径
|
|
348
320
|
central_repo = get_central_tool_repo()
|
|
349
321
|
if central_repo:
|
|
350
|
-
#
|
|
351
|
-
|
|
352
|
-
|
|
322
|
+
# 支持本地目录路径或Git仓库URL
|
|
323
|
+
expanded = os.path.expanduser(os.path.expandvars(central_repo))
|
|
324
|
+
if os.path.isdir(expanded):
|
|
325
|
+
# 直接使用本地目录(支持Git仓库的子目录)
|
|
326
|
+
tool_dirs.append(expanded)
|
|
327
|
+
else:
|
|
328
|
+
# 中心工具仓库存储在数据目录下的特定位置
|
|
329
|
+
central_repo_path = os.path.join(get_data_dir(), "central_tool_repo")
|
|
330
|
+
tool_dirs.append(central_repo_path)
|
|
353
331
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
332
|
+
# 确保中心工具仓库被克隆/更新
|
|
333
|
+
if not os.path.exists(central_repo_path):
|
|
334
|
+
try:
|
|
335
|
+
import subprocess
|
|
358
336
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
f"克隆中心工具仓库失败: {str(e)}", OutputType.ERROR
|
|
365
|
-
)
|
|
337
|
+
subprocess.run(
|
|
338
|
+
["git", "clone", central_repo, central_repo_path], check=True
|
|
339
|
+
)
|
|
340
|
+
except Exception as e:
|
|
341
|
+
print(f"❌ 克隆中心工具仓库失败: {str(e)}")
|
|
366
342
|
|
|
367
343
|
# --- 全局每日更新检查 ---
|
|
368
344
|
daily_check_git_updates(tool_dirs, "tools")
|
|
@@ -391,9 +367,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
391
367
|
"""
|
|
392
368
|
try:
|
|
393
369
|
if "type" not in config:
|
|
394
|
-
|
|
395
|
-
f"配置{config.get('name', '')}缺少type字段", OutputType.WARNING
|
|
396
|
-
)
|
|
370
|
+
print(f"⚠️ 配置{config.get('name', '')}缺少type字段")
|
|
397
371
|
return False
|
|
398
372
|
|
|
399
373
|
# 检查enable标志
|
|
@@ -413,7 +387,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
413
387
|
|
|
414
388
|
return {
|
|
415
389
|
"success": True,
|
|
416
|
-
"stdout":
|
|
390
|
+
"stdout": json.dumps(ret, ensure_ascii=False, indent=2),
|
|
417
391
|
"stderr": "",
|
|
418
392
|
}
|
|
419
393
|
|
|
@@ -449,29 +423,18 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
449
423
|
|
|
450
424
|
if config["type"] == "stdio":
|
|
451
425
|
if "command" not in config:
|
|
452
|
-
|
|
453
|
-
f"配置{config.get('name', '')}缺少command字段",
|
|
454
|
-
OutputType.WARNING,
|
|
455
|
-
)
|
|
426
|
+
print(f"⚠️ 配置{config.get('name', '')}缺少command字段")
|
|
456
427
|
return False
|
|
457
428
|
elif config["type"] == "sse":
|
|
458
429
|
if "base_url" not in config:
|
|
459
|
-
|
|
460
|
-
f"配置{config.get('name', '')}缺少base_url字段",
|
|
461
|
-
OutputType.WARNING,
|
|
462
|
-
)
|
|
430
|
+
print(f"⚠️ 配置{config.get('name', '')}缺少base_url字段")
|
|
463
431
|
return False
|
|
464
432
|
elif config["type"] == "streamable":
|
|
465
433
|
if "base_url" not in config:
|
|
466
|
-
|
|
467
|
-
f"配置{config.get('name', '')}缺少base_url字段",
|
|
468
|
-
OutputType.WARNING,
|
|
469
|
-
)
|
|
434
|
+
print(f"⚠️ 配置{config.get('name', '')}缺少base_url字段")
|
|
470
435
|
return False
|
|
471
436
|
else:
|
|
472
|
-
|
|
473
|
-
f"不支持的MCP客户端类型: {config['type']}", OutputType.WARNING
|
|
474
|
-
)
|
|
437
|
+
print(f"⚠️ 不支持的MCP客户端类型: {config['type']}")
|
|
475
438
|
return False
|
|
476
439
|
|
|
477
440
|
# 创建MCP客户端
|
|
@@ -488,10 +451,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
488
451
|
# 获取工具信息
|
|
489
452
|
tools = mcp_client.get_tool_list()
|
|
490
453
|
if not tools:
|
|
491
|
-
|
|
492
|
-
f"从配置{config.get('name', '')}获取工具列表失败",
|
|
493
|
-
OutputType.WARNING,
|
|
494
|
-
)
|
|
454
|
+
print(f"⚠️ 从配置{config.get('name', '')}获取工具列表失败")
|
|
495
455
|
return False
|
|
496
456
|
|
|
497
457
|
# 注册每个工具
|
|
@@ -529,9 +489,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
529
489
|
return True
|
|
530
490
|
|
|
531
491
|
except Exception as e:
|
|
532
|
-
|
|
533
|
-
f"MCP配置{config.get('name', '')}加载失败: {str(e)}", OutputType.WARNING
|
|
534
|
-
)
|
|
492
|
+
print(f"⚠️ MCP配置{config.get('name', '')}加载失败: {str(e)}")
|
|
535
493
|
return False
|
|
536
494
|
|
|
537
495
|
def register_tool_by_file(self, file_path: str) -> bool:
|
|
@@ -546,7 +504,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
546
504
|
try:
|
|
547
505
|
p_file_path = Path(file_path).resolve() # 获取绝对路径
|
|
548
506
|
if not p_file_path.exists() or not p_file_path.is_file():
|
|
549
|
-
|
|
507
|
+
print(f"❌ 文件不存在: {p_file_path}")
|
|
550
508
|
return False
|
|
551
509
|
|
|
552
510
|
# 临时将父目录添加到sys.path
|
|
@@ -601,27 +559,176 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
601
559
|
sys.path.remove(parent_dir)
|
|
602
560
|
|
|
603
561
|
except Exception as e:
|
|
604
|
-
|
|
605
|
-
f"从 {Path(file_path).name} 加载工具失败: {str(e)}", OutputType.ERROR
|
|
606
|
-
)
|
|
562
|
+
print(f"❌ 从 {Path(file_path).name} 加载工具失败: {str(e)}")
|
|
607
563
|
return False
|
|
608
564
|
|
|
609
565
|
@staticmethod
|
|
610
566
|
def _has_tool_calls_block(content: str) -> bool:
|
|
611
|
-
"""
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
567
|
+
"""从内容中提取工具调用块(仅匹配行首标签,忽略大小写)"""
|
|
568
|
+
pattern = rf'(?msi){re.escape(ot("TOOL_CALL"))}(.*?)^{re.escape(ct("TOOL_CALL"))}'
|
|
569
|
+
return re.search(pattern, content) is not None
|
|
570
|
+
|
|
571
|
+
@staticmethod
|
|
572
|
+
def _get_long_response_hint(content: str) -> str:
|
|
573
|
+
"""生成长响应的提示信息
|
|
574
|
+
|
|
575
|
+
参数:
|
|
576
|
+
content: 响应内容
|
|
577
|
+
|
|
578
|
+
返回:
|
|
579
|
+
str: 如果响应较长,返回提示信息;否则返回空字符串
|
|
580
|
+
"""
|
|
581
|
+
if len(content) > 2048:
|
|
582
|
+
return "\n\n⚠️ 提示:响应内容较长(超过2048字符),可能是上下文溢出导致工具调用解析失败。如果是修改文件(edit_file)或重写文件(rewrite_file)操作,建议分多次进行,每次处理文件的一部分。"
|
|
583
|
+
return ""
|
|
584
|
+
|
|
585
|
+
@staticmethod
|
|
586
|
+
def _extract_json_from_text(text: str, start_pos: int = 0) -> Tuple[Optional[str], int]:
|
|
587
|
+
"""从文本中提取完整的JSON对象(通过括号匹配)
|
|
588
|
+
|
|
589
|
+
参数:
|
|
590
|
+
text: 要提取的文本
|
|
591
|
+
start_pos: 开始搜索的位置
|
|
592
|
+
|
|
593
|
+
返回:
|
|
594
|
+
Tuple[Optional[str], int]:
|
|
595
|
+
- 第一个元素是提取的JSON字符串(如果找到),否则为None
|
|
596
|
+
- 第二个元素是JSON结束后的位置
|
|
597
|
+
"""
|
|
598
|
+
# 跳过空白字符
|
|
599
|
+
pos = start_pos
|
|
600
|
+
while pos < len(text) and text[pos] in (' ', '\t', '\n', '\r'):
|
|
601
|
+
pos += 1
|
|
602
|
+
|
|
603
|
+
if pos >= len(text):
|
|
604
|
+
return None, pos
|
|
605
|
+
|
|
606
|
+
# 检查是否以 { 开头
|
|
607
|
+
if text[pos] != '{':
|
|
608
|
+
return None, pos
|
|
609
|
+
|
|
610
|
+
# 使用括号匹配找到完整的JSON对象
|
|
611
|
+
brace_count = 0
|
|
612
|
+
in_string = False
|
|
613
|
+
escape_next = False
|
|
614
|
+
string_char = None
|
|
615
|
+
|
|
616
|
+
json_start = pos
|
|
617
|
+
for i in range(pos, len(text)):
|
|
618
|
+
char = text[i]
|
|
619
|
+
|
|
620
|
+
if escape_next:
|
|
621
|
+
escape_next = False
|
|
622
|
+
continue
|
|
623
|
+
|
|
624
|
+
if char == '\\':
|
|
625
|
+
escape_next = True
|
|
626
|
+
continue
|
|
627
|
+
|
|
628
|
+
if not in_string:
|
|
629
|
+
if char in ('"', "'"):
|
|
630
|
+
in_string = True
|
|
631
|
+
string_char = char
|
|
632
|
+
elif char == '{':
|
|
633
|
+
brace_count += 1
|
|
634
|
+
elif char == '}':
|
|
635
|
+
brace_count -= 1
|
|
636
|
+
if brace_count == 0:
|
|
637
|
+
# 找到完整的JSON对象
|
|
638
|
+
return text[json_start:i+1], i + 1
|
|
639
|
+
else:
|
|
640
|
+
if char == string_char:
|
|
641
|
+
in_string = False
|
|
642
|
+
string_char = None
|
|
643
|
+
|
|
644
|
+
return None, len(text)
|
|
645
|
+
|
|
646
|
+
@staticmethod
|
|
647
|
+
def _clean_extra_markers(text: str) -> str:
|
|
648
|
+
"""清理文本中的额外标记(如 <|tool_call_end|> 等)
|
|
649
|
+
|
|
650
|
+
参数:
|
|
651
|
+
text: 要清理的文本
|
|
652
|
+
|
|
653
|
+
返回:
|
|
654
|
+
清理后的文本
|
|
655
|
+
"""
|
|
656
|
+
# 常见的额外标记模式
|
|
657
|
+
extra_markers = [
|
|
658
|
+
r'<\|tool_call_end\|>',
|
|
659
|
+
r'<\|tool_calls_section_end\|>',
|
|
660
|
+
r'<\|.*?\|>', # 匹配所有 <|...|> 格式的标记
|
|
661
|
+
]
|
|
662
|
+
|
|
663
|
+
cleaned = text
|
|
664
|
+
for pattern in extra_markers:
|
|
665
|
+
cleaned = re.sub(pattern, '', cleaned, flags=re.IGNORECASE)
|
|
666
|
+
|
|
667
|
+
return cleaned.strip()
|
|
668
|
+
|
|
669
|
+
@staticmethod
|
|
670
|
+
def _try_llm_fix(content: str, agent: Any, error_msg: str) -> Optional[str]:
|
|
671
|
+
"""尝试使用大模型修复工具调用格式
|
|
672
|
+
|
|
673
|
+
参数:
|
|
674
|
+
content: 包含错误工具调用的内容
|
|
675
|
+
agent: Agent实例,用于调用大模型
|
|
676
|
+
error_msg: 错误消息
|
|
677
|
+
|
|
678
|
+
返回:
|
|
679
|
+
Optional[str]: 修复后的内容,如果修复失败则返回None
|
|
680
|
+
"""
|
|
681
|
+
try:
|
|
682
|
+
from jarvis.jarvis_agent import Agent
|
|
683
|
+
agent_instance: Agent = agent
|
|
684
|
+
|
|
685
|
+
# 获取工具使用说明
|
|
686
|
+
tool_usage = agent_instance.get_tool_usage_prompt()
|
|
687
|
+
|
|
688
|
+
# 构建修复提示
|
|
689
|
+
fix_prompt = f"""你之前的工具调用格式有误,请根据工具使用说明修复以下内容。
|
|
690
|
+
|
|
691
|
+
**错误信息:**
|
|
692
|
+
{error_msg}
|
|
693
|
+
|
|
694
|
+
**工具使用说明:**
|
|
695
|
+
{tool_usage}
|
|
696
|
+
|
|
697
|
+
**错误的工具调用内容:**
|
|
698
|
+
{content}
|
|
699
|
+
|
|
700
|
+
请修复上述工具调用内容,确保:
|
|
701
|
+
1. 包含完整的 {ot("TOOL_CALL")} 和 {ct("TOOL_CALL")} 标签
|
|
702
|
+
2. JSON格式正确,包含 name、arguments、want 三个字段
|
|
703
|
+
3. 如果使用多行字符串,推荐使用 ||| 或 ``` 分隔符包裹
|
|
704
|
+
|
|
705
|
+
请直接返回修复后的完整工具调用内容,不要添加其他说明文字。"""
|
|
706
|
+
|
|
707
|
+
# 调用大模型修复
|
|
708
|
+
print("🤖 尝试使用大模型修复工具调用格式...")
|
|
709
|
+
fixed_content = agent_instance.model.chat_until_success(fix_prompt) # type: ignore
|
|
710
|
+
|
|
711
|
+
if fixed_content:
|
|
712
|
+
print("✅ 大模型修复完成")
|
|
713
|
+
return fixed_content
|
|
714
|
+
else:
|
|
715
|
+
print("❌ 大模型修复失败:返回内容为空")
|
|
716
|
+
return None
|
|
717
|
+
|
|
718
|
+
except Exception as e:
|
|
719
|
+
print(f"❌ 大模型修复失败:{str(e)}")
|
|
720
|
+
return None
|
|
616
721
|
|
|
617
722
|
@staticmethod
|
|
618
723
|
def _extract_tool_calls(
|
|
619
724
|
content: str,
|
|
725
|
+
agent: Optional[Any] = None,
|
|
620
726
|
) -> Tuple[Dict[str, Dict[str, Any]], str, bool]:
|
|
621
727
|
"""从内容中提取工具调用。
|
|
622
728
|
|
|
623
729
|
参数:
|
|
624
730
|
content: 包含工具调用的内容
|
|
731
|
+
agent: 可选的Agent实例,用于在解析失败时调用大模型修复
|
|
625
732
|
|
|
626
733
|
返回:
|
|
627
734
|
Tuple[Dict[str, Dict[str, Any]], str, bool]:
|
|
@@ -632,66 +739,138 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
632
739
|
异常:
|
|
633
740
|
Exception: 如果工具调用缺少必要字段
|
|
634
741
|
"""
|
|
635
|
-
#
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
)
|
|
742
|
+
# 如果</TOOL_CALL>出现在响应的末尾,但是前面没有换行符,自动插入一个换行符进行修复(忽略大小写)
|
|
743
|
+
close_tag = ct("TOOL_CALL")
|
|
744
|
+
# 使用正则表达式查找结束标签(忽略大小写),以获取实际位置和原始大小写
|
|
745
|
+
close_tag_pattern = re.escape(close_tag)
|
|
746
|
+
match = re.search(rf'{close_tag_pattern}$', content.rstrip(), re.IGNORECASE)
|
|
747
|
+
if match:
|
|
748
|
+
pos = match.start()
|
|
749
|
+
if pos > 0 and content[pos - 1] not in ("\n", "\r"):
|
|
750
|
+
content = content[:pos] + "\n" + content[pos:]
|
|
751
|
+
|
|
752
|
+
# 首先尝试标准的提取方式(忽略大小写)
|
|
753
|
+
pattern = rf'(?msi){re.escape(ot("TOOL_CALL"))}(.*?)^{re.escape(ct("TOOL_CALL"))}'
|
|
754
|
+
data = re.findall(pattern, content)
|
|
639
755
|
auto_completed = False
|
|
756
|
+
|
|
757
|
+
# 如果标准提取失败,尝试更宽松的提取方式
|
|
640
758
|
if not data:
|
|
641
|
-
# can_handle 确保 ot("TOOL_CALL")
|
|
642
|
-
#
|
|
643
|
-
|
|
644
|
-
|
|
759
|
+
# can_handle 确保 ot("TOOL_CALL") 在内容中(行首)。
|
|
760
|
+
# 如果数据为空,则表示行首的 ct("TOOL_CALL") 可能丢失。
|
|
761
|
+
has_open_at_bol = re.search(rf'(?mi){re.escape(ot("TOOL_CALL"))}', content) is not None
|
|
762
|
+
has_close_at_bol = re.search(rf'(?mi)^{re.escape(ct("TOOL_CALL"))}', content) is not None
|
|
763
|
+
|
|
764
|
+
if has_open_at_bol and not has_close_at_bol:
|
|
765
|
+
# 尝试通过附加结束标签来修复它(确保结束标签位于行首)
|
|
645
766
|
fixed_content = content.strip() + f"\n{ct('TOOL_CALL')}"
|
|
646
767
|
|
|
647
|
-
# 再次提取,并检查
|
|
768
|
+
# 再次提取,并检查JSON是否有效
|
|
648
769
|
temp_data = re.findall(
|
|
649
|
-
|
|
770
|
+
pattern,
|
|
650
771
|
fixed_content,
|
|
651
|
-
re.DOTALL,
|
|
652
772
|
)
|
|
653
773
|
|
|
654
774
|
if temp_data:
|
|
655
775
|
try:
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
# Ask user for confirmation
|
|
659
|
-
|
|
776
|
+
json_loads(temp_data[0]) # Check if valid JSON
|
|
660
777
|
data = temp_data
|
|
661
778
|
auto_completed = True
|
|
662
|
-
except (
|
|
663
|
-
# Even after fixing, it's not valid
|
|
664
|
-
# Fall through to
|
|
779
|
+
except (Exception, EOFError, KeyboardInterrupt):
|
|
780
|
+
# Even after fixing, it's not valid JSON, or user cancelled.
|
|
781
|
+
# Fall through to try more lenient extraction.
|
|
665
782
|
pass
|
|
666
|
-
|
|
783
|
+
|
|
784
|
+
# 如果仍然没有数据,尝试更宽松的提取:直接从开始标签后提取JSON
|
|
667
785
|
if not data:
|
|
786
|
+
# 找到开始标签的位置
|
|
787
|
+
open_tag_match = re.search(rf'(?i){re.escape(ot("TOOL_CALL"))}', content)
|
|
788
|
+
if open_tag_match:
|
|
789
|
+
# 从开始标签后提取JSON
|
|
790
|
+
start_pos = open_tag_match.end()
|
|
791
|
+
json_str, end_pos = ToolRegistry._extract_json_from_text(content, start_pos)
|
|
792
|
+
|
|
793
|
+
if json_str:
|
|
794
|
+
# 清理JSON字符串中的额外标记
|
|
795
|
+
json_str = ToolRegistry._clean_extra_markers(json_str)
|
|
796
|
+
|
|
797
|
+
# 尝试解析JSON
|
|
798
|
+
try:
|
|
799
|
+
parsed = json_loads(json_str)
|
|
800
|
+
# 验证是否包含必要字段
|
|
801
|
+
if "name" in parsed and "arguments" in parsed and "want" in parsed:
|
|
802
|
+
data = [json_str]
|
|
803
|
+
auto_completed = True
|
|
804
|
+
except Exception:
|
|
805
|
+
# JSON解析失败,继续尝试其他方法
|
|
806
|
+
pass
|
|
807
|
+
|
|
808
|
+
# 如果仍然没有数据,尝试使用大模型修复
|
|
809
|
+
if not data:
|
|
810
|
+
long_hint = ToolRegistry._get_long_response_hint(content)
|
|
811
|
+
error_msg = f"只有{ot('TOOL_CALL')}标签,未找到{ct('TOOL_CALL')}标签,调用格式错误,请检查工具调用格式。\n{tool_call_help}{long_hint}"
|
|
812
|
+
|
|
813
|
+
# 如果提供了agent且long_hint为空,尝试使用大模型修复
|
|
814
|
+
if agent is not None and not long_hint:
|
|
815
|
+
fixed_content = ToolRegistry._try_llm_fix(content, agent, error_msg)
|
|
816
|
+
if fixed_content:
|
|
817
|
+
# 递归调用自身,尝试解析修复后的内容
|
|
818
|
+
return ToolRegistry._extract_tool_calls(fixed_content, None)
|
|
819
|
+
|
|
820
|
+
# 如果大模型修复失败或未提供agent或long_hint不为空,返回错误
|
|
668
821
|
return (
|
|
669
822
|
{},
|
|
670
|
-
|
|
823
|
+
error_msg,
|
|
671
824
|
False,
|
|
672
825
|
)
|
|
826
|
+
|
|
673
827
|
ret = []
|
|
674
828
|
for item in data:
|
|
675
829
|
try:
|
|
676
|
-
|
|
830
|
+
# 清理可能存在的额外标记
|
|
831
|
+
cleaned_item = ToolRegistry._clean_extra_markers(item)
|
|
832
|
+
msg = json_loads(cleaned_item)
|
|
677
833
|
except Exception as e:
|
|
834
|
+
long_hint = ToolRegistry._get_long_response_hint(content)
|
|
835
|
+
error_msg = f"""Jsonnet 解析失败:{e}
|
|
836
|
+
|
|
837
|
+
提示:Jsonnet支持双引号/单引号、尾随逗号、注释。多行字符串推荐使用 ||| 或 ``` 分隔符包裹,直接换行无需转义,支持保留缩进。
|
|
838
|
+
|
|
839
|
+
{tool_call_help}{long_hint}"""
|
|
840
|
+
|
|
841
|
+
# 如果提供了agent且long_hint为空,尝试使用大模型修复
|
|
842
|
+
if agent is not None and not long_hint:
|
|
843
|
+
fixed_content = ToolRegistry._try_llm_fix(content, agent, error_msg)
|
|
844
|
+
if fixed_content:
|
|
845
|
+
# 递归调用自身,尝试解析修复后的内容
|
|
846
|
+
return ToolRegistry._extract_tool_calls(fixed_content, None)
|
|
847
|
+
|
|
848
|
+
# 如果大模型修复失败或未提供agent或long_hint不为空,返回错误
|
|
678
849
|
return (
|
|
679
850
|
{},
|
|
680
|
-
|
|
681
|
-
{e}
|
|
682
|
-
|
|
683
|
-
{tool_call_help}""",
|
|
851
|
+
error_msg,
|
|
684
852
|
False,
|
|
685
853
|
)
|
|
686
854
|
|
|
687
855
|
if "name" in msg and "arguments" in msg and "want" in msg:
|
|
688
856
|
ret.append(msg)
|
|
689
857
|
else:
|
|
858
|
+
long_hint = ToolRegistry._get_long_response_hint(content)
|
|
859
|
+
error_msg = f"""工具调用格式错误,请检查工具调用格式(缺少name、arguments、want字段)。
|
|
860
|
+
|
|
861
|
+
{tool_call_help}{long_hint}"""
|
|
862
|
+
|
|
863
|
+
# 如果提供了agent且long_hint为空,尝试使用大模型修复
|
|
864
|
+
if agent is not None and not long_hint:
|
|
865
|
+
fixed_content = ToolRegistry._try_llm_fix(content, agent, error_msg)
|
|
866
|
+
if fixed_content:
|
|
867
|
+
# 递归调用自身,尝试解析修复后的内容
|
|
868
|
+
return ToolRegistry._extract_tool_calls(fixed_content, None)
|
|
869
|
+
|
|
870
|
+
# 如果大模型修复失败或未提供agent或long_hint不为空,返回错误
|
|
690
871
|
return (
|
|
691
872
|
{},
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
{tool_call_help}""",
|
|
873
|
+
error_msg,
|
|
695
874
|
False,
|
|
696
875
|
)
|
|
697
876
|
if len(ret) > 1:
|
|
@@ -715,9 +894,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
715
894
|
func: 工具执行函数
|
|
716
895
|
"""
|
|
717
896
|
if name in self.tools:
|
|
718
|
-
|
|
719
|
-
f"警告: 工具 '{name}' 已存在,将被覆盖", OutputType.WARNING
|
|
720
|
-
)
|
|
897
|
+
print(f"⚠️ 警告: 工具 '{name}' 已存在,将被覆盖")
|
|
721
898
|
self.tools[name] = Tool(name, description, parameters, func, protocol_version)
|
|
722
899
|
|
|
723
900
|
def get_tool(self, name: str) -> Optional[Tool]:
|
|
@@ -739,6 +916,18 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
739
916
|
"""
|
|
740
917
|
return [tool.to_dict() for tool in self.tools.values()]
|
|
741
918
|
|
|
919
|
+
def get_custom_tools(self) -> List[Dict[str, Any]]:
|
|
920
|
+
"""获取用户自定义工具(非内置工具)
|
|
921
|
+
|
|
922
|
+
返回:
|
|
923
|
+
List[Dict[str, Any]]: 包含用户自定义工具信息的列表
|
|
924
|
+
"""
|
|
925
|
+
return [
|
|
926
|
+
tool.to_dict()
|
|
927
|
+
for tool in self.tools.values()
|
|
928
|
+
if tool.name not in self._builtin_tool_names
|
|
929
|
+
]
|
|
930
|
+
|
|
742
931
|
def execute_tool(
|
|
743
932
|
self, name: str, arguments: Dict[str, Any], agent: Optional[Any] = None
|
|
744
933
|
) -> Dict[str, Any]:
|
|
@@ -810,6 +999,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
810
999
|
"""
|
|
811
1000
|
if len(output.splitlines()) > 60:
|
|
812
1001
|
lines = output.splitlines()
|
|
1002
|
+
print("⚠️ 输出太长,截取前后30行")
|
|
813
1003
|
return "\n".join(
|
|
814
1004
|
lines[:30] + ["\n...内容太长,已截取前后30行...\n"] + lines[-30:]
|
|
815
1005
|
)
|
|
@@ -828,15 +1018,25 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
828
1018
|
# 如果args是字符串,尝试解析为JSON
|
|
829
1019
|
if isinstance(args, str):
|
|
830
1020
|
try:
|
|
831
|
-
args =
|
|
832
|
-
except
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1021
|
+
args = json_loads(args)
|
|
1022
|
+
except Exception:
|
|
1023
|
+
# 返回错误并附带完整的工具使用提示,指导下一次正确调用
|
|
1024
|
+
try:
|
|
1025
|
+
usage_prompt = agent_instance.get_tool_usage_prompt()
|
|
1026
|
+
except Exception:
|
|
1027
|
+
usage_prompt = tool_call_help
|
|
1028
|
+
print("❌ 工具参数格式无效")
|
|
1029
|
+
return f"工具参数格式无效: {name}。arguments 应为可解析的 Jsonnet 或对象,请按工具调用格式提供。\n提示:对于多行字符串参数,推荐使用 ||| 或 ``` 分隔符包裹,直接换行无需转义,支持保留缩进。\n\n{usage_prompt}"
|
|
837
1030
|
|
|
1031
|
+
print(f"🛠️ 执行工具调用 {name}")
|
|
838
1032
|
# 执行工具调用(根据工具实现的协议版本,由系统在内部决定agent的传递方式)
|
|
839
1033
|
result = self.execute_tool(name, args, agent)
|
|
1034
|
+
|
|
1035
|
+
# 打印执行状态
|
|
1036
|
+
if result.get("success", False):
|
|
1037
|
+
print(f"✅ 执行工具调用 {name} 成功")
|
|
1038
|
+
else:
|
|
1039
|
+
print(f"❌ 执行工具调用 {name} 失败")
|
|
840
1040
|
|
|
841
1041
|
# 记录本轮实际执行的工具,供上层逻辑(如记忆保存判定)使用
|
|
842
1042
|
try:
|
|
@@ -853,6 +1053,15 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
853
1053
|
except Exception:
|
|
854
1054
|
pass
|
|
855
1055
|
|
|
1056
|
+
# 如果执行失败,附带工具使用提示返回
|
|
1057
|
+
if not result.get("success", False):
|
|
1058
|
+
try:
|
|
1059
|
+
usage_prompt = agent_instance.get_tool_usage_prompt()
|
|
1060
|
+
except Exception:
|
|
1061
|
+
usage_prompt = tool_call_help
|
|
1062
|
+
err_output = self._format_tool_output(result.get("stdout", ""), result.get("stderr", ""))
|
|
1063
|
+
return f"{err_output}\n\n{usage_prompt}"
|
|
1064
|
+
|
|
856
1065
|
# 格式化输出
|
|
857
1066
|
output = self._format_tool_output(
|
|
858
1067
|
result["stdout"], result.get("stderr", "")
|
|
@@ -860,9 +1069,11 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
860
1069
|
|
|
861
1070
|
# 检查内容是否过大
|
|
862
1071
|
model_group = None
|
|
1072
|
+
platform = None
|
|
863
1073
|
if agent_instance.model:
|
|
864
1074
|
model_group = agent_instance.model.model_group
|
|
865
|
-
|
|
1075
|
+
platform = agent_instance.model
|
|
1076
|
+
is_large_content = is_context_overflow(output, model_group, platform)
|
|
866
1077
|
|
|
867
1078
|
if is_large_content:
|
|
868
1079
|
# 创建临时文件
|
|
@@ -895,7 +1106,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
895
1106
|
</content>
|
|
896
1107
|
|
|
897
1108
|
上传的文件是以下工具执行结果:
|
|
898
|
-
{
|
|
1109
|
+
{json.dumps({"name":name, "arguments":args, "want":want}, ensure_ascii=False, indent=2)}
|
|
899
1110
|
|
|
900
1111
|
请根据以上信息,继续完成任务。
|
|
901
1112
|
"""
|
|
@@ -912,5 +1123,21 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
912
1123
|
return output
|
|
913
1124
|
|
|
914
1125
|
except Exception as e:
|
|
915
|
-
|
|
916
|
-
|
|
1126
|
+
# 尝试获取工具名称(如果已定义)
|
|
1127
|
+
tool_name = ""
|
|
1128
|
+
try:
|
|
1129
|
+
if 'name' in locals():
|
|
1130
|
+
tool_name = name
|
|
1131
|
+
except Exception:
|
|
1132
|
+
pass
|
|
1133
|
+
if tool_name:
|
|
1134
|
+
print(f"❌ 执行工具调用 {tool_name} 失败:{str(e)}")
|
|
1135
|
+
else:
|
|
1136
|
+
print(f"❌ 工具调用失败:{str(e)}")
|
|
1137
|
+
try:
|
|
1138
|
+
from jarvis.jarvis_agent import Agent # 延迟导入避免循环依赖
|
|
1139
|
+
agent_instance_for_prompt: Agent = agent # type: ignore
|
|
1140
|
+
usage_prompt = agent_instance_for_prompt.get_tool_usage_prompt()
|
|
1141
|
+
except Exception:
|
|
1142
|
+
usage_prompt = tool_call_help
|
|
1143
|
+
return f"工具调用失败: {str(e)}\n\n{usage_prompt}"
|