jarvis-ai-assistant 0.7.16__py3-none-any.whl → 1.0.2__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.
Files changed (279) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +567 -222
  3. jarvis/jarvis_agent/agent_manager.py +19 -12
  4. jarvis/jarvis_agent/builtin_input_handler.py +79 -11
  5. jarvis/jarvis_agent/config_editor.py +7 -2
  6. jarvis/jarvis_agent/event_bus.py +24 -13
  7. jarvis/jarvis_agent/events.py +19 -1
  8. jarvis/jarvis_agent/file_context_handler.py +67 -64
  9. jarvis/jarvis_agent/file_methodology_manager.py +38 -24
  10. jarvis/jarvis_agent/jarvis.py +186 -114
  11. jarvis/jarvis_agent/language_extractors/__init__.py +8 -1
  12. jarvis/jarvis_agent/language_extractors/c_extractor.py +7 -4
  13. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +9 -4
  14. jarvis/jarvis_agent/language_extractors/go_extractor.py +7 -4
  15. jarvis/jarvis_agent/language_extractors/java_extractor.py +27 -20
  16. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +22 -17
  17. jarvis/jarvis_agent/language_extractors/python_extractor.py +7 -4
  18. jarvis/jarvis_agent/language_extractors/rust_extractor.py +7 -4
  19. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +22 -17
  20. jarvis/jarvis_agent/language_support_info.py +250 -219
  21. jarvis/jarvis_agent/main.py +19 -23
  22. jarvis/jarvis_agent/memory_manager.py +9 -6
  23. jarvis/jarvis_agent/methodology_share_manager.py +21 -15
  24. jarvis/jarvis_agent/output_handler.py +4 -2
  25. jarvis/jarvis_agent/prompt_builder.py +7 -6
  26. jarvis/jarvis_agent/prompt_manager.py +113 -8
  27. jarvis/jarvis_agent/prompts.py +317 -85
  28. jarvis/jarvis_agent/protocols.py +5 -2
  29. jarvis/jarvis_agent/run_loop.py +192 -32
  30. jarvis/jarvis_agent/session_manager.py +7 -3
  31. jarvis/jarvis_agent/share_manager.py +23 -13
  32. jarvis/jarvis_agent/shell_input_handler.py +12 -8
  33. jarvis/jarvis_agent/stdio_redirect.py +25 -26
  34. jarvis/jarvis_agent/task_analyzer.py +29 -23
  35. jarvis/jarvis_agent/task_list.py +869 -0
  36. jarvis/jarvis_agent/task_manager.py +26 -23
  37. jarvis/jarvis_agent/tool_executor.py +6 -5
  38. jarvis/jarvis_agent/tool_share_manager.py +24 -14
  39. jarvis/jarvis_agent/user_interaction.py +3 -3
  40. jarvis/jarvis_agent/utils.py +9 -1
  41. jarvis/jarvis_agent/web_bridge.py +37 -17
  42. jarvis/jarvis_agent/web_output_sink.py +5 -2
  43. jarvis/jarvis_agent/web_server.py +165 -36
  44. jarvis/jarvis_c2rust/__init__.py +1 -1
  45. jarvis/jarvis_c2rust/cli.py +260 -141
  46. jarvis/jarvis_c2rust/collector.py +37 -18
  47. jarvis/jarvis_c2rust/constants.py +60 -0
  48. jarvis/jarvis_c2rust/library_replacer.py +242 -1010
  49. jarvis/jarvis_c2rust/library_replacer_checkpoint.py +133 -0
  50. jarvis/jarvis_c2rust/library_replacer_llm.py +287 -0
  51. jarvis/jarvis_c2rust/library_replacer_loader.py +191 -0
  52. jarvis/jarvis_c2rust/library_replacer_output.py +134 -0
  53. jarvis/jarvis_c2rust/library_replacer_prompts.py +124 -0
  54. jarvis/jarvis_c2rust/library_replacer_utils.py +188 -0
  55. jarvis/jarvis_c2rust/llm_module_agent.py +98 -1044
  56. jarvis/jarvis_c2rust/llm_module_agent_apply.py +170 -0
  57. jarvis/jarvis_c2rust/llm_module_agent_executor.py +288 -0
  58. jarvis/jarvis_c2rust/llm_module_agent_loader.py +170 -0
  59. jarvis/jarvis_c2rust/llm_module_agent_prompts.py +268 -0
  60. jarvis/jarvis_c2rust/llm_module_agent_types.py +57 -0
  61. jarvis/jarvis_c2rust/llm_module_agent_utils.py +150 -0
  62. jarvis/jarvis_c2rust/llm_module_agent_validator.py +119 -0
  63. jarvis/jarvis_c2rust/loaders.py +28 -10
  64. jarvis/jarvis_c2rust/models.py +5 -2
  65. jarvis/jarvis_c2rust/optimizer.py +192 -1974
  66. jarvis/jarvis_c2rust/optimizer_build_fix.py +286 -0
  67. jarvis/jarvis_c2rust/optimizer_clippy.py +766 -0
  68. jarvis/jarvis_c2rust/optimizer_config.py +49 -0
  69. jarvis/jarvis_c2rust/optimizer_docs.py +183 -0
  70. jarvis/jarvis_c2rust/optimizer_options.py +48 -0
  71. jarvis/jarvis_c2rust/optimizer_progress.py +469 -0
  72. jarvis/jarvis_c2rust/optimizer_report.py +52 -0
  73. jarvis/jarvis_c2rust/optimizer_unsafe.py +309 -0
  74. jarvis/jarvis_c2rust/optimizer_utils.py +469 -0
  75. jarvis/jarvis_c2rust/optimizer_visibility.py +185 -0
  76. jarvis/jarvis_c2rust/scanner.py +229 -166
  77. jarvis/jarvis_c2rust/transpiler.py +531 -2732
  78. jarvis/jarvis_c2rust/transpiler_agents.py +503 -0
  79. jarvis/jarvis_c2rust/transpiler_build.py +1294 -0
  80. jarvis/jarvis_c2rust/transpiler_codegen.py +204 -0
  81. jarvis/jarvis_c2rust/transpiler_compile.py +146 -0
  82. jarvis/jarvis_c2rust/transpiler_config.py +178 -0
  83. jarvis/jarvis_c2rust/transpiler_context.py +122 -0
  84. jarvis/jarvis_c2rust/transpiler_executor.py +516 -0
  85. jarvis/jarvis_c2rust/transpiler_generation.py +278 -0
  86. jarvis/jarvis_c2rust/transpiler_git.py +163 -0
  87. jarvis/jarvis_c2rust/transpiler_mod_utils.py +225 -0
  88. jarvis/jarvis_c2rust/transpiler_modules.py +336 -0
  89. jarvis/jarvis_c2rust/transpiler_planning.py +394 -0
  90. jarvis/jarvis_c2rust/transpiler_review.py +1196 -0
  91. jarvis/jarvis_c2rust/transpiler_symbols.py +176 -0
  92. jarvis/jarvis_c2rust/utils.py +269 -79
  93. jarvis/jarvis_code_agent/after_change.py +233 -0
  94. jarvis/jarvis_code_agent/build_validation_config.py +37 -30
  95. jarvis/jarvis_code_agent/builtin_rules.py +68 -0
  96. jarvis/jarvis_code_agent/code_agent.py +976 -1517
  97. jarvis/jarvis_code_agent/code_agent_build.py +227 -0
  98. jarvis/jarvis_code_agent/code_agent_diff.py +246 -0
  99. jarvis/jarvis_code_agent/code_agent_git.py +525 -0
  100. jarvis/jarvis_code_agent/code_agent_impact.py +177 -0
  101. jarvis/jarvis_code_agent/code_agent_lint.py +283 -0
  102. jarvis/jarvis_code_agent/code_agent_llm.py +159 -0
  103. jarvis/jarvis_code_agent/code_agent_postprocess.py +105 -0
  104. jarvis/jarvis_code_agent/code_agent_prompts.py +46 -0
  105. jarvis/jarvis_code_agent/code_agent_rules.py +305 -0
  106. jarvis/jarvis_code_agent/code_analyzer/__init__.py +52 -48
  107. jarvis/jarvis_code_agent/code_analyzer/base_language.py +12 -10
  108. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +12 -11
  109. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +16 -12
  110. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +26 -17
  111. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +558 -104
  112. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +27 -16
  113. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +22 -18
  114. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +21 -16
  115. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +20 -16
  116. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +27 -16
  117. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +47 -23
  118. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +71 -37
  119. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +162 -35
  120. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +111 -57
  121. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +18 -12
  122. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +185 -183
  123. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +2 -1
  124. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +24 -15
  125. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +227 -141
  126. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +321 -247
  127. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +37 -29
  128. jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -13
  129. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +15 -9
  130. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +75 -45
  131. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +87 -52
  132. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +84 -51
  133. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +94 -64
  134. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +109 -71
  135. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +97 -63
  136. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +103 -69
  137. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +271 -268
  138. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +76 -64
  139. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +92 -19
  140. jarvis/jarvis_code_agent/diff_visualizer.py +998 -0
  141. jarvis/jarvis_code_agent/lint.py +223 -524
  142. jarvis/jarvis_code_agent/rule_share_manager.py +158 -0
  143. jarvis/jarvis_code_agent/rules/clean_code.md +144 -0
  144. jarvis/jarvis_code_agent/rules/code_review.md +115 -0
  145. jarvis/jarvis_code_agent/rules/documentation.md +165 -0
  146. jarvis/jarvis_code_agent/rules/generate_rules.md +52 -0
  147. jarvis/jarvis_code_agent/rules/performance.md +158 -0
  148. jarvis/jarvis_code_agent/rules/refactoring.md +139 -0
  149. jarvis/jarvis_code_agent/rules/security.md +160 -0
  150. jarvis/jarvis_code_agent/rules/tdd.md +78 -0
  151. jarvis/jarvis_code_agent/test_rules/cpp_test.md +118 -0
  152. jarvis/jarvis_code_agent/test_rules/go_test.md +98 -0
  153. jarvis/jarvis_code_agent/test_rules/java_test.md +99 -0
  154. jarvis/jarvis_code_agent/test_rules/javascript_test.md +113 -0
  155. jarvis/jarvis_code_agent/test_rules/php_test.md +117 -0
  156. jarvis/jarvis_code_agent/test_rules/python_test.md +91 -0
  157. jarvis/jarvis_code_agent/test_rules/ruby_test.md +102 -0
  158. jarvis/jarvis_code_agent/test_rules/rust_test.md +86 -0
  159. jarvis/jarvis_code_agent/utils.py +36 -26
  160. jarvis/jarvis_code_analysis/checklists/loader.py +21 -21
  161. jarvis/jarvis_code_analysis/code_review.py +64 -33
  162. jarvis/jarvis_data/config_schema.json +285 -192
  163. jarvis/jarvis_git_squash/main.py +8 -6
  164. jarvis/jarvis_git_utils/git_commiter.py +53 -76
  165. jarvis/jarvis_mcp/__init__.py +5 -2
  166. jarvis/jarvis_mcp/sse_mcp_client.py +40 -30
  167. jarvis/jarvis_mcp/stdio_mcp_client.py +27 -19
  168. jarvis/jarvis_mcp/streamable_mcp_client.py +35 -26
  169. jarvis/jarvis_memory_organizer/memory_organizer.py +78 -55
  170. jarvis/jarvis_methodology/main.py +48 -39
  171. jarvis/jarvis_multi_agent/__init__.py +56 -23
  172. jarvis/jarvis_multi_agent/main.py +15 -18
  173. jarvis/jarvis_platform/base.py +179 -111
  174. jarvis/jarvis_platform/human.py +27 -16
  175. jarvis/jarvis_platform/kimi.py +52 -45
  176. jarvis/jarvis_platform/openai.py +101 -40
  177. jarvis/jarvis_platform/registry.py +51 -33
  178. jarvis/jarvis_platform/tongyi.py +68 -38
  179. jarvis/jarvis_platform/yuanbao.py +59 -43
  180. jarvis/jarvis_platform_manager/main.py +68 -76
  181. jarvis/jarvis_platform_manager/service.py +24 -14
  182. jarvis/jarvis_rag/README_CONFIG.md +314 -0
  183. jarvis/jarvis_rag/README_DYNAMIC_LOADING.md +311 -0
  184. jarvis/jarvis_rag/README_ONLINE_MODELS.md +230 -0
  185. jarvis/jarvis_rag/__init__.py +57 -4
  186. jarvis/jarvis_rag/cache.py +3 -1
  187. jarvis/jarvis_rag/cli.py +48 -68
  188. jarvis/jarvis_rag/embedding_interface.py +39 -0
  189. jarvis/jarvis_rag/embedding_manager.py +7 -230
  190. jarvis/jarvis_rag/embeddings/__init__.py +41 -0
  191. jarvis/jarvis_rag/embeddings/base.py +114 -0
  192. jarvis/jarvis_rag/embeddings/cohere.py +66 -0
  193. jarvis/jarvis_rag/embeddings/edgefn.py +117 -0
  194. jarvis/jarvis_rag/embeddings/local.py +260 -0
  195. jarvis/jarvis_rag/embeddings/openai.py +62 -0
  196. jarvis/jarvis_rag/embeddings/registry.py +293 -0
  197. jarvis/jarvis_rag/llm_interface.py +8 -6
  198. jarvis/jarvis_rag/query_rewriter.py +8 -9
  199. jarvis/jarvis_rag/rag_pipeline.py +61 -52
  200. jarvis/jarvis_rag/reranker.py +7 -75
  201. jarvis/jarvis_rag/reranker_interface.py +32 -0
  202. jarvis/jarvis_rag/rerankers/__init__.py +41 -0
  203. jarvis/jarvis_rag/rerankers/base.py +109 -0
  204. jarvis/jarvis_rag/rerankers/cohere.py +67 -0
  205. jarvis/jarvis_rag/rerankers/edgefn.py +140 -0
  206. jarvis/jarvis_rag/rerankers/jina.py +79 -0
  207. jarvis/jarvis_rag/rerankers/local.py +89 -0
  208. jarvis/jarvis_rag/rerankers/registry.py +293 -0
  209. jarvis/jarvis_rag/retriever.py +58 -43
  210. jarvis/jarvis_sec/__init__.py +66 -141
  211. jarvis/jarvis_sec/agents.py +21 -17
  212. jarvis/jarvis_sec/analysis.py +80 -33
  213. jarvis/jarvis_sec/checkers/__init__.py +7 -13
  214. jarvis/jarvis_sec/checkers/c_checker.py +356 -164
  215. jarvis/jarvis_sec/checkers/rust_checker.py +47 -29
  216. jarvis/jarvis_sec/cli.py +43 -21
  217. jarvis/jarvis_sec/clustering.py +430 -272
  218. jarvis/jarvis_sec/file_manager.py +99 -55
  219. jarvis/jarvis_sec/parsers.py +9 -6
  220. jarvis/jarvis_sec/prompts.py +4 -3
  221. jarvis/jarvis_sec/report.py +44 -22
  222. jarvis/jarvis_sec/review.py +180 -107
  223. jarvis/jarvis_sec/status.py +50 -41
  224. jarvis/jarvis_sec/types.py +3 -0
  225. jarvis/jarvis_sec/utils.py +160 -83
  226. jarvis/jarvis_sec/verification.py +411 -181
  227. jarvis/jarvis_sec/workflow.py +132 -21
  228. jarvis/jarvis_smart_shell/main.py +28 -41
  229. jarvis/jarvis_stats/cli.py +14 -12
  230. jarvis/jarvis_stats/stats.py +28 -19
  231. jarvis/jarvis_stats/storage.py +14 -8
  232. jarvis/jarvis_stats/visualizer.py +12 -7
  233. jarvis/jarvis_tools/base.py +5 -2
  234. jarvis/jarvis_tools/clear_memory.py +13 -9
  235. jarvis/jarvis_tools/cli/main.py +23 -18
  236. jarvis/jarvis_tools/edit_file.py +572 -873
  237. jarvis/jarvis_tools/execute_script.py +10 -7
  238. jarvis/jarvis_tools/file_analyzer.py +7 -8
  239. jarvis/jarvis_tools/meta_agent.py +287 -0
  240. jarvis/jarvis_tools/methodology.py +5 -3
  241. jarvis/jarvis_tools/read_code.py +305 -1438
  242. jarvis/jarvis_tools/read_symbols.py +50 -17
  243. jarvis/jarvis_tools/read_webpage.py +19 -18
  244. jarvis/jarvis_tools/registry.py +435 -156
  245. jarvis/jarvis_tools/retrieve_memory.py +16 -11
  246. jarvis/jarvis_tools/save_memory.py +8 -6
  247. jarvis/jarvis_tools/search_web.py +31 -31
  248. jarvis/jarvis_tools/sub_agent.py +32 -28
  249. jarvis/jarvis_tools/sub_code_agent.py +44 -60
  250. jarvis/jarvis_tools/task_list_manager.py +1811 -0
  251. jarvis/jarvis_tools/virtual_tty.py +29 -19
  252. jarvis/jarvis_utils/__init__.py +4 -0
  253. jarvis/jarvis_utils/builtin_replace_map.py +2 -1
  254. jarvis/jarvis_utils/clipboard.py +9 -8
  255. jarvis/jarvis_utils/collections.py +331 -0
  256. jarvis/jarvis_utils/config.py +699 -194
  257. jarvis/jarvis_utils/dialogue_recorder.py +294 -0
  258. jarvis/jarvis_utils/embedding.py +6 -3
  259. jarvis/jarvis_utils/file_processors.py +7 -1
  260. jarvis/jarvis_utils/fzf.py +9 -3
  261. jarvis/jarvis_utils/git_utils.py +71 -42
  262. jarvis/jarvis_utils/globals.py +116 -32
  263. jarvis/jarvis_utils/http.py +6 -2
  264. jarvis/jarvis_utils/input.py +318 -83
  265. jarvis/jarvis_utils/jsonnet_compat.py +119 -104
  266. jarvis/jarvis_utils/methodology.py +37 -28
  267. jarvis/jarvis_utils/output.py +201 -44
  268. jarvis/jarvis_utils/utils.py +986 -628
  269. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/METADATA +49 -33
  270. jarvis_ai_assistant-1.0.2.dist-info/RECORD +304 -0
  271. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +0 -556
  272. jarvis/jarvis_tools/generate_new_tool.py +0 -205
  273. jarvis/jarvis_tools/lsp_client.py +0 -1552
  274. jarvis/jarvis_tools/rewrite_file.py +0 -105
  275. jarvis_ai_assistant-0.7.16.dist-info/RECORD +0 -218
  276. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
  277. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
  278. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
  279. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/top_level.txt +0 -0
