jarvis-ai-assistant 0.1.225__py3-none-any.whl → 0.2.1__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 +8 -5
- jarvis/jarvis_agent/jarvis.py +6 -0
- jarvis/jarvis_agent/main.py +7 -0
- jarvis/jarvis_code_agent/code_agent.py +12 -1
- jarvis/jarvis_code_analysis/code_review.py +14 -3
- jarvis/jarvis_data/config_schema.json +41 -0
- jarvis/jarvis_git_utils/git_commiter.py +9 -2
- jarvis/jarvis_mcp/sse_mcp_client.py +9 -7
- jarvis/jarvis_mcp/stdio_mcp_client.py +2 -2
- jarvis/jarvis_multi_agent/__init__.py +7 -5
- jarvis/jarvis_platform/base.py +28 -13
- jarvis/jarvis_tools/generate_new_tool.py +1 -0
- jarvis/jarvis_tools/registry.py +71 -28
- jarvis/jarvis_utils/config.py +80 -21
- jarvis/jarvis_utils/git_utils.py +2 -2
- jarvis/jarvis_utils/globals.py +17 -11
- jarvis/jarvis_utils/methodology.py +37 -23
- jarvis/jarvis_utils/output.py +2 -2
- jarvis/jarvis_utils/utils.py +137 -3
- {jarvis_ai_assistant-0.1.225.dist-info → jarvis_ai_assistant-0.2.1.dist-info}/METADATA +61 -13
- {jarvis_ai_assistant-0.1.225.dist-info → jarvis_ai_assistant-0.2.1.dist-info}/RECORD +26 -26
- {jarvis_ai_assistant-0.1.225.dist-info → jarvis_ai_assistant-0.2.1.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.225.dist-info → jarvis_ai_assistant-0.2.1.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.225.dist-info → jarvis_ai_assistant-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.225.dist-info → jarvis_ai_assistant-0.2.1.dist-info}/top_level.txt +0 -0
jarvis/__init__.py
CHANGED
jarvis/jarvis_agent/__init__.py
CHANGED
@@ -102,6 +102,7 @@ class Agent:
|
|
102
102
|
name: str = "Jarvis",
|
103
103
|
description: str = "",
|
104
104
|
llm_type: str = "normal",
|
105
|
+
model_group: Optional[str] = None,
|
105
106
|
summary_prompt: Optional[str] = None,
|
106
107
|
auto_complete: bool = False,
|
107
108
|
output_handler: List[OutputHandlerProtocol] = [],
|
@@ -137,11 +138,11 @@ class Agent:
|
|
137
138
|
self.description = description
|
138
139
|
# 初始化平台和模型
|
139
140
|
if llm_type == "thinking":
|
140
|
-
platform_name = get_thinking_platform_name()
|
141
|
-
model_name = get_thinking_model_name()
|
141
|
+
platform_name = get_thinking_platform_name(model_group)
|
142
|
+
model_name = get_thinking_model_name(model_group)
|
142
143
|
else: # 默认为 normal
|
143
|
-
platform_name = get_normal_platform_name()
|
144
|
-
model_name = get_normal_model_name()
|
144
|
+
platform_name = get_normal_platform_name(model_group)
|
145
|
+
model_name = get_normal_model_name(model_group)
|
145
146
|
|
146
147
|
self.model = PlatformRegistry().create_platform(platform_name)
|
147
148
|
if self.model is None:
|
@@ -153,6 +154,8 @@ class Agent:
|
|
153
154
|
if model_name:
|
154
155
|
self.model.set_model_name(model_name)
|
155
156
|
|
157
|
+
self.model.set_model_group(model_group)
|
158
|
+
|
156
159
|
self.user_data: Dict[str, Any] = {}
|
157
160
|
|
158
161
|
self.model.set_suppress_output(False)
|
@@ -197,7 +200,7 @@ class Agent:
|
|
197
200
|
summary_prompt if summary_prompt else DEFAULT_SUMMARY_PROMPT
|
198
201
|
)
|
199
202
|
|
200
|
-
self.max_token_count = get_max_token_count()
|
203
|
+
self.max_token_count = get_max_token_count(model_group)
|
201
204
|
self.auto_complete = auto_complete
|
202
205
|
welcome_message = f"{name} 初始化完成 - 使用 {self.model.name()} 模型"
|
203
206
|
|
jarvis/jarvis_agent/jarvis.py
CHANGED
@@ -125,6 +125,11 @@ def _parse_args() -> argparse.Namespace:
|
|
125
125
|
type=str,
|
126
126
|
help="Directly input task content from command line",
|
127
127
|
)
|
128
|
+
parser.add_argument(
|
129
|
+
"--model_group",
|
130
|
+
type=str,
|
131
|
+
help="Model group to use, overriding config",
|
132
|
+
)
|
128
133
|
parser.add_argument("-f", "--config", type=str, help="Path to custom config file")
|
129
134
|
parser.add_argument(
|
130
135
|
"--restore-session",
|
@@ -170,6 +175,7 @@ def _initialize_agent(args: argparse.Namespace) -> Agent:
|
|
170
175
|
agent = Agent(
|
171
176
|
system_prompt=origin_agent_system_prompt,
|
172
177
|
llm_type=args.llm_type,
|
178
|
+
model_group=args.model_group,
|
173
179
|
input_handler=[shell_input_handler, builtin_input_handler],
|
174
180
|
output_handler=[ToolRegistry()], # type: ignore
|
175
181
|
need_summary=False,
|
jarvis/jarvis_agent/main.py
CHANGED
@@ -52,6 +52,11 @@ def main():
|
|
52
52
|
choices=["normal", "thinking"],
|
53
53
|
help="LLM type to use, overriding config",
|
54
54
|
)
|
55
|
+
parser.add_argument(
|
56
|
+
"--model_group",
|
57
|
+
type=str,
|
58
|
+
help="Model group to use, overriding config",
|
59
|
+
)
|
55
60
|
args = parser.parse_args()
|
56
61
|
|
57
62
|
# Initialize environment
|
@@ -65,6 +70,8 @@ def main():
|
|
65
70
|
# Override config with command-line arguments if provided
|
66
71
|
if args.llm_type:
|
67
72
|
config["llm_type"] = args.llm_type
|
73
|
+
if args.model_group:
|
74
|
+
config["model_group"] = args.model_group
|
68
75
|
|
69
76
|
# Create and run agent
|
70
77
|
try:
|
@@ -47,6 +47,7 @@ class CodeAgent:
|
|
47
47
|
def __init__(
|
48
48
|
self,
|
49
49
|
llm_type: str = "normal",
|
50
|
+
model_group: Optional[str] = None,
|
50
51
|
need_summary: bool = True,
|
51
52
|
):
|
52
53
|
self.root_dir = os.getcwd()
|
@@ -120,6 +121,7 @@ class CodeAgent:
|
|
120
121
|
auto_complete=False,
|
121
122
|
output_handler=[tool_registry, EditFileHandler()], # type: ignore
|
122
123
|
llm_type=llm_type,
|
124
|
+
model_group=model_group,
|
123
125
|
input_handler=[shell_input_handler, builtin_input_handler],
|
124
126
|
need_summary=need_summary,
|
125
127
|
use_methodology=False, # 禁用方法论
|
@@ -411,6 +413,11 @@ def main() -> None:
|
|
411
413
|
choices=["normal", "thinking"],
|
412
414
|
help="LLM type to use",
|
413
415
|
)
|
416
|
+
parser.add_argument(
|
417
|
+
"--model_group",
|
418
|
+
type=str,
|
419
|
+
help="Model group to use, overriding config",
|
420
|
+
)
|
414
421
|
parser.add_argument(
|
415
422
|
"-r", "--requirement", type=str, help="Requirement to process", default=None
|
416
423
|
)
|
@@ -427,7 +434,11 @@ def main() -> None:
|
|
427
434
|
PrettyOutput.print(f"当前目录: {git_dir}", OutputType.INFO)
|
428
435
|
|
429
436
|
try:
|
430
|
-
agent = CodeAgent(
|
437
|
+
agent = CodeAgent(
|
438
|
+
llm_type=args.llm_type,
|
439
|
+
model_group=args.model_group,
|
440
|
+
need_summary=False,
|
441
|
+
)
|
431
442
|
|
432
443
|
# 尝试恢复会话
|
433
444
|
if args.restore_session:
|
@@ -3,12 +3,13 @@ import os
|
|
3
3
|
import re
|
4
4
|
import subprocess
|
5
5
|
import tempfile
|
6
|
-
from typing import Any, Dict, List
|
6
|
+
from typing import Any, Dict, List, Optional
|
7
7
|
|
8
8
|
from jarvis.jarvis_agent import Agent
|
9
9
|
from jarvis.jarvis_code_analysis.checklists.loader import get_language_checklist
|
10
10
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
11
11
|
from jarvis.jarvis_tools.read_code import ReadCodeTool
|
12
|
+
from jarvis.jarvis_utils.globals import get_agent, current_agent_name
|
12
13
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
13
14
|
from jarvis.jarvis_utils.tag import ct, ot
|
14
15
|
from jarvis.jarvis_utils.utils import init_env, is_context_overflow
|
@@ -261,7 +262,9 @@ class CodeReviewTool:
|
|
261
262
|
checklist = get_language_checklist(language)
|
262
263
|
return checklist if checklist else ""
|
263
264
|
|
264
|
-
def execute(
|
265
|
+
def execute(
|
266
|
+
self, args: Dict[str, Any], agent: Optional["Agent"] = None
|
267
|
+
) -> Dict[str, Any]:
|
265
268
|
try:
|
266
269
|
review_type = args.get("review_type", "current").strip()
|
267
270
|
root_dir = args.get("root_dir", ".")
|
@@ -570,9 +573,17 @@ class CodeReviewTool:
|
|
570
573
|
|
571
574
|
tool_registry = ToolRegistry()
|
572
575
|
tool_registry.dont_use_tools(["code_review"])
|
576
|
+
|
577
|
+
# Use the provided agent's model_group or get it from globals
|
578
|
+
calling_agent = agent or get_agent(current_agent_name)
|
579
|
+
model_group = None
|
580
|
+
if calling_agent and hasattr(calling_agent, "model") and calling_agent.model:
|
581
|
+
model_group = calling_agent.model.model_group
|
582
|
+
|
573
583
|
agent = Agent(
|
574
584
|
system_prompt=system_prompt,
|
575
585
|
name="Code Review Agent",
|
586
|
+
model_group=model_group,
|
576
587
|
summary_prompt=f"""<code_review_report>
|
577
588
|
<overview>
|
578
589
|
# 整体评估
|
@@ -675,7 +686,7 @@ class CodeReviewTool:
|
|
675
686
|
|
676
687
|
try:
|
677
688
|
# Check if content is too large
|
678
|
-
is_large_content = is_context_overflow(diff_output)
|
689
|
+
is_large_content = is_context_overflow(diff_output, model_group)
|
679
690
|
|
680
691
|
# Upload the file to the agent's model
|
681
692
|
if is_large_content:
|
@@ -141,6 +141,47 @@
|
|
141
141
|
"description": "思考操作模型名称",
|
142
142
|
"default": "deep_seek"
|
143
143
|
},
|
144
|
+
"JARVIS_MODEL_GROUP": {
|
145
|
+
"type": "string",
|
146
|
+
"description": "选择一个预定义的模型组"
|
147
|
+
},
|
148
|
+
"JARVIS_MODEL_GROUPS": {
|
149
|
+
"type": "array",
|
150
|
+
"description": "预定义的模型配置组",
|
151
|
+
"items": {
|
152
|
+
"type": "object",
|
153
|
+
"additionalProperties": {
|
154
|
+
"type": "object",
|
155
|
+
"properties": {
|
156
|
+
"JARVIS_PLATFORM": {
|
157
|
+
"type": "string"
|
158
|
+
},
|
159
|
+
"JARVIS_MODEL": {
|
160
|
+
"type": "string"
|
161
|
+
},
|
162
|
+
"JARVIS_THINKING_PLATFORM": {
|
163
|
+
"type": "string"
|
164
|
+
},
|
165
|
+
"JARVIS_THINKING_MODEL": {
|
166
|
+
"type": "string"
|
167
|
+
},
|
168
|
+
"JARVIS_MAX_TOKEN_COUNT": {
|
169
|
+
"type": "number"
|
170
|
+
},
|
171
|
+
"JARVIS_MAX_INPUT_TOKEN_COUNT": {
|
172
|
+
"type": "number"
|
173
|
+
},
|
174
|
+
"JARVIS_MAX_BIG_CONTENT_SIZE": {
|
175
|
+
"type": "number"
|
176
|
+
}
|
177
|
+
},
|
178
|
+
"required": [
|
179
|
+
"JARVIS_PLATFORM",
|
180
|
+
"JARVIS_MODEL"
|
181
|
+
]
|
182
|
+
}
|
183
|
+
}
|
184
|
+
},
|
144
185
|
"JARVIS_EXECUTE_TOOL_CONFIRM": {
|
145
186
|
"type": "boolean",
|
146
187
|
"description": "执行工具前是否需要确认",
|
@@ -16,6 +16,7 @@ from jarvis.jarvis_utils.git_utils import (
|
|
16
16
|
find_git_root_and_cd,
|
17
17
|
has_uncommitted_changes,
|
18
18
|
)
|
19
|
+
from jarvis.jarvis_utils.globals import get_agent, current_agent_name
|
19
20
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
20
21
|
from jarvis.jarvis_utils.tag import ct, ot
|
21
22
|
from jarvis.jarvis_utils.utils import init_env, is_context_overflow
|
@@ -162,11 +163,17 @@ commit信息
|
|
162
163
|
"""
|
163
164
|
|
164
165
|
# 获取模型并尝试上传文件
|
165
|
-
|
166
|
+
agent = get_agent(current_agent_name)
|
167
|
+
if agent:
|
168
|
+
platform = agent.model
|
169
|
+
model_group = agent.model.model_group
|
170
|
+
else:
|
171
|
+
platform = PlatformRegistry().get_normal_platform()
|
172
|
+
model_group = None
|
166
173
|
upload_success = False
|
167
174
|
|
168
175
|
# Check if content is too large
|
169
|
-
is_large_content = is_context_overflow(diff)
|
176
|
+
is_large_content = is_context_overflow(diff, model_group)
|
170
177
|
|
171
178
|
if is_large_content:
|
172
179
|
if not platform.support_upload_files():
|
@@ -2,7 +2,7 @@
|
|
2
2
|
import json
|
3
3
|
import threading
|
4
4
|
import time
|
5
|
-
from typing import Any, Callable, Dict, List
|
5
|
+
from typing import Any, Callable, Dict, List, Optional
|
6
6
|
from urllib.parse import parse_qs, urlencode, urljoin
|
7
7
|
|
8
8
|
import requests
|
@@ -46,10 +46,10 @@ class SSEMcpClient(McpClient):
|
|
46
46
|
self.session.headers.update(extra_headers)
|
47
47
|
|
48
48
|
# SSE相关属性
|
49
|
-
self.sse_response = None
|
50
|
-
self.sse_thread = None
|
51
|
-
self.messages_endpoint = None
|
52
|
-
self.session_id = None
|
49
|
+
self.sse_response: Optional[requests.Response] = None
|
50
|
+
self.sse_thread: Optional[threading.Thread] = None
|
51
|
+
self.messages_endpoint: Optional[str] = None
|
52
|
+
self.session_id: Optional[str] = None
|
53
53
|
self.pending_requests = {} # 存储等待响应的请求 {id: Event}
|
54
54
|
self.request_results = {} # 存储请求结果 {id: result}
|
55
55
|
self.notification_handlers = {}
|
@@ -123,13 +123,15 @@ class SSEMcpClient(McpClient):
|
|
123
123
|
self.sse_response = self.session.get(
|
124
124
|
sse_url, stream=True, headers=sse_headers, timeout=30
|
125
125
|
)
|
126
|
-
self.sse_response
|
126
|
+
if self.sse_response:
|
127
|
+
self.sse_response.raise_for_status()
|
127
128
|
|
128
129
|
# 启动事件处理线程
|
129
130
|
self.sse_thread = threading.Thread(
|
130
131
|
target=self._process_sse_events, daemon=True
|
131
132
|
)
|
132
|
-
self.sse_thread
|
133
|
+
if self.sse_thread:
|
134
|
+
self.sse_thread.start()
|
133
135
|
|
134
136
|
except Exception as e:
|
135
137
|
PrettyOutput.print(f"SSE连接失败: {str(e)}", OutputType.ERROR)
|
@@ -2,7 +2,7 @@
|
|
2
2
|
import json
|
3
3
|
import os
|
4
4
|
import subprocess
|
5
|
-
from typing import Any, Dict, List
|
5
|
+
from typing import Any, Dict, List, Optional
|
6
6
|
|
7
7
|
from jarvis.jarvis_mcp import McpClient
|
8
8
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
@@ -17,7 +17,7 @@ class StdioMcpClient(McpClient):
|
|
17
17
|
|
18
18
|
def __init__(self, config: Dict[str, Any]):
|
19
19
|
self.config = config
|
20
|
-
self.process = None
|
20
|
+
self.process: Optional[subprocess.Popen] = None
|
21
21
|
self.protocol_version = "2025-03-26" # MCP协议版本
|
22
22
|
self._start_process()
|
23
23
|
self._initialize()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
import re
|
3
|
-
from typing import Any, Dict, List, Optional, Tuple
|
3
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
4
4
|
|
5
5
|
import yaml
|
6
6
|
|
@@ -89,6 +89,8 @@ content: |2
|
|
89
89
|
Args:
|
90
90
|
content: The content containing send message
|
91
91
|
"""
|
92
|
+
if ot("SEND_MESSAGE") in content and ct("SEND_MESSAGE") not in content:
|
93
|
+
content += "\n" + ct("SEND_MESSAGE")
|
92
94
|
data = re.findall(
|
93
95
|
ot("SEND_MESSAGE") + r"\n(.*?)\n" + ct("SEND_MESSAGE"), content, re.DOTALL
|
94
96
|
)
|
@@ -102,7 +104,7 @@ content: |2
|
|
102
104
|
continue
|
103
105
|
return ret
|
104
106
|
|
105
|
-
def _get_agent(self, name: str) -> Agent
|
107
|
+
def _get_agent(self, name: str) -> Union[Agent, None]:
|
106
108
|
if name in self.agents:
|
107
109
|
return self.agents[name]
|
108
110
|
|
@@ -113,9 +115,9 @@ content: |2
|
|
113
115
|
|
114
116
|
if name != self.main_agent_name and self.original_question:
|
115
117
|
system_prompt = config.get("system_prompt", "")
|
116
|
-
config[
|
117
|
-
|
118
|
-
|
118
|
+
config[
|
119
|
+
"system_prompt"
|
120
|
+
] = f"{system_prompt}\n\n# 原始问题\n{self.original_question}"
|
119
121
|
|
120
122
|
output_handler = config.get("output_handler", [])
|
121
123
|
if len(output_handler) == 0:
|
jarvis/jarvis_platform/base.py
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
import re
|
3
3
|
from abc import ABC, abstractmethod
|
4
|
-
from
|
4
|
+
from types import TracebackType
|
5
|
+
from typing import Generator, List, Optional, Tuple, Type
|
6
|
+
|
7
|
+
from typing_extensions import Self
|
5
8
|
|
6
9
|
from rich import box # type: ignore
|
7
10
|
from rich.live import Live # type: ignore
|
@@ -28,9 +31,19 @@ class BasePlatform(ABC):
|
|
28
31
|
self.suppress_output = True # 添加输出控制标志
|
29
32
|
self.web = False # 添加web属性,默认false
|
30
33
|
self._saved = False
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
+
self.model_group: Optional[str] = None
|
35
|
+
|
36
|
+
def __enter__(self) -> Self:
|
37
|
+
"""Enter context manager"""
|
38
|
+
return self
|
39
|
+
|
40
|
+
def __exit__(
|
41
|
+
self,
|
42
|
+
exc_type: Optional[Type[BaseException]],
|
43
|
+
exc_val: Optional[BaseException],
|
44
|
+
exc_tb: Optional[TracebackType],
|
45
|
+
) -> None:
|
46
|
+
"""Exit context manager"""
|
34
47
|
if not self._saved:
|
35
48
|
self.delete_chat()
|
36
49
|
|
@@ -64,9 +77,11 @@ class BasePlatform(ABC):
|
|
64
77
|
|
65
78
|
input_token_count = get_context_token_count(message)
|
66
79
|
|
67
|
-
if input_token_count > get_max_input_token_count():
|
68
|
-
max_chunk_size =
|
69
|
-
|
80
|
+
if input_token_count > get_max_input_token_count(self.model_group):
|
81
|
+
max_chunk_size = (
|
82
|
+
get_max_input_token_count(self.model_group) - 1024
|
83
|
+
) # 留出一些余量
|
84
|
+
min_chunk_size = get_max_input_token_count(self.model_group) - 2048
|
70
85
|
inputs = split_text_into_chunks(message, max_chunk_size, min_chunk_size)
|
71
86
|
print("📤 正在提交长上下文...")
|
72
87
|
prefix_prompt = f"""
|
@@ -95,14 +110,10 @@ class BasePlatform(ABC):
|
|
95
110
|
):
|
96
111
|
response += trunk
|
97
112
|
|
98
|
-
print(
|
99
|
-
f"📤 提交第{submit_count}部分完成,当前进度:{length}/{len(message)}"
|
100
|
-
)
|
113
|
+
print(f"📤 提交第{submit_count}部分完成,当前进度:{length}/{len(message)}")
|
101
114
|
print("✅ 提交完成")
|
102
115
|
response += "\n" + while_true(
|
103
|
-
lambda: while_success(
|
104
|
-
lambda: self._chat("内容已经全部提供完毕,请根据内容继续"), 5
|
105
|
-
),
|
116
|
+
lambda: while_success(lambda: self._chat("内容已经全部提供完毕,请根据内容继续"), 5),
|
106
117
|
5,
|
107
118
|
)
|
108
119
|
else:
|
@@ -233,6 +244,10 @@ class BasePlatform(ABC):
|
|
233
244
|
"""Set whether to suppress output"""
|
234
245
|
self.suppress_output = suppress
|
235
246
|
|
247
|
+
def set_model_group(self, model_group: Optional[str]):
|
248
|
+
"""Set model group"""
|
249
|
+
self.model_group = model_group
|
250
|
+
|
236
251
|
def set_web(self, web: bool):
|
237
252
|
"""Set web flag"""
|
238
253
|
self.web = web
|
jarvis/jarvis_tools/registry.py
CHANGED
@@ -15,9 +15,10 @@ from jarvis.jarvis_mcp.stdio_mcp_client import StdioMcpClient
|
|
15
15
|
from jarvis.jarvis_mcp.streamable_mcp_client import StreamableMcpClient
|
16
16
|
from jarvis.jarvis_tools.base import Tool
|
17
17
|
from jarvis.jarvis_utils.config import get_data_dir, get_tool_load_dirs
|
18
|
+
from jarvis.jarvis_utils.input import user_confirm
|
18
19
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
19
20
|
from jarvis.jarvis_utils.tag import ct, ot
|
20
|
-
from jarvis.jarvis_utils.utils import is_context_overflow
|
21
|
+
from jarvis.jarvis_utils.utils import is_context_overflow, daily_check_git_updates
|
21
22
|
|
22
23
|
tool_call_help = f"""
|
23
24
|
<tool_system_guide>
|
@@ -106,14 +107,20 @@ arguments:
|
|
106
107
|
|
107
108
|
|
108
109
|
class OutputHandlerProtocol(Protocol):
|
109
|
-
def name(self) -> str:
|
110
|
-
|
111
|
-
def prompt(self) -> str: ...
|
112
|
-
def handle(self, response: str, agent: Any) -> Tuple[bool, Any]: ...
|
110
|
+
def name(self) -> str:
|
111
|
+
...
|
113
112
|
|
113
|
+
def can_handle(self, response: str) -> bool:
|
114
|
+
...
|
114
115
|
|
115
|
-
|
116
|
+
def prompt(self) -> str:
|
117
|
+
...
|
116
118
|
|
119
|
+
def handle(self, response: str, agent: Any) -> Tuple[bool, Any]:
|
120
|
+
...
|
121
|
+
|
122
|
+
|
123
|
+
class ToolRegistry(OutputHandlerProtocol):
|
117
124
|
def name(self) -> str:
|
118
125
|
return "TOOL_CALL"
|
119
126
|
|
@@ -124,14 +131,16 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
124
131
|
"""加载工具"""
|
125
132
|
tools = self.get_all_tools()
|
126
133
|
if tools:
|
127
|
-
tools_prompt = "<tools_section>\n"
|
134
|
+
tools_prompt = f"<tools_section>\n"
|
128
135
|
tools_prompt += " <header>## 可用工具:</header>\n"
|
129
136
|
tools_prompt += " <tools_list>\n"
|
130
137
|
for tool in tools:
|
131
138
|
try:
|
132
139
|
tools_prompt += " <tool>\n"
|
133
140
|
tools_prompt += f" <name>名称: {tool['name']}</name>\n"
|
134
|
-
tools_prompt +=
|
141
|
+
tools_prompt += (
|
142
|
+
f" <description>描述: {tool['description']}</description>\n"
|
143
|
+
)
|
135
144
|
tools_prompt += " <parameters>\n"
|
136
145
|
tools_prompt += " <yaml>|\n"
|
137
146
|
|
@@ -197,9 +206,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
197
206
|
with open(stats_file, "r", encoding="utf-8") as f:
|
198
207
|
return yaml.safe_load(f) or {}
|
199
208
|
except Exception as e:
|
200
|
-
PrettyOutput.print(
|
201
|
-
f"加载工具调用统计失败: {str(e)}", OutputType.WARNING
|
202
|
-
)
|
209
|
+
PrettyOutput.print(f"加载工具调用统计失败: {str(e)}", OutputType.WARNING)
|
203
210
|
return {}
|
204
211
|
|
205
212
|
def _update_tool_stats(self, name: str) -> None:
|
@@ -269,9 +276,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
269
276
|
config = yaml.safe_load(open(file_path, "r", encoding="utf-8"))
|
270
277
|
self.register_mcp_tool_by_config(config)
|
271
278
|
except Exception as e:
|
272
|
-
PrettyOutput.print(
|
273
|
-
f"文件 {file_path} 加载失败: {str(e)}", OutputType.WARNING
|
274
|
-
)
|
279
|
+
PrettyOutput.print(f"文件 {file_path} 加载失败: {str(e)}", OutputType.WARNING)
|
275
280
|
|
276
281
|
def _load_builtin_tools(self) -> None:
|
277
282
|
"""从内置工具目录加载工具"""
|
@@ -287,16 +292,18 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
287
292
|
|
288
293
|
def _load_external_tools(self) -> None:
|
289
294
|
"""从jarvis_data/tools和配置的目录加载外部工具"""
|
290
|
-
tool_dirs = [Path(get_data_dir()) / "tools"] +
|
291
|
-
|
292
|
-
|
295
|
+
tool_dirs = [str(Path(get_data_dir()) / "tools")] + get_tool_load_dirs()
|
296
|
+
|
297
|
+
# --- 全局每日更新检查 ---
|
298
|
+
daily_check_git_updates(tool_dirs, "tools")
|
293
299
|
|
294
300
|
for tool_dir in tool_dirs:
|
295
|
-
|
301
|
+
p_tool_dir = Path(tool_dir)
|
302
|
+
if not p_tool_dir.exists() or not p_tool_dir.is_dir():
|
296
303
|
continue
|
297
304
|
|
298
305
|
# 遍历目录中的所有.py文件
|
299
|
-
for file_path in
|
306
|
+
for file_path in p_tool_dir.glob("*.py"):
|
300
307
|
# 跳过__init__.py
|
301
308
|
if file_path.name == "__init__.py":
|
302
309
|
continue
|
@@ -506,7 +513,6 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
506
513
|
and hasattr(item, "execute")
|
507
514
|
and item.name == module_name
|
508
515
|
):
|
509
|
-
|
510
516
|
if hasattr(item, "check"):
|
511
517
|
if not item.check():
|
512
518
|
continue
|
@@ -565,10 +571,46 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
565
571
|
ot("TOOL_CALL") + r"(.*?)" + ct("TOOL_CALL"), content, re.DOTALL
|
566
572
|
)
|
567
573
|
if not data:
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
574
|
+
# can_handle 确保 ot("TOOL_CALL") 在内容中。
|
575
|
+
# 如果数据为空,则表示 ct("TOOL_CALL") 可能丢失。
|
576
|
+
if ot("TOOL_CALL") in content and ct("TOOL_CALL") not in content:
|
577
|
+
# 尝试通过附加结束标签来修复它
|
578
|
+
fixed_content = content.strip() + f"\n{ct('TOOL_CALL')}"
|
579
|
+
|
580
|
+
# 再次提取,并检查YAML是否有效
|
581
|
+
temp_data = re.findall(
|
582
|
+
ot("TOOL_CALL") + r"(.*?)" + ct("TOOL_CALL"),
|
583
|
+
fixed_content,
|
584
|
+
re.DOTALL,
|
585
|
+
)
|
586
|
+
|
587
|
+
if temp_data:
|
588
|
+
try:
|
589
|
+
yaml.safe_load(temp_data[0]) # Check if valid YAML
|
590
|
+
|
591
|
+
# Ask user for confirmation
|
592
|
+
PrettyOutput.print(
|
593
|
+
f"检测到缺失的 {ct('TOOL_CALL')} 标签,已自动修复。修复后的内容如下:",
|
594
|
+
OutputType.INFO,
|
595
|
+
)
|
596
|
+
PrettyOutput.print(fixed_content, OutputType.TOOL)
|
597
|
+
if user_confirm("这是纠正后的,是否需要执行?", default=True):
|
598
|
+
data = temp_data
|
599
|
+
else:
|
600
|
+
return (
|
601
|
+
{},
|
602
|
+
f"只有{ot('TOOL_CALL')}标签,未找到{ct('TOOL_CALL')}标签,调用格式错误,请检查工具调用格式。\n{tool_call_help}",
|
603
|
+
)
|
604
|
+
except (yaml.YAMLError, EOFError, KeyboardInterrupt):
|
605
|
+
# Even after fixing, it's not valid YAML, or user cancelled.
|
606
|
+
# Fall through to the original error.
|
607
|
+
pass
|
608
|
+
|
609
|
+
if not data:
|
610
|
+
return (
|
611
|
+
{},
|
612
|
+
f"只有{ot('TOOL_CALL')}标签,未找到{ct('TOOL_CALL')}标签,调用格式错误,请检查工具调用格式。\n{tool_call_help}",
|
613
|
+
)
|
572
614
|
ret = []
|
573
615
|
for item in data:
|
574
616
|
try:
|
@@ -683,9 +725,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
683
725
|
"""
|
684
726
|
if len(output.splitlines()) > 60:
|
685
727
|
lines = output.splitlines()
|
686
|
-
return "\n".join(
|
687
|
-
lines[:30] + ["\n...内容太长,已截取前后30行...\n"] + lines[-30:]
|
688
|
-
)
|
728
|
+
return "\n".join(lines[:30] + ["\n...内容太长,已截取前后30行...\n"] + lines[-30:])
|
689
729
|
return output
|
690
730
|
|
691
731
|
def handle_tool_calls(self, tool_call: Dict[str, Any], agent: Any) -> str:
|
@@ -717,7 +757,10 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
717
757
|
)
|
718
758
|
|
719
759
|
# 检查内容是否过大
|
720
|
-
|
760
|
+
model_group = None
|
761
|
+
if agent_instance.model:
|
762
|
+
model_group = agent_instance.model.model_group
|
763
|
+
is_large_content = is_context_overflow(output, model_group)
|
721
764
|
|
722
765
|
if is_large_content:
|
723
766
|
# 创建临时文件
|