@@ -1,23 +1,36 @@
1
- # -*- coding: utf-8 -*-
2
- from jarvis.jarvis_utils.jsonnet_compat import loads as json_loads
3
1
  import json
4
2
  import os
5
3
  import re
6
4
  import sys
7
5
  import tempfile
8
6
  from pathlib import Path
9
- from typing import Any, Callable, Dict, List, Optional, Protocol, Tuple
7
+ from typing import Any
8
+ from typing import Callable
9
+ from typing import Dict
10
+ from typing import List
11
+ from typing import Optional
12
+ from typing import Protocol
13
+ from typing import Tuple
14
+ from typing import cast
10
15
 
11
- import yaml # type: ignore[import-untyped]
16
+ import yaml
12
17
 
13
18
  from jarvis.jarvis_mcp import McpClient
14
19
  from jarvis.jarvis_mcp.sse_mcp_client import SSEMcpClient
15
20
  from jarvis.jarvis_mcp.stdio_mcp_client import StdioMcpClient
16
21
  from jarvis.jarvis_mcp.streamable_mcp_client import StreamableMcpClient
17
22
  from jarvis.jarvis_tools.base import Tool
18
- from jarvis.jarvis_utils.config import get_data_dir, get_tool_load_dirs
19
- from jarvis.jarvis_utils.tag import ct, ot
20
- from jarvis.jarvis_utils.utils import is_context_overflow, daily_check_git_updates
23
+ from jarvis.jarvis_utils.config import get_data_dir
24
+ from jarvis.jarvis_utils.config import get_tool_load_dirs
25
+ from jarvis.jarvis_utils.globals import get_global_model_group
26
+
27
+ # -*- coding: utf-8 -*-
28
+ from jarvis.jarvis_utils.jsonnet_compat import loads as json_loads
29
+ from jarvis.jarvis_utils.output import PrettyOutput
30
+ from jarvis.jarvis_utils.tag import ct
31
+ from jarvis.jarvis_utils.tag import ot
32
+ from jarvis.jarvis_utils.utils import daily_check_git_updates
33
+ from jarvis.jarvis_utils.utils import is_context_overflow
21
34
 
22
35
  _multiline_example = """ {
23
36
  "multiline_str": |||
@@ -39,8 +52,9 @@ _multiline_example = """ {
39
52
  }"""
40
53
 
41
54
  tool_call_help = f"""
42
- <tool_system_guide>
43
- 工具调用格式(Jsonnet):
55
+ ## 工具调用指南(Markdown)
56
+
57
+ **工具调用格式(Jsonnet)**
44
58
  {ot("TOOL_CALL")}
45
59
  {{
46
60
  "want": "想要从执行结果中获取到的信息",
@@ -52,7 +66,7 @@ tool_call_help = f"""
52
66
  }}
53
67
  {ct("TOOL_CALL")}
54
68
 
55
- Jsonnet格式特性:
69
+ **Jsonnet格式特性**
56
70
  - 字符串引号:可使用双引号或单引号
57
71
  - 多行字符串:推荐使用 ||| 或 ``` 分隔符包裹多行字符串,直接换行无需转义,支持保留缩进
58
72
  示例:
@@ -60,15 +74,18 @@ Jsonnet格式特性:
60
74
  - 尾随逗号:对象/数组最后一个元素后可添加逗号
61
75
  - 注释:支持 // 单行或 /* */ 多行注释
62
76
 
63
- 关键规则:
77
+ **关键规则**
64
78
  1. 每次只使用一个工具,等待结果后再进行下一步
65
79
  2. {ot("TOOL_CALL")} 和 {ct("TOOL_CALL")} 必须出现在行首
66
80
  3. 多行字符串参数推荐使用 ||| 或 ``` 分隔符包裹,直接换行无需转义,支持保留缩进
67
81
  4. 等待执行结果,不要假设或创建虚假响应
68
82
  5. 信息不足时询问用户,不要在没有完整信息的情况下继续
69
83
 
70
- 常见错误:同时调用多个工具、假设工具结果、Jsonnet格式错误、标签未出现在行首
71
- </tool_system_guide>
84
+ **常见错误**
85
+ - 同时调用多个工具
86
+ - 假设工具结果
87
+ - Jsonnet格式错误
88
+ - 缺少行首的开始/结束标签
72
89
  """
73
90
 
74
91
 
@@ -88,25 +105,22 @@ class ToolRegistry(OutputHandlerProtocol):
88
105
 
89
106
  def can_handle(self, response: str) -> bool:
90
107
  # 仅当 {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
108
+ has_tool_call = (
109
+ re.search(rf"(?mi){re.escape(ot('TOOL_CALL'))}", response) is not None
110
+ )
94
111
  return has_tool_call
95
112
 
96
113
  def prompt(self) -> str:
97
114
  """加载工具"""
98
115
  tools = self.get_all_tools()
99
116
  if tools:
100
- tools_prompt = "<tools_section>\n"
101
- tools_prompt += " <header>## 可用工具:</header>\n"
102
- tools_prompt += " <tools_list>\n"
117
+ tools_prompt = "## 可用工具\n"
103
118
  for tool in tools:
104
119
  try:
105
- tools_prompt += " <tool>\n"
106
- tools_prompt += f" <name>名称: {tool['name']}</name>\n"
107
- tools_prompt += f" <description>描述: {tool['description']}</description>\n"
108
- tools_prompt += " <parameters>\n"
109
- tools_prompt += " <json>|\n"
120
+ tools_prompt += f"- **名称**: {tool['name']}\n"
121
+ tools_prompt += f" - 描述: {tool['description']}\n"
122
+ tools_prompt += " - 参数:\n"
123
+ tools_prompt += "```json\n"
110
124
 
111
125
  # 生成格式化的JSON参数
112
126
  json_params = json.dumps(
@@ -118,18 +132,16 @@ class ToolRegistry(OutputHandlerProtocol):
118
132
 
119
133
  # 添加缩进并移除尾部空格
120
134
  for line in json_params.split("\n"):
121
- tools_prompt += f" {line.rstrip()}\n"
135
+ tools_prompt += f"{line.rstrip()}\n"
122
136
 
123
- tools_prompt += " </json>\n"
124
- tools_prompt += " </parameters>\n"
125
- tools_prompt += " </tool>\n"
137
+ tools_prompt += "```\n"
126
138
 
127
139
  except Exception as e:
128
- print(f"❌ 工具 {tool['name']} 参数序列化失败: {str(e)}")
140
+ PrettyOutput.auto_print(
141
+ f"❌ 工具 {tool['name']} 参数序列化失败: {str(e)}"
142
+ )
129
143
  continue
130
144
 
131
- tools_prompt += " </tools_list>\n"
132
- tools_prompt += "</tools_section>\n"
133
145
  tools_prompt += tool_call_help.rstrip() # 移除帮助文本尾部空格
134
146
  return tools_prompt
135
147
  return ""
@@ -137,13 +149,16 @@ class ToolRegistry(OutputHandlerProtocol):
137
149
  def handle(self, response: str, agent_: Any) -> Tuple[bool, Any]:
138
150
  try:
139
151
  # 传递agent给_extract_tool_calls,以便在解析失败时调用大模型修复
140
- tool_call, err_msg, auto_completed = self._extract_tool_calls(response, agent_)
152
+ tool_call, err_msg, auto_completed = self._extract_tool_calls(
153
+ response, agent_
154
+ )
141
155
  if err_msg:
142
156
  # 只要工具解析错误,追加工具使用帮助信息(相当于一次 <ToolUsage>)
143
157
  try:
144
158
  from jarvis.jarvis_agent import Agent
145
- agent: Agent = agent_
146
- tool_usage = agent.get_tool_usage_prompt()
159
+
160
+ agent_obj: Agent = agent_
161
+ tool_usage = agent_obj.get_tool_usage_prompt()
147
162
  return False, f"{err_msg}\n\n{tool_usage}"
148
163
  except Exception:
149
164
  # 兼容处理:无法获取Agent或ToolUsage时,至少返回工具系统帮助信息
@@ -154,13 +169,13 @@ class ToolRegistry(OutputHandlerProtocol):
154
169
  result = f"检测到工具调用缺少结束标签,已自动补全{ct('TOOL_CALL')}。请确保后续工具调用包含完整的开始和结束标签。\n\n{result}"
155
170
  return False, result
156
171
  except Exception as e:
157
- print(f"❌ 工具调用处理失败: {str(e)}")
172
+ PrettyOutput.auto_print(f"❌ 工具调用处理失败: {str(e)}")
158
173
  from jarvis.jarvis_agent import Agent
159
174
 
160
- agent: Agent = agent_
175
+ agent_final: Agent = agent_
161
176
  return (
162
177
  False,
163
- f"工具调用处理失败: {str(e)}\n\n{agent.get_tool_usage_prompt()}",
178
+ f"工具调用处理失败: {str(e)}\n\n{agent_final.get_tool_usage_prompt()}",
164
179
  )
165
180
 
166
181
  def __init__(self) -> None:
@@ -168,6 +183,8 @@ class ToolRegistry(OutputHandlerProtocol):
168
183
  self.tools: Dict[str, Tool] = {}
169
184
  # 记录内置工具名称,用于区分内置工具和用户自定义工具
170
185
  self._builtin_tool_names: set = set()
186
+ # 定义必选工具列表(这些工具将始终可用)
187
+ self._required_tools: List[str] = ["execute_script"]
171
188
  # 加载内置工具和外部工具
172
189
  self._load_builtin_tools()
173
190
  self._load_external_tools()
@@ -177,9 +194,10 @@ class ToolRegistry(OutputHandlerProtocol):
177
194
 
178
195
  def _get_tool_stats(self) -> Dict[str, int]:
179
196
  """从数据目录获取工具调用统计"""
180
- from jarvis.jarvis_stats.stats import StatsManager
181
197
  from datetime import datetime
182
198
 
199
+ from jarvis.jarvis_stats.stats import StatsManager
200
+
183
201
  # 获取所有工具的统计数据
184
202
  tool_stats = {}
185
203
  tools = self.get_all_tools()
@@ -221,7 +239,9 @@ class ToolRegistry(OutputHandlerProtocol):
221
239
  """
222
240
  missing_tools = [tool_name for tool_name in name if tool_name not in self.tools]
223
241
  if missing_tools:
224
- print(f"⚠️ 工具 {missing_tools} 不存在,可用的工具有: {', '.join(self.tools.keys())}")
242
+ PrettyOutput.auto_print(
243
+ f"⚠️ 工具 {missing_tools} 不存在,可用的工具有: {', '.join(self.tools.keys())}"
244
+ )
225
245
  self.tools = {
226
246
  tool_name: self.tools[tool_name]
227
247
  for tool_name in name
@@ -234,13 +254,29 @@ class ToolRegistry(OutputHandlerProtocol):
234
254
  参数:
235
255
  names: 要移除的工具名称列表
236
256
  """
257
+ # 过滤掉必选工具,确保它们不会被移除
258
+ filtered_names = [name for name in names if name not in self._required_tools]
259
+ if filtered_names != names:
260
+ removed_required = [name for name in names if name in self._required_tools]
261
+ PrettyOutput.auto_print(
262
+ f"⚠️ 警告: 无法移除必选工具: {', '.join(removed_required)}"
263
+ )
237
264
  self.tools = {
238
- name: tool for name, tool in self.tools.items() if name not in names
265
+ name: tool
266
+ for name, tool in self.tools.items()
267
+ if name not in filtered_names
239
268
  }
240
269
 
241
270
  def _apply_tool_config_filter(self) -> None:
242
271
  """应用工具配置组的过滤规则"""
243
- from jarvis.jarvis_utils.config import get_tool_use_list, get_tool_dont_use_list
272
+ from jarvis.jarvis_utils.config import get_tool_dont_use_list
273
+ from jarvis.jarvis_utils.config import get_tool_use_list
274
+
275
+ # 在过滤前保存必选工具的引用
276
+ required_tools_backup: Dict[str, Tool] = {}
277
+ for tool_name in self._required_tools:
278
+ if tool_name in self.tools:
279
+ required_tools_backup[tool_name] = self.tools[tool_name]
244
280
 
245
281
  use_list = get_tool_use_list()
246
282
  dont_use_list = get_tool_dont_use_list()
@@ -255,15 +291,27 @@ class ToolRegistry(OutputHandlerProtocol):
255
291
  else:
256
292
  missing.append(tool_name)
257
293
  if missing:
258
- print("⚠️ 警告: 配置的工具不存在: " + ", ".join(f"'{name}'" for name in missing))
294
+ PrettyOutput.auto_print(
295
+ "⚠️ 警告: 配置的工具不存在: "
296
+ + ", ".join(f"'{name}'" for name in missing)
297
+ )
259
298
  self.tools = filtered_tools
260
299
 
261
- # 如果配置了 dont_use 列表,排除列表中的工具
300
+ # 如果配置了 dont_use 列表,排除列表中的工具(但必选工具除外)
262
301
  if dont_use_list:
263
302
  for tool_name in dont_use_list:
264
- if tool_name in self.tools:
303
+ if tool_name in self.tools and tool_name not in self._required_tools:
265
304
  del self.tools[tool_name]
266
305
 
306
+ # 确保必选工具始终被包含(如果它们之前被加载过)
307
+ for tool_name in self._required_tools:
308
+ if tool_name in required_tools_backup:
309
+ self.tools[tool_name] = required_tools_backup[tool_name]
310
+ elif tool_name not in self.tools:
311
+ PrettyOutput.auto_print(
312
+ f"⚠️ 警告: 必选工具 '{tool_name}' 未加载,可能无法正常工作"
313
+ )
314
+
267
315
  def _load_mcp_tools(self) -> None:
268
316
  """加载MCP工具,优先从配置获取,其次从目录扫描"""
269
317
  from jarvis.jarvis_utils.config import get_mcp_config
@@ -281,7 +329,9 @@ class ToolRegistry(OutputHandlerProtocol):
281
329
  return
282
330
 
283
331
  # 添加警告信息
284
- print("⚠️ 警告: 从文件目录加载MCP工具的方式将在未来版本中废弃,请尽快迁移到JARVIS_MCP配置方式")
332
+ PrettyOutput.auto_print(
333
+ "⚠️ 警告: 从文件目录加载MCP工具的方式将在未来版本中废弃,请尽快迁移到mcp配置方式"
334
+ )
285
335
 
286
336
  # 遍历目录中的所有.yaml文件
287
337
  error_lines = []
@@ -293,7 +343,7 @@ class ToolRegistry(OutputHandlerProtocol):
293
343
  except Exception as e:
294
344
  error_lines.append(f"文件 {file_path} 加载失败: {str(e)}")
295
345
  if error_lines:
296
- print("⚠️ " + "\n⚠️ ".join(error_lines))
346
+ PrettyOutput.auto_print("⚠️ " + "\n⚠️ ".join(error_lines))
297
347
 
298
348
  def _load_builtin_tools(self) -> None:
299
349
  """从内置工具目录加载工具"""
@@ -306,7 +356,7 @@ class ToolRegistry(OutputHandlerProtocol):
306
356
  continue
307
357
 
308
358
  self.register_tool_by_file(str(file_path))
309
-
359
+
310
360
  # 记录当前已加载的工具名称为内置工具
311
361
  self._builtin_tool_names = set(self.tools.keys())
312
362
 
@@ -335,10 +385,11 @@ class ToolRegistry(OutputHandlerProtocol):
335
385
  import subprocess
336
386
 
337
387
  subprocess.run(
338
- ["git", "clone", central_repo, central_repo_path], check=True
388
+ ["git", "clone", central_repo, central_repo_path],
389
+ check=True,
339
390
  )
340
391
  except Exception as e:
341
- print(f"❌ 克隆中心工具仓库失败: {str(e)}")
392
+ PrettyOutput.auto_print(f"❌ 克隆中心工具仓库失败: {str(e)}")
342
393
 
343
394
  # --- 全局每日更新检查 ---
344
395
  daily_check_git_updates(tool_dirs, "tools")
@@ -367,12 +418,11 @@ class ToolRegistry(OutputHandlerProtocol):
367
418
  """
368
419
  try:
369
420
  if "type" not in config:
370
- print(f"⚠️ 配置{config.get('name', '')}缺少type字段")
421
+ PrettyOutput.auto_print(f"⚠️ 配置{config.get('name', '')}缺少type字段")
371
422
  return False
372
423
 
373
424
  # 检查enable标志
374
425
  if not config.get("enable", True):
375
-
376
426
  return False
377
427
 
378
428
  name = config.get("name", "mcp")
@@ -423,18 +473,24 @@ class ToolRegistry(OutputHandlerProtocol):
423
473
 
424
474
  if config["type"] == "stdio":
425
475
  if "command" not in config:
426
- print(f"⚠️ 配置{config.get('name', '')}缺少command字段")
476
+ PrettyOutput.auto_print(
477
+ f"⚠️ 配置{config.get('name', '')}缺少command字段"
478
+ )
427
479
  return False
428
480
  elif config["type"] == "sse":
429
481
  if "base_url" not in config:
430
- print(f"⚠️ 配置{config.get('name', '')}缺少base_url字段")
482
+ PrettyOutput.auto_print(
483
+ f"⚠️ 配置{config.get('name', '')}缺少base_url字段"
484
+ )
431
485
  return False
432
486
  elif config["type"] == "streamable":
433
487
  if "base_url" not in config:
434
- print(f"⚠️ 配置{config.get('name', '')}缺少base_url字段")
488
+ PrettyOutput.auto_print(
489
+ f"⚠️ 配置{config.get('name', '')}缺少base_url字段"
490
+ )
435
491
  return False
436
492
  else:
437
- print(f"⚠️ 不支持的MCP客户端类型: {config['type']}")
493
+ PrettyOutput.auto_print(f"⚠️ 不支持的MCP客户端类型: {config['type']}")
438
494
  return False
439
495
 
440
496
  # 创建MCP客户端
@@ -451,7 +507,9 @@ class ToolRegistry(OutputHandlerProtocol):
451
507
  # 获取工具信息
452
508
  tools = mcp_client.get_tool_list()
453
509
  if not tools:
454
- print(f"⚠️ 从配置{config.get('name', '')}获取工具列表失败")
510
+ PrettyOutput.auto_print(
511
+ f"⚠️ 从配置{config.get('name', '')}获取工具列表失败"
512
+ )
455
513
  return False
456
514
 
457
515
  # 注册每个工具
@@ -489,7 +547,9 @@ class ToolRegistry(OutputHandlerProtocol):
489
547
  return True
490
548
 
491
549
  except Exception as e:
492
- print(f"⚠️ MCP配置{config.get('name', '')}加载失败: {str(e)}")
550
+ PrettyOutput.auto_print(
551
+ f"⚠️ MCP配置{config.get('name', '')}加载失败: {str(e)}"
552
+ )
493
553
  return False
494
554
 
495
555
  def register_tool_by_file(self, file_path: str) -> bool:
@@ -504,7 +564,7 @@ class ToolRegistry(OutputHandlerProtocol):
504
564
  try:
505
565
  p_file_path = Path(file_path).resolve() # 获取绝对路径
506
566
  if not p_file_path.exists() or not p_file_path.is_file():
507
- print(f"❌ 文件不存在: {p_file_path}")
567
+ PrettyOutput.auto_print(f"❌ 文件不存在: {p_file_path}")
508
568
  return False
509
569
 
510
570
  # 临时将父目录添加到sys.path
@@ -559,37 +619,47 @@ class ToolRegistry(OutputHandlerProtocol):
559
619
  sys.path.remove(parent_dir)
560
620
 
561
621
  except Exception as e:
562
- print(f"❌ 从 {Path(file_path).name} 加载工具失败: {str(e)}")
622
+ PrettyOutput.auto_print(
623
+ f"❌ 从 {Path(file_path).name} 加载工具失败: {str(e)}"
624
+ )
563
625
  return False
564
626
 
565
627
  @staticmethod
566
628
  def _has_tool_calls_block(content: str) -> bool:
567
629
  """从内容中提取工具调用块(仅匹配行首标签,忽略大小写)"""
568
- pattern = rf'(?msi){re.escape(ot("TOOL_CALL"))}(.*?)^{re.escape(ct("TOOL_CALL"))}'
630
+ pattern = (
631
+ rf"(?msi){re.escape(ot('TOOL_CALL'))}(.*?)^{re.escape(ct('TOOL_CALL'))}"
632
+ )
569
633
  return re.search(pattern, content) is not None
570
634
 
571
635
  @staticmethod
572
636
  def _get_long_response_hint(content: str) -> str:
573
637
  """生成长响应的提示信息
574
-
638
+
575
639
  参数:
576
640
  content: 响应内容
577
-
641
+
578
642
  返回:
579
643
  str: 如果响应较长,返回提示信息;否则返回空字符串
580
644
  """
581
645
  if len(content) > 2048:
582
- return "\n\n⚠️ 提示:响应内容较长(超过2048字符),可能是上下文溢出导致工具调用解析失败。如果是修改文件(edit_file)或重写文件(rewrite_file)操作,建议分多次进行,每次处理文件的一部分。"
646
+ return (
647
+ "\n\n⚠️ 提示:响应内容较长(超过2048字符),可能是上下文溢出导致工具调用解析失败。"
648
+ "如果是修改文件(edit_file)操作,"
649
+ "建议分多次进行,每次处理文件的一部分。"
650
+ )
583
651
  return ""
584
652
 
585
653
  @staticmethod
586
- def _extract_json_from_text(text: str, start_pos: int = 0) -> Tuple[Optional[str], int]:
654
+ def _extract_json_from_text(
655
+ text: str, start_pos: int = 0
656
+ ) -> Tuple[Optional[str], int]:
587
657
  """从文本中提取完整的JSON对象(通过括号匹配)
588
-
658
+
589
659
  参数:
590
660
  text: 要提取的文本
591
661
  start_pos: 开始搜索的位置
592
-
662
+
593
663
  返回:
594
664
  Tuple[Optional[str], int]:
595
665
  - 第一个元素是提取的JSON字符串(如果找到),否则为None
@@ -597,94 +667,95 @@ class ToolRegistry(OutputHandlerProtocol):
597
667
  """
598
668
  # 跳过空白字符
599
669
  pos = start_pos
600
- while pos < len(text) and text[pos] in (' ', '\t', '\n', '\r'):
670
+ while pos < len(text) and text[pos] in (" ", "\t", "\n", "\r"):
601
671
  pos += 1
602
-
672
+
603
673
  if pos >= len(text):
604
674
  return None, pos
605
-
675
+
606
676
  # 检查是否以 { 开头
607
- if text[pos] != '{':
677
+ if text[pos] != "{":
608
678
  return None, pos
609
-
679
+
610
680
  # 使用括号匹配找到完整的JSON对象
611
681
  brace_count = 0
612
682
  in_string = False
613
683
  escape_next = False
614
684
  string_char = None
615
-
685
+
616
686
  json_start = pos
617
687
  for i in range(pos, len(text)):
618
688
  char = text[i]
619
-
689
+
620
690
  if escape_next:
621
691
  escape_next = False
622
692
  continue
623
-
624
- if char == '\\':
693
+
694
+ if char == "\\":
625
695
  escape_next = True
626
696
  continue
627
-
697
+
628
698
  if not in_string:
629
699
  if char in ('"', "'"):
630
700
  in_string = True
631
701
  string_char = char
632
- elif char == '{':
702
+ elif char == "{":
633
703
  brace_count += 1
634
- elif char == '}':
704
+ elif char == "}":
635
705
  brace_count -= 1
636
706
  if brace_count == 0:
637
707
  # 找到完整的JSON对象
638
- return text[json_start:i+1], i + 1
708
+ return text[json_start : i + 1], i + 1
639
709
  else:
640
710
  if char == string_char:
641
711
  in_string = False
642
712
  string_char = None
643
-
713
+
644
714
  return None, len(text)
645
-
715
+
646
716
  @staticmethod
647
717
  def _clean_extra_markers(text: str) -> str:
648
718
  """清理文本中的额外标记(如 <|tool_call_end|> 等)
649
-
719
+
650
720
  参数:
651
721
  text: 要清理的文本
652
-
722
+
653
723
  返回:
654
724
  清理后的文本
655
725
  """
656
726
  # 常见的额外标记模式
657
727
  extra_markers = [
658
- r'<\|tool_call_end\|>',
659
- r'<\|tool_calls_section_end\|>',
660
- r'<\|.*?\|>', # 匹配所有 <|...|> 格式的标记
728
+ r"<\|tool_call_end\|>",
729
+ r"<\|tool_calls_section_end\|>",
730
+ r"<\|.*?\|>", # 匹配所有 <|...|> 格式的标记
661
731
  ]
662
-
732
+
663
733
  cleaned = text
664
734
  for pattern in extra_markers:
665
- cleaned = re.sub(pattern, '', cleaned, flags=re.IGNORECASE)
666
-
735
+ cleaned = re.sub(pattern, "", cleaned, flags=re.IGNORECASE)
736
+
667
737
  return cleaned.strip()
668
738
 
669
739
  @staticmethod
670
740
  def _try_llm_fix(content: str, agent: Any, error_msg: str) -> Optional[str]:
671
741
  """尝试使用大模型修复工具调用格式
672
-
742
+
673
743
  参数:
674
744
  content: 包含错误工具调用的内容
675
745
  agent: Agent实例,用于调用大模型
676
746
  error_msg: 错误消息
677
-
747
+
678
748
  返回:
679
749
  Optional[str]: 修复后的内容,如果修复失败则返回None
680
750
  """
681
751
  try:
682
752
  from jarvis.jarvis_agent import Agent
753
+
683
754
  agent_instance: Agent = agent
684
-
755
+
685
756
  # 获取工具使用说明
686
757
  tool_usage = agent_instance.get_tool_usage_prompt()
687
-
758
+
688
759
  # 构建修复提示
689
760
  fix_prompt = f"""你之前的工具调用格式有误,请根据工具使用说明修复以下内容。
690
761
 
@@ -703,22 +774,75 @@ class ToolRegistry(OutputHandlerProtocol):
703
774
  3. 如果使用多行字符串,推荐使用 ||| 或 ``` 分隔符包裹
704
775
 
705
776
  请直接返回修复后的完整工具调用内容,不要添加其他说明文字。"""
706
-
777
+
707
778
  # 调用大模型修复
708
- print("🤖 尝试使用大模型修复工具调用格式...")
709
- fixed_content = agent_instance.model.chat_until_success(fix_prompt) # type: ignore
710
-
779
+ PrettyOutput.auto_print("🤖 尝试使用大模型修复工具调用格式...")
780
+ fixed_content = agent_instance.model.chat_until_success(fix_prompt)
781
+
711
782
  if fixed_content:
712
- print("✅ 大模型修复完成")
783
+ PrettyOutput.auto_print("✅ 大模型修复完成")
713
784
  return fixed_content
714
785
  else:
715
- print("❌ 大模型修复失败:返回内容为空")
786
+ PrettyOutput.auto_print("❌ 大模型修复失败:返回内容为空")
716
787
  return None
717
-
788
+
718
789
  except Exception as e:
719
- print(f"❌ 大模型修复失败:{str(e)}")
790
+ PrettyOutput.auto_print(f"❌ 大模型修复失败:{str(e)}")
720
791
  return None
721
792
 
793
+ @staticmethod
794
+ def _check_and_handle_multiple_tool_calls(
795
+ content: str, blocks: List[str]
796
+ ) -> Tuple[Optional[str], bool]:
797
+ """检测并处理多个工具调用的情况
798
+
799
+ 参数:
800
+ content: 包含工具调用的内容
801
+ blocks: 工具调用块列表
802
+
803
+ 返回:
804
+ Tuple[Optional[str], bool]:
805
+ - 第一个元素:如果检测到多个工具调用,返回错误消息;否则返回None
806
+ - 第二个元素:是否检测到多个工具调用
807
+ """
808
+ if len(blocks) <= 1:
809
+ return None, False
810
+
811
+ # 尝试解析每个块,收集所有成功解析的工具调用
812
+ parsed_tools = []
813
+ for item in blocks:
814
+ try:
815
+ cleaned_item = ToolRegistry._clean_extra_markers(item)
816
+ msg = json_loads(cleaned_item)
817
+ if "name" in msg and "arguments" in msg:
818
+ parsed_tools.append(msg)
819
+ except Exception:
820
+ # 如果某个块解析失败,可能是格式问题,继续检查其他块
821
+ pass
822
+
823
+ # 如果成功解析了多个工具调用,返回明确的错误信息
824
+ if len(parsed_tools) > 1:
825
+ tool_names = [
826
+ tool_call.get("name", "未知工具") for tool_call in parsed_tools
827
+ ]
828
+ error_msg = f"""检测到多个工具调用(共 {len(parsed_tools)} 个),请一次只处理一个工具调用。
829
+
830
+ 检测到的工具调用:
831
+ {chr(10).join(f" - {i + 1}. {name}" for i, name in enumerate(tool_names))}
832
+
833
+ 失败原因:
834
+ 系统要求每次只能执行一个工具调用,等待结果后再进行下一步操作。同时调用多个工具会导致:
835
+ 1. 无法确定工具执行的顺序和依赖关系
836
+ 2. 无法正确处理工具之间的交互
837
+ 3. 可能导致资源竞争和状态不一致
838
+
839
+ 请修改工具调用,确保每次只包含一个 {ot("TOOL_CALL")}...{ct("TOOL_CALL")} 块。
840
+
841
+ {tool_call_help}"""
842
+ return error_msg, True
843
+
844
+ return None, False
845
+
722
846
  @staticmethod
723
847
  def _extract_tool_calls(
724
848
  content: str,
@@ -743,24 +867,45 @@ class ToolRegistry(OutputHandlerProtocol):
743
867
  close_tag = ct("TOOL_CALL")
744
868
  # 使用正则表达式查找结束标签(忽略大小写),以获取实际位置和原始大小写
745
869
  close_tag_pattern = re.escape(close_tag)
746
- match = re.search(rf'{close_tag_pattern}$', content.rstrip(), re.IGNORECASE)
870
+ match = re.search(rf"{close_tag_pattern}$", content.rstrip(), re.IGNORECASE)
747
871
  if match:
748
872
  pos = match.start()
749
873
  if pos > 0 and content[pos - 1] not in ("\n", "\r"):
750
874
  content = content[:pos] + "\n" + content[pos:]
751
875
 
752
876
  # 首先尝试标准的提取方式(忽略大小写)
753
- pattern = rf'(?msi){re.escape(ot("TOOL_CALL"))}(.*?)^{re.escape(ct("TOOL_CALL"))}'
877
+ pattern = (
878
+ rf"(?msi){re.escape(ot('TOOL_CALL'))}(.*?)^{re.escape(ct('TOOL_CALL'))}"
879
+ )
754
880
  data = re.findall(pattern, content)
755
881
  auto_completed = False
756
-
882
+
883
+ # 如果检测到多个工具调用块,先检查是否是多个独立的工具调用
884
+ if len(data) > 1:
885
+ (
886
+ error_msg,
887
+ has_multiple,
888
+ ) = ToolRegistry._check_and_handle_multiple_tool_calls(content, data)
889
+ if has_multiple:
890
+ return (
891
+ cast(Dict[str, Dict[str, Any]], {}),
892
+ error_msg if error_msg else "",
893
+ False,
894
+ )
895
+ # 如果解析失败,可能是多个工具调用被当作一个 JSON 来解析了
896
+ # 继续执行后续的宽松提取逻辑
897
+
757
898
  # 如果标准提取失败,尝试更宽松的提取方式
758
899
  if not data:
759
900
  # can_handle 确保 ot("TOOL_CALL") 在内容中(行首)。
760
901
  # 如果数据为空,则表示行首的 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
-
902
+ has_open_at_bol = (
903
+ re.search(rf"(?mi){re.escape(ot('TOOL_CALL'))}", content) is not None
904
+ )
905
+ has_close_at_bol = (
906
+ re.search(rf"(?mi)^{re.escape(ct('TOOL_CALL'))}", content) is not None
907
+ )
908
+
764
909
  if has_open_at_bol and not has_close_at_bol:
765
910
  # 尝试通过附加结束标签来修复它(确保结束标签位于行首)
766
911
  fixed_content = content.strip() + f"\n{ct('TOOL_CALL')}"
@@ -780,50 +925,104 @@ class ToolRegistry(OutputHandlerProtocol):
780
925
  # Even after fixing, it's not valid JSON, or user cancelled.
781
926
  # Fall through to try more lenient extraction.
782
927
  pass
783
-
928
+
784
929
  # 如果仍然没有数据,尝试更宽松的提取:直接从开始标签后提取JSON
785
930
  if not data:
931
+ # 先检查是否有多个工具调用块(可能被当作一个 JSON 来解析导致失败)
932
+ multiple_blocks = re.findall(
933
+ rf"(?msi){re.escape(ot('TOOL_CALL'))}(.*?){re.escape(ct('TOOL_CALL'))}",
934
+ content,
935
+ )
936
+ (
937
+ error_msg,
938
+ has_multiple,
939
+ ) = ToolRegistry._check_and_handle_multiple_tool_calls(
940
+ content, multiple_blocks
941
+ )
942
+ if has_multiple:
943
+ return (
944
+ cast(Dict[str, Dict[str, Any]], {}),
945
+ error_msg if error_msg else "",
946
+ False,
947
+ )
948
+
786
949
  # 找到开始标签的位置
787
- open_tag_match = re.search(rf'(?i){re.escape(ot("TOOL_CALL"))}', content)
950
+ open_tag_match = re.search(
951
+ rf"(?i){re.escape(ot('TOOL_CALL'))}", content
952
+ )
788
953
  if open_tag_match:
789
954
  # 从开始标签后提取JSON
790
955
  start_pos = open_tag_match.end()
791
- json_str, end_pos = ToolRegistry._extract_json_from_text(content, start_pos)
792
-
956
+ json_str, end_pos = ToolRegistry._extract_json_from_text(
957
+ content, start_pos
958
+ )
959
+
793
960
  if json_str:
794
961
  # 清理JSON字符串中的额外标记
795
962
  json_str = ToolRegistry._clean_extra_markers(json_str)
796
-
963
+
797
964
  # 尝试解析JSON
798
965
  try:
799
966
  parsed = json_loads(json_str)
800
967
  # 验证是否包含必要字段
801
- if "name" in parsed and "arguments" in parsed and "want" in parsed:
968
+ if "name" in parsed and "arguments" in parsed:
802
969
  data = [json_str]
803
970
  auto_completed = True
971
+ else:
972
+ # 记录缺少必要字段的错误
973
+ missing_fields = []
974
+ if "name" not in parsed:
975
+ missing_fields.append("name")
976
+ if "arguments" not in parsed:
977
+ missing_fields.append("arguments")
978
+ # 不立即返回错误,继续尝试其他方法,但记录信息用于后续错误提示
979
+ pass
804
980
  except Exception:
805
- # JSON解析失败,继续尝试其他方法
981
+ # JSON解析失败,记录错误信息用于后续错误提示
982
+ # 不立即返回错误,继续尝试其他方法(如大模型修复)
806
983
  pass
807
-
984
+ else:
985
+ # JSON提取失败:没有找到有效的JSON对象
986
+ # 不立即返回错误,继续尝试其他方法(如大模型修复)
987
+ pass
988
+
808
989
  # 如果仍然没有数据,尝试使用大模型修复
809
990
  if not data:
810
991
  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
-
992
+ # 检查是否有开始和结束标签,生成更准确的错误消息
993
+ has_open = (
994
+ re.search(rf"(?i){re.escape(ot('TOOL_CALL'))}", content) is not None
995
+ )
996
+ has_close = (
997
+ re.search(rf"(?i){re.escape(ct('TOOL_CALL'))}", content) is not None
998
+ )
999
+
1000
+ if has_open and has_close:
1001
+ # 有开始和结束标签,但JSON解析失败
1002
+ error_msg = f"工具调用格式错误:检测到{ot('TOOL_CALL')}和{ct('TOOL_CALL')}标签,但JSON解析失败。请检查JSON格式是否正确,确保包含name和arguments字段。\n{tool_call_help}{long_hint}"
1003
+ elif has_open and not has_close:
1004
+ # 只有开始标签,没有结束标签
1005
+ error_msg = f"工具调用格式错误:检测到{ot('TOOL_CALL')}标签,但未找到{ct('TOOL_CALL')}标签。请确保工具调用包含完整的开始和结束标签。\n{tool_call_help}{long_hint}"
1006
+ else:
1007
+ # 其他情况
1008
+ error_msg = f"工具调用格式错误:无法解析工具调用内容。请检查工具调用格式。\n{tool_call_help}{long_hint}"
1009
+
813
1010
  # 如果提供了agent且long_hint为空,尝试使用大模型修复
814
1011
  if agent is not None and not long_hint:
815
- fixed_content = ToolRegistry._try_llm_fix(content, agent, error_msg)
816
- if fixed_content:
1012
+ llm_fixed_content: Optional[str] = ToolRegistry._try_llm_fix(
1013
+ content, agent, error_msg
1014
+ )
1015
+ if llm_fixed_content is not None:
817
1016
  # 递归调用自身,尝试解析修复后的内容
818
1017
  return ToolRegistry._extract_tool_calls(fixed_content, None)
819
-
1018
+
820
1019
  # 如果大模型修复失败或未提供agent或long_hint不为空,返回错误
821
1020
  return (
822
1021
  {},
823
1022
  error_msg,
824
1023
  False,
825
1024
  )
826
-
1025
+
827
1026
  ret = []
828
1027
  for item in data:
829
1028
  try:
@@ -831,20 +1030,46 @@ class ToolRegistry(OutputHandlerProtocol):
831
1030
  cleaned_item = ToolRegistry._clean_extra_markers(item)
832
1031
  msg = json_loads(cleaned_item)
833
1032
  except Exception as e:
1033
+ # 如果解析失败,先检查是否是因为有多个工具调用
1034
+ # 检查错误信息中是否包含 "expected a comma" 或类似的多对象错误
1035
+ error_str = str(e).lower()
1036
+ if "expected a comma" in error_str or "multiple" in error_str:
1037
+ # 尝试检测是否有多个工具调用块
1038
+ multiple_blocks = re.findall(
1039
+ rf"(?msi){re.escape(ot('TOOL_CALL'))}(.*?){re.escape(ct('TOOL_CALL'))}",
1040
+ content,
1041
+ )
1042
+ (
1043
+ error_msg,
1044
+ has_multiple,
1045
+ ) = ToolRegistry._check_and_handle_multiple_tool_calls(
1046
+ content, multiple_blocks
1047
+ )
1048
+ if has_multiple:
1049
+ return (
1050
+ cast(Dict[str, Dict[str, Any]], {}),
1051
+ error_msg if error_msg else "",
1052
+ False,
1053
+ )
1054
+
834
1055
  long_hint = ToolRegistry._get_long_response_hint(content)
835
1056
  error_msg = f"""Jsonnet 解析失败:{e}
836
1057
 
837
1058
  提示:Jsonnet支持双引号/单引号、尾随逗号、注释。多行字符串推荐使用 ||| 或 ``` 分隔符包裹,直接换行无需转义,支持保留缩进。
838
1059
 
839
1060
  {tool_call_help}{long_hint}"""
840
-
1061
+
841
1062
  # 如果提供了agent且long_hint为空,尝试使用大模型修复
842
1063
  if agent is not None and not long_hint:
843
- fixed_content = ToolRegistry._try_llm_fix(content, agent, error_msg)
844
- if fixed_content:
1064
+ retry_fixed_content: Optional[str] = ToolRegistry._try_llm_fix(
1065
+ content, agent, error_msg
1066
+ )
1067
+ if retry_fixed_content is not None:
845
1068
  # 递归调用自身,尝试解析修复后的内容
846
- return ToolRegistry._extract_tool_calls(fixed_content, None)
847
-
1069
+ return ToolRegistry._extract_tool_calls(
1070
+ retry_fixed_content, None
1071
+ )
1072
+
848
1073
  # 如果大模型修复失败或未提供agent或long_hint不为空,返回错误
849
1074
  return (
850
1075
  {},
@@ -852,21 +1077,23 @@ class ToolRegistry(OutputHandlerProtocol):
852
1077
  False,
853
1078
  )
854
1079
 
855
- if "name" in msg and "arguments" in msg and "want" in msg:
1080
+ if "name" in msg and "arguments" in msg:
856
1081
  ret.append(msg)
857
1082
  else:
858
1083
  long_hint = ToolRegistry._get_long_response_hint(content)
859
- error_msg = f"""工具调用格式错误,请检查工具调用格式(缺少name、arguments、want字段)。
1084
+ error_msg = f"""工具调用格式错误,请检查工具调用格式(缺少name、arguments字段)。
860
1085
 
861
1086
  {tool_call_help}{long_hint}"""
862
-
1087
+
863
1088
  # 如果提供了agent且long_hint为空,尝试使用大模型修复
864
1089
  if agent is not None and not long_hint:
865
- fixed_content = ToolRegistry._try_llm_fix(content, agent, error_msg)
866
- if fixed_content:
1090
+ fixed_content_3: Optional[str] = ToolRegistry._try_llm_fix(
1091
+ content, agent, error_msg
1092
+ )
1093
+ if fixed_content_3 is not None:
867
1094
  # 递归调用自身,尝试解析修复后的内容
868
- return ToolRegistry._extract_tool_calls(fixed_content, None)
869
-
1095
+ return ToolRegistry._extract_tool_calls(fixed_content_3, None)
1096
+
870
1097
  # 如果大模型修复失败或未提供agent或long_hint不为空,返回错误
871
1098
  return (
872
1099
  {},
@@ -894,7 +1121,7 @@ class ToolRegistry(OutputHandlerProtocol):
894
1121
  func: 工具执行函数
895
1122
  """
896
1123
  if name in self.tools:
897
- print(f"⚠️ 警告: 工具 '{name}' 已存在,将被覆盖")
1124
+ PrettyOutput.auto_print(f"⚠️ 警告: 工具 '{name}' 已存在,将被覆盖")
898
1125
  self.tools[name] = Tool(name, description, parameters, func, protocol_version)
899
1126
 
900
1127
  def get_tool(self, name: str) -> Optional[Tool]:
@@ -956,7 +1183,7 @@ class ToolRegistry(OutputHandlerProtocol):
956
1183
  try:
957
1184
  if getattr(tool, "protocol_version", "1.0") == "2.0":
958
1185
  # v2.0: agent与参数分离传递
959
- return tool.func(arguments, agent) # type: ignore[misc]
1186
+ return cast(Dict[str, Any], tool.func(arguments, agent))
960
1187
  else:
961
1188
  # v1.0: 兼容旧实现,将agent注入到arguments(如果提供)
962
1189
  args_to_call = arguments.copy() if isinstance(arguments, dict) else {}
@@ -999,7 +1226,7 @@ class ToolRegistry(OutputHandlerProtocol):
999
1226
  """
1000
1227
  if len(output.splitlines()) > 60:
1001
1228
  lines = output.splitlines()
1002
- print("⚠️ 输出太长,截取前后30行")
1229
+ PrettyOutput.auto_print("⚠️ 输出太长,截取前后30行")
1003
1230
  return "\n".join(
1004
1231
  lines[:30] + ["\n...内容太长,已截取前后30行...\n"] + lines[-30:]
1005
1232
  )
@@ -1009,7 +1236,7 @@ class ToolRegistry(OutputHandlerProtocol):
1009
1236
  try:
1010
1237
  name = tool_call["name"] # 确保name是str类型
1011
1238
  args = tool_call["arguments"] # 原始参数(来自外部协议)
1012
- want = tool_call["want"]
1239
+ want = tool_call.get("want", "")
1013
1240
 
1014
1241
  from jarvis.jarvis_agent import Agent
1015
1242
 
@@ -1025,31 +1252,82 @@ class ToolRegistry(OutputHandlerProtocol):
1025
1252
  usage_prompt = agent_instance.get_tool_usage_prompt()
1026
1253
  except Exception:
1027
1254
  usage_prompt = tool_call_help
1028
- print("❌ 工具参数格式无效")
1255
+ PrettyOutput.auto_print("❌ 工具参数格式无效")
1029
1256
  return f"工具参数格式无效: {name}。arguments 应为可解析的 Jsonnet 或对象,请按工具调用格式提供。\n提示:对于多行字符串参数,推荐使用 ||| 或 ``` 分隔符包裹,直接换行无需转义,支持保留缩进。\n\n{usage_prompt}"
1030
1257
 
1031
- print(f"🛠️ 执行工具调用 {name}")
1258
+ # 生成参数摘要,过滤敏感信息
1259
+ param_summary = ""
1260
+ if isinstance(args, dict) and args:
1261
+ # 敏感字段列表
1262
+ sensitive_keys = {
1263
+ "password",
1264
+ "token",
1265
+ "key",
1266
+ "secret",
1267
+ "auth",
1268
+ "credential",
1269
+ }
1270
+ summary_parts = []
1271
+
1272
+ for key, value in args.items():
1273
+ if key.lower() in sensitive_keys:
1274
+ summary_parts.append(f"{key}='***'")
1275
+ elif isinstance(value, (dict, list)):
1276
+ # 复杂类型简化为类型信息
1277
+ summary_parts.append(
1278
+ f"{key}={type(value).__name__}({len(value)} items)"
1279
+ )
1280
+ elif isinstance(value, str) and len(value) > 50:
1281
+ # 长字符串截断
1282
+ summary_parts.append(f"{key}='{value[:47]}...'")
1283
+ else:
1284
+ summary_parts.append(f"{key}={repr(value)}")
1285
+
1286
+ if summary_parts:
1287
+ # 将参数值中的换行符替换为空格,避免摘要中出现换行
1288
+ cleaned_parts = [
1289
+ part.replace("\n", " ").replace("\r", " ")
1290
+ for part in summary_parts
1291
+ ]
1292
+ param_summary = " | ".join(cleaned_parts)
1293
+
1294
+ # 合并为一行输出:执行工具调用和参数摘要
1295
+ if param_summary:
1296
+ PrettyOutput.auto_print(f"🛠️ 执行工具调用 {name} [{param_summary}]")
1297
+ else:
1298
+ PrettyOutput.auto_print(f"🛠️ 执行工具调用 {name}")
1299
+
1032
1300
  # 执行工具调用(根据工具实现的协议版本,由系统在内部决定agent的传递方式)
1033
1301
  result = self.execute_tool(name, args, agent)
1034
-
1302
+
1035
1303
  # 打印执行状态
1036
1304
  if result.get("success", False):
1037
- print(f"✅ 执行工具调用 {name} 成功")
1305
+ PrettyOutput.auto_print(f"✅ 执行工具调用 {name} 成功")
1038
1306
  else:
1039
- print(f"❌ 执行工具调用 {name} 失败")
1307
+ # 获取失败原因
1308
+ stderr = result.get("stderr", "")
1309
+ stdout = result.get("stdout", "")
1310
+ error_msg = stderr if stderr else (stdout if stdout else "未知错误")
1311
+ PrettyOutput.auto_print(f"❌ 执行工具调用 {name} 失败")
1312
+ PrettyOutput.auto_print(f" 失败原因: {error_msg}")
1040
1313
 
1041
1314
  # 记录本轮实际执行的工具,供上层逻辑(如记忆保存判定)使用
1042
1315
  try:
1043
1316
  from jarvis.jarvis_agent import Agent # 延迟导入避免循环依赖
1317
+
1044
1318
  agent_instance_for_record: Agent = agent_instance
1045
1319
  # 记录最后一次执行的工具
1046
- agent_instance_for_record.set_user_data("__last_executed_tool__", name) # type: ignore
1320
+ agent_instance_for_record.set_user_data("__last_executed_tool__", name)
1047
1321
  # 记录本轮累计执行的工具列表
1048
- executed_list = agent_instance_for_record.get_user_data("__executed_tools__") # type: ignore
1322
+ executed_list = agent_instance_for_record.get_user_data(
1323
+ "__executed_tools__"
1324
+ )
1049
1325
  if not isinstance(executed_list, list):
1050
1326
  executed_list = []
1051
1327
  executed_list.append(name)
1052
- agent_instance_for_record.set_user_data("__executed_tools__", executed_list) # type: ignore
1328
+ agent_instance_for_record.set_user_data(
1329
+ "__executed_tools__", executed_list
1330
+ )
1053
1331
  except Exception:
1054
1332
  pass
1055
1333
 
@@ -1059,7 +1337,9 @@ class ToolRegistry(OutputHandlerProtocol):
1059
1337
  usage_prompt = agent_instance.get_tool_usage_prompt()
1060
1338
  except Exception:
1061
1339
  usage_prompt = tool_call_help
1062
- err_output = self._format_tool_output(result.get("stdout", ""), result.get("stderr", ""))
1340
+ err_output = self._format_tool_output(
1341
+ result.get("stdout", ""), result.get("stderr", "")
1342
+ )
1063
1343
  return f"{err_output}\n\n{usage_prompt}"
1064
1344
 
1065
1345
  # 格式化输出
@@ -1068,11 +1348,9 @@ class ToolRegistry(OutputHandlerProtocol):
1068
1348
  )
1069
1349
 
1070
1350
  # 检查内容是否过大
1071
- model_group = None
1072
- platform = None
1073
- if agent_instance.model:
1074
- model_group = agent_instance.model.model_group
1075
- platform = agent_instance.model
1351
+ # 使用全局模型组(不再从 agent 继承)
1352
+ model_group = get_global_model_group()
1353
+ platform = agent_instance.model if agent_instance.model else None
1076
1354
  is_large_content = is_context_overflow(output, model_group, platform)
1077
1355
 
1078
1356
  if is_large_content:
@@ -1106,7 +1384,7 @@ class ToolRegistry(OutputHandlerProtocol):
1106
1384
  </content>
1107
1385
 
1108
1386
  上传的文件是以下工具执行结果:
1109
- {json.dumps({"name":name, "arguments":args, "want":want}, ensure_ascii=False, indent=2)}
1387
+ {json.dumps({"name": name, "arguments": args, "want": want}, ensure_ascii=False, indent=2)}
1110
1388
 
1111
1389
  请根据以上信息,继续完成任务。
1112
1390
  """
@@ -1126,17 +1404,18 @@ class ToolRegistry(OutputHandlerProtocol):
1126
1404
  # 尝试获取工具名称(如果已定义)
1127
1405
  tool_name = ""
1128
1406
  try:
1129
- if 'name' in locals():
1407
+ if "name" in locals():
1130
1408
  tool_name = name
1131
1409
  except Exception:
1132
1410
  pass
1133
1411
  if tool_name:
1134
- print(f"❌ 执行工具调用 {tool_name} 失败:{str(e)}")
1412
+ PrettyOutput.auto_print(f"❌ 执行工具调用 {tool_name} 失败:{str(e)}")
1135
1413
  else:
1136
- print(f"❌ 工具调用失败:{str(e)}")
1414
+ PrettyOutput.auto_print(f"❌ 工具调用失败:{str(e)}")
1137
1415
  try:
1138
1416
  from jarvis.jarvis_agent import Agent # 延迟导入避免循环依赖
1139
- agent_instance_for_prompt: Agent = agent # type: ignore
1417
+
1418
+ agent_instance_for_prompt: Agent = agent
1140
1419
  usage_prompt = agent_instance_for_prompt.get_tool_usage_prompt()
1141
1420
  except Exception:
1142
1421
  usage_prompt = tool_call_help