jarvis-ai-assistant 0.1.128__py3-none-any.whl → 0.1.130__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of jarvis-ai-assistant might be problematic. Click here for more details.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +26 -31
- jarvis/jarvis_agent/main.py +77 -0
- jarvis/jarvis_c2rust/c2rust.yaml +734 -0
- jarvis/jarvis_code_agent/builtin_input_handler.py +43 -0
- jarvis/jarvis_code_agent/code_agent.py +82 -156
- jarvis/jarvis_code_agent/file_input_handler.py +88 -0
- jarvis/jarvis_code_agent/patch.py +262 -80
- jarvis/jarvis_code_agent/shell_input_handler.py +8 -2
- jarvis/jarvis_dev/main.py +832 -740
- jarvis/jarvis_multi_agent/__init__.py +113 -92
- jarvis/jarvis_platform/registry.py +0 -1
- jarvis/jarvis_tools/create_sub_agent.py +1 -8
- jarvis/jarvis_tools/git_commiter.py +2 -1
- jarvis/jarvis_tools/read_code.py +143 -0
- jarvis/jarvis_tools/registry.py +35 -39
- jarvis/jarvis_tools/tool_generator.py +45 -17
- jarvis/jarvis_utils/__init__.py +17 -17
- jarvis/jarvis_utils/config.py +87 -51
- jarvis/jarvis_utils/embedding.py +49 -48
- jarvis/jarvis_utils/git_utils.py +34 -34
- jarvis/jarvis_utils/globals.py +26 -26
- jarvis/jarvis_utils/input.py +61 -45
- jarvis/jarvis_utils/methodology.py +22 -22
- jarvis/jarvis_utils/output.py +64 -64
- jarvis/jarvis_utils/utils.py +2 -2
- {jarvis_ai_assistant-0.1.128.dist-info → jarvis_ai_assistant-0.1.130.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.1.128.dist-info → jarvis_ai_assistant-0.1.130.dist-info}/RECORD +32 -27
- {jarvis_ai_assistant-0.1.128.dist-info → jarvis_ai_assistant-0.1.130.dist-info}/entry_points.txt +2 -0
- {jarvis_ai_assistant-0.1.128.dist-info → jarvis_ai_assistant-0.1.130.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.128.dist-info → jarvis_ai_assistant-0.1.130.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.128.dist-info → jarvis_ai_assistant-0.1.130.dist-info}/top_level.txt +0 -0
|
@@ -5,92 +5,78 @@ import yaml
|
|
|
5
5
|
|
|
6
6
|
from jarvis.jarvis_agent import Agent
|
|
7
7
|
from jarvis.jarvis_agent.output_handler import OutputHandler
|
|
8
|
+
from jarvis.jarvis_tools.registry import ToolRegistry
|
|
9
|
+
from jarvis.jarvis_utils.input import get_multiline_input
|
|
8
10
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
11
|
+
from jarvis.jarvis_utils.utils import init_env
|
|
9
12
|
|
|
10
13
|
|
|
11
|
-
class AgentConfig:
|
|
12
|
-
def __init__(self, **config):
|
|
13
|
-
self.system_prompt = config.get('system_prompt', '')
|
|
14
|
-
self.name = config.get('name', 'Jarvis')
|
|
15
|
-
self.description = config.get('description', '')
|
|
16
|
-
self.is_sub_agent = config.get('is_sub_agent', False)
|
|
17
|
-
self.output_handler = config.get('output_handler', [])
|
|
18
|
-
self.platform = config.get('platform')
|
|
19
|
-
self.model_name = config.get('model_name')
|
|
20
|
-
self.summary_prompt = config.get('summary_prompt')
|
|
21
|
-
self.auto_complete = config.get('auto_complete', False)
|
|
22
|
-
self.input_handler = config.get('input_handler')
|
|
23
|
-
self.max_context_length = config.get('max_context_length')
|
|
24
|
-
self.execute_tool_confirm = config.get('execute_tool_confirm')
|
|
25
|
-
|
|
26
14
|
class MultiAgent(OutputHandler):
|
|
27
|
-
def __init__(self,
|
|
28
|
-
self.agents_config =
|
|
15
|
+
def __init__(self, agents_config: List[Dict], main_agent_name: str):
|
|
16
|
+
self.agents_config = agents_config
|
|
29
17
|
self.agents = {}
|
|
30
18
|
self.init_agents()
|
|
31
19
|
self.main_agent_name = main_agent_name
|
|
32
20
|
|
|
33
21
|
def prompt(self) -> str:
|
|
34
22
|
return f"""
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
# 📝 消息格式
|
|
23
|
+
# 多智能体协作系统
|
|
24
|
+
|
|
25
|
+
## 身份与角色定位
|
|
26
|
+
- **核心职责**:作为多智能体系统的协调者,通过结构化消息实现高效协作
|
|
27
|
+
- **关键能力**:消息路由、任务分发、结果整合、流程协调
|
|
28
|
+
- **工作范围**:在多个专业智能体之间建立有效沟通渠道
|
|
29
|
+
|
|
30
|
+
## 交互原则与策略
|
|
31
|
+
### 消息处理规范
|
|
32
|
+
- **单一操作原则**:每轮只执行一个操作(工具调用或消息发送)
|
|
33
|
+
- **完整性原则**:确保消息包含所有必要信息,避免歧义
|
|
34
|
+
- **明确性原则**:清晰表达意图、需求和期望结果
|
|
35
|
+
- **上下文保留**:在消息中包含足够的背景信息
|
|
36
|
+
|
|
37
|
+
### 消息格式标准
|
|
52
38
|
```
|
|
53
39
|
<SEND_MESSAGE>
|
|
54
40
|
to: 智能体名称 # 目标智能体名称
|
|
55
41
|
content: |
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
42
|
+
# 消息主题
|
|
43
|
+
|
|
44
|
+
## 背景信息
|
|
45
|
+
[提供必要的上下文和背景]
|
|
46
|
+
|
|
47
|
+
## 具体需求
|
|
48
|
+
[明确表达期望完成的任务]
|
|
49
|
+
|
|
50
|
+
## 相关资源
|
|
51
|
+
[列出相关文档、数据或工具]
|
|
52
|
+
|
|
53
|
+
## 期望结果
|
|
54
|
+
[描述期望的输出格式和内容]
|
|
59
55
|
</SEND_MESSAGE>
|
|
60
56
|
```
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
3. 处理后再进行下一步
|
|
85
|
-
4. 回复消息
|
|
86
|
-
5. 需要时转发任务
|
|
87
|
-
|
|
88
|
-
# 💡 提示
|
|
89
|
-
- 第一个操作将被执行
|
|
90
|
-
- 额外的操作将被忽略
|
|
91
|
-
- 总是先处理响应
|
|
92
|
-
- 需要时发送消息以继续任务
|
|
93
|
-
- 处理并回复收到的消息
|
|
58
|
+
## 协作流程规范
|
|
59
|
+
### 任务分发流程
|
|
60
|
+
1. **需求分析**:理解用户需求并确定最适合的智能体
|
|
61
|
+
2. **任务分解**:将复杂任务分解为可管理的子任务
|
|
62
|
+
3. **精准分发**:根据专长将任务分配给合适的智能体
|
|
63
|
+
4. **结果整合**:收集各智能体的输出并整合为连贯结果
|
|
64
|
+
|
|
65
|
+
### 消息流控制
|
|
66
|
+
1. **单向流动**:发送消息后等待响应,避免消息风暴
|
|
67
|
+
2. **优先级管理**:处理紧急消息优先,保持任务顺序
|
|
68
|
+
3. **状态跟踪**:记录每个任务的当前状态和处理进度
|
|
69
|
+
4. **异常处理**:优雅处理超时、错误和意外响应
|
|
70
|
+
|
|
71
|
+
## 可用智能体资源
|
|
72
|
+
{chr(10).join([f"- **{c['name']}**: {c.get('description', '')}" for c in self.agents_config])}
|
|
73
|
+
|
|
74
|
+
## 最佳实践指南
|
|
75
|
+
1. **任务明确化**:每个消息专注于单一、明确的任务
|
|
76
|
+
2. **信息充分性**:提供足够信息让接收者能独立完成任务
|
|
77
|
+
3. **反馈循环**:建立清晰的反馈机制,及时调整方向
|
|
78
|
+
4. **知识共享**:确保关键信息在相关智能体间共享
|
|
79
|
+
5. **协作效率**:避免不必要的消息传递,减少协调开销
|
|
94
80
|
"""
|
|
95
81
|
|
|
96
82
|
def can_handle(self, response: str) -> bool:
|
|
@@ -128,29 +114,22 @@ content: |
|
|
|
128
114
|
return ret
|
|
129
115
|
|
|
130
116
|
def init_agents(self):
|
|
131
|
-
for
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
is_sub_agent=agent_config.is_sub_agent,
|
|
146
|
-
output_handler=[*agent_config.output_handler, self],
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
self.agents[agent_config.name] = agent
|
|
150
|
-
|
|
151
|
-
def run(self, user_input: str, file_list: Optional[List[str]] = None) -> str:
|
|
117
|
+
for config in self.agents_config:
|
|
118
|
+
output_handler = config.get('output_handler', [])
|
|
119
|
+
if len(output_handler) == 0:
|
|
120
|
+
output_handler = [
|
|
121
|
+
ToolRegistry(),
|
|
122
|
+
self,
|
|
123
|
+
]
|
|
124
|
+
else:
|
|
125
|
+
output_handler.append(self)
|
|
126
|
+
config['output_handler'] = output_handler
|
|
127
|
+
agent = Agent(**config)
|
|
128
|
+
self.agents[config['name']] = agent
|
|
129
|
+
|
|
130
|
+
def run(self, user_input: str) -> str:
|
|
152
131
|
last_agent = self.main_agent_name
|
|
153
|
-
msg = self.agents[self.main_agent_name].run(user_input
|
|
132
|
+
msg = self.agents[self.main_agent_name].run(user_input)
|
|
154
133
|
while msg:
|
|
155
134
|
if isinstance(msg, str):
|
|
156
135
|
return msg
|
|
@@ -167,4 +146,46 @@ content: {msg['content']}
|
|
|
167
146
|
PrettyOutput.print(f"{last_agent} 正在向 {msg['to']} 发送消息...", OutputType.INFO)
|
|
168
147
|
last_agent = self.agents[msg['to']].name
|
|
169
148
|
msg = self.agents[msg['to']].run(prompt)
|
|
170
|
-
return ""
|
|
149
|
+
return ""
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def main():
|
|
153
|
+
"""从YAML配置文件初始化并运行多智能体系统
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
最终处理结果
|
|
157
|
+
"""
|
|
158
|
+
init_env()
|
|
159
|
+
import argparse
|
|
160
|
+
parser = argparse.ArgumentParser(description="多智能体系统启动器")
|
|
161
|
+
parser.add_argument("--config", "-c", required=True, help="YAML配置文件路径")
|
|
162
|
+
parser.add_argument("--input", "-i", help="用户输入(可选)")
|
|
163
|
+
args = parser.parse_args()
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
with open(args.config, 'r') as f:
|
|
167
|
+
config_data = yaml.safe_load(f)
|
|
168
|
+
|
|
169
|
+
# 获取agents配置
|
|
170
|
+
agents_config = config_data.get('agents', [])
|
|
171
|
+
|
|
172
|
+
main_agent_name = config_data.get('main_agent', '')
|
|
173
|
+
if not main_agent_name:
|
|
174
|
+
raise ValueError("必须指定main_agent作为主智能体")
|
|
175
|
+
|
|
176
|
+
# 创建并运行多智能体系统
|
|
177
|
+
multi_agent = MultiAgent(agents_config, main_agent_name)
|
|
178
|
+
user_input = args.input if args.input is not None else get_multiline_input("请输入内容(输入空行结束):")
|
|
179
|
+
if user_input == "":
|
|
180
|
+
return
|
|
181
|
+
return multi_agent.run(user_input)
|
|
182
|
+
|
|
183
|
+
except yaml.YAMLError as e:
|
|
184
|
+
raise ValueError(f"YAML配置文件解析错误: {str(e)}")
|
|
185
|
+
except Exception as e:
|
|
186
|
+
raise RuntimeError(f"多智能体系统初始化失败: {str(e)}")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
if __name__ == "__main__":
|
|
190
|
+
result = main()
|
|
191
|
+
|
|
@@ -30,12 +30,6 @@ class SubAgentTool:
|
|
|
30
30
|
"description": "任务的完成目标",
|
|
31
31
|
"default": ""
|
|
32
32
|
},
|
|
33
|
-
"files": {
|
|
34
|
-
"type": "array",
|
|
35
|
-
"items": {"type": "string"},
|
|
36
|
-
"description": "相关文件路径列表,用于文件问答和处理",
|
|
37
|
-
"default": []
|
|
38
|
-
}
|
|
39
33
|
},
|
|
40
34
|
"required": ["agent_name", "task", "context", "goal"]
|
|
41
35
|
}
|
|
@@ -48,7 +42,6 @@ class SubAgentTool:
|
|
|
48
42
|
task = args["task"]
|
|
49
43
|
context = args.get("context", "")
|
|
50
44
|
goal = args.get("goal", "")
|
|
51
|
-
files = args.get("files", [])
|
|
52
45
|
|
|
53
46
|
PrettyOutput.print(f"创建子代理: {agent_name}", OutputType.INFO)
|
|
54
47
|
|
|
@@ -69,7 +62,7 @@ class SubAgentTool:
|
|
|
69
62
|
|
|
70
63
|
# Run sub-agent, pass file list
|
|
71
64
|
PrettyOutput.print("子代理开始执行任务...", OutputType.INFO)
|
|
72
|
-
result = sub_agent.run(task_description
|
|
65
|
+
result = sub_agent.run(task_description)
|
|
73
66
|
|
|
74
67
|
return {
|
|
75
68
|
"success": True,
|
|
@@ -9,7 +9,7 @@ from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
|
9
9
|
import sys
|
|
10
10
|
import argparse
|
|
11
11
|
|
|
12
|
-
from jarvis.jarvis_utils.git_utils import has_uncommitted_changes
|
|
12
|
+
from jarvis.jarvis_utils.git_utils import find_git_root, has_uncommitted_changes
|
|
13
13
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
14
14
|
from jarvis.jarvis_utils.utils import init_env
|
|
15
15
|
|
|
@@ -51,6 +51,7 @@ class GitCommitTool:
|
|
|
51
51
|
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
52
52
|
"""Execute automatic commit process with support for multi-line messages and special characters"""
|
|
53
53
|
try:
|
|
54
|
+
find_git_root()
|
|
54
55
|
if not has_uncommitted_changes():
|
|
55
56
|
PrettyOutput.print("没有未提交的更改", OutputType.SUCCESS)
|
|
56
57
|
return {"success": True, "stdout": "No changes to commit", "stderr": ""}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from yaspin import yaspin
|
|
5
|
+
|
|
6
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
7
|
+
|
|
8
|
+
class ReadCodeTool:
|
|
9
|
+
name = "read_code"
|
|
10
|
+
description = "用于读取代码文件并在每行前添加行号的工具"
|
|
11
|
+
parameters = {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"properties": {
|
|
14
|
+
"files": {
|
|
15
|
+
"type": "array",
|
|
16
|
+
"items": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"properties": {
|
|
19
|
+
"path": {"type": "string"},
|
|
20
|
+
"start_line": {"type": "number", "default": 1},
|
|
21
|
+
"end_line": {"type": "number", "default": -1}
|
|
22
|
+
},
|
|
23
|
+
"required": ["path"]
|
|
24
|
+
},
|
|
25
|
+
"description": "要读取的文件列表"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"required": ["files"]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def _handle_single_file(self, filepath: str, start_line: int = 1, end_line: int = -1) -> Dict[str, Any]:
|
|
32
|
+
try:
|
|
33
|
+
abs_path = os.path.abspath(filepath)
|
|
34
|
+
with yaspin(text=f"正在读取文件: {abs_path}...", color="cyan") as spinner:
|
|
35
|
+
# 文件存在性检查
|
|
36
|
+
if not os.path.exists(abs_path):
|
|
37
|
+
return {
|
|
38
|
+
"success": False,
|
|
39
|
+
"stdout": "",
|
|
40
|
+
"stderr": f"文件不存在: {abs_path}"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# 文件大小限制检查(10MB)
|
|
44
|
+
if os.path.getsize(abs_path) > 10 * 1024 * 1024:
|
|
45
|
+
return {
|
|
46
|
+
"success": False,
|
|
47
|
+
"stdout": "",
|
|
48
|
+
"stderr": "文件过大 (>10MB)"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# 读取文件内容
|
|
52
|
+
with open(abs_path, 'r', encoding='utf-8') as f:
|
|
53
|
+
lines = f.readlines()
|
|
54
|
+
|
|
55
|
+
total_lines = len(lines)
|
|
56
|
+
|
|
57
|
+
# 处理特殊值-1表示文件末尾
|
|
58
|
+
if end_line == -1:
|
|
59
|
+
end_line = total_lines
|
|
60
|
+
else:
|
|
61
|
+
end_line = max(1, min(end_line, total_lines)) if end_line >= 0 else total_lines + end_line + 1
|
|
62
|
+
|
|
63
|
+
start_line = max(1, min(start_line, total_lines)) if start_line >= 0 else total_lines + start_line + 1
|
|
64
|
+
|
|
65
|
+
if start_line > end_line:
|
|
66
|
+
spinner.fail("❌")
|
|
67
|
+
return {
|
|
68
|
+
"success": False,
|
|
69
|
+
"stdout": "",
|
|
70
|
+
"stderr": f"无效的行范围 [{start_line}-{end_line}] (总行数: {total_lines})"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# 添加行号并构建输出内容
|
|
74
|
+
selected_lines = lines[start_line-1:end_line]
|
|
75
|
+
numbered_content = "".join(
|
|
76
|
+
[f"{i:4d} | {line}"
|
|
77
|
+
for i, line in enumerate(selected_lines, start=start_line)]
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# 构建输出格式
|
|
81
|
+
output = (
|
|
82
|
+
f"\n🔍 文件: {abs_path}\n"
|
|
83
|
+
f"📄 原始行号: {start_line}-{end_line} (共{end_line - start_line + 1}行) | 显示行号: 1-{len(selected_lines)}\n\n"
|
|
84
|
+
f"{numbered_content}\n"
|
|
85
|
+
f"{'='*80}\n"
|
|
86
|
+
)
|
|
87
|
+
spinner.text = f"文件读取完成: {abs_path}"
|
|
88
|
+
spinner.ok("✅")
|
|
89
|
+
return {
|
|
90
|
+
"success": True,
|
|
91
|
+
"stdout": output,
|
|
92
|
+
"stderr": ""
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
PrettyOutput.print(str(e), OutputType.ERROR)
|
|
97
|
+
return {
|
|
98
|
+
"success": False,
|
|
99
|
+
"stdout": "",
|
|
100
|
+
"stderr": f"文件读取失败: {str(e)}"
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
104
|
+
try:
|
|
105
|
+
if "files" not in args or not isinstance(args["files"], list):
|
|
106
|
+
return {
|
|
107
|
+
"success": False,
|
|
108
|
+
"stdout": "",
|
|
109
|
+
"stderr": "参数中必须包含文件列表"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
all_outputs = []
|
|
113
|
+
overall_success = True
|
|
114
|
+
|
|
115
|
+
for file_info in args["files"]:
|
|
116
|
+
if not isinstance(file_info, dict) or "path" not in file_info:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
result = self._handle_single_file(
|
|
120
|
+
file_info["path"].strip(),
|
|
121
|
+
file_info.get("start_line", 1),
|
|
122
|
+
file_info.get("end_line", -1)
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if result["success"]:
|
|
126
|
+
all_outputs.append(result["stdout"])
|
|
127
|
+
else:
|
|
128
|
+
all_outputs.append(f"❌ {file_info['path']}: {result['stderr']}")
|
|
129
|
+
overall_success = False
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
"success": overall_success,
|
|
133
|
+
"stdout": "\n".join(all_outputs),
|
|
134
|
+
"stderr": ""
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
except Exception as e:
|
|
138
|
+
PrettyOutput.print(str(e), OutputType.ERROR)
|
|
139
|
+
return {
|
|
140
|
+
"success": False,
|
|
141
|
+
"stdout": "",
|
|
142
|
+
"stderr": f"代码读取失败: {str(e)}"
|
|
143
|
+
}
|
jarvis/jarvis_tools/registry.py
CHANGED
|
@@ -354,45 +354,41 @@ arguments:
|
|
|
354
354
|
if result["success"]:
|
|
355
355
|
# If the output exceeds 4k characters, use a large model to summarize
|
|
356
356
|
if get_context_token_count(output) > self.max_token_count:
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
spinner.text = "总结失败"
|
|
393
|
-
spinner.fail("❌")
|
|
394
|
-
PrettyOutput.print(f"总结失败: {str(e)}", OutputType.ERROR)
|
|
395
|
-
output = f"输出过长 ({len(output)} 字符),建议查看原始输出。\n前300字符预览:\n{output[:300]}..."
|
|
357
|
+
PrettyOutput.section("输出过长,正在总结...", OutputType.SYSTEM)
|
|
358
|
+
try:
|
|
359
|
+
|
|
360
|
+
model = PlatformRegistry.get_global_platform_registry().get_normal_platform()
|
|
361
|
+
model.set_suppress_output(False)
|
|
362
|
+
# If the output exceeds the maximum context length, only take the last part
|
|
363
|
+
max_count = self.max_token_count
|
|
364
|
+
if get_context_token_count(output) > max_count:
|
|
365
|
+
output_to_summarize = output[-max_count:]
|
|
366
|
+
truncation_notice = f"\n(注意:由于输出过长,仅总结最后 {max_count} 个字符)"
|
|
367
|
+
else:
|
|
368
|
+
output_to_summarize = output
|
|
369
|
+
truncation_notice = ""
|
|
370
|
+
|
|
371
|
+
prompt = f"""请总结以下工具的执行结果,提取关键信息和重要结果。注意:
|
|
372
|
+
1. 保留所有重要的数值、路径、错误信息等
|
|
373
|
+
2. 保持结果的准确性
|
|
374
|
+
3. 用简洁的语言描述主要内容
|
|
375
|
+
4. 如果有错误信息,确保包含在总结中
|
|
376
|
+
|
|
377
|
+
工具名称: {name}
|
|
378
|
+
执行结果:
|
|
379
|
+
{output_to_summarize}
|
|
380
|
+
|
|
381
|
+
请提供总结:"""
|
|
382
|
+
|
|
383
|
+
summary = model.chat_until_success(prompt)
|
|
384
|
+
output = f"""--- 原始输出过长,以下是总结 ---{truncation_notice}
|
|
385
|
+
|
|
386
|
+
{summary}
|
|
387
|
+
|
|
388
|
+
--- 总结结束 ---"""
|
|
389
|
+
except Exception as e:
|
|
390
|
+
PrettyOutput.print(f"总结失败: {str(e)}", OutputType.ERROR)
|
|
391
|
+
output = f"输出过长 ({len(output)} 字符),建议查看原始输出。\n前300字符预览:\n{output[:300]}..."
|
|
396
392
|
return output
|
|
397
393
|
|
|
398
394
|
except Exception as e:
|
|
@@ -9,6 +9,8 @@ from yaspin import yaspin
|
|
|
9
9
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
10
10
|
|
|
11
11
|
class ToolGenerator:
|
|
12
|
+
"""工具生成器类,用于自动创建与Jarvis系统集成的新工具"""
|
|
13
|
+
|
|
12
14
|
name = "tool_generator"
|
|
13
15
|
description = "使用LLM自动生成与系统集成的新工具"
|
|
14
16
|
parameters = {
|
|
@@ -31,8 +33,14 @@ class ToolGenerator:
|
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
def execute(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
+
"""
|
|
37
|
+
执行工具生成过程
|
|
38
|
+
Args:
|
|
39
|
+
arguments: 包含工具生成所需参数的字典
|
|
40
|
+
Returns:
|
|
41
|
+
包含执行结果的字典,包含success、stdout和stderr字段
|
|
42
|
+
"""
|
|
43
|
+
# 获取代码生成平台实例
|
|
36
44
|
model = PlatformRegistry.get_global_platform_registry().get_codegen_platform()
|
|
37
45
|
|
|
38
46
|
try:
|
|
@@ -40,37 +48,37 @@ class ToolGenerator:
|
|
|
40
48
|
description = arguments["description"]
|
|
41
49
|
input_spec = arguments["input_spec"]
|
|
42
50
|
|
|
43
|
-
#
|
|
51
|
+
# 使用LLM生成工具实现代码
|
|
44
52
|
with yaspin(text="正在生成工具...", color="cyan") as spinner:
|
|
45
53
|
prompt = self._create_prompt(tool_name, description, input_spec)
|
|
46
54
|
llm_response = model.chat_until_success(prompt)
|
|
47
55
|
spinner.text = "工具生成完成"
|
|
48
56
|
spinner.ok("✅")
|
|
49
57
|
|
|
50
|
-
#
|
|
58
|
+
# 从LLM响应中提取实现代码
|
|
51
59
|
with yaspin(text="正在提取工具实现...", color="cyan") as spinner:
|
|
52
60
|
implementation = self._extract_code(llm_response)
|
|
53
61
|
if not implementation:
|
|
54
62
|
return {
|
|
55
63
|
"success": False,
|
|
56
64
|
"stdout": "",
|
|
57
|
-
"stderr": "
|
|
65
|
+
"stderr": "无法从LLM响应中提取有效的Python代码"
|
|
58
66
|
}
|
|
59
67
|
spinner.text = "工具实现提取完成"
|
|
60
68
|
spinner.ok("✅")
|
|
61
69
|
|
|
62
|
-
#
|
|
70
|
+
# 验证生成的工具代码是否符合返回值格式要求
|
|
63
71
|
with yaspin(text="正在验证工具返回值格式...", color="cyan") as spinner:
|
|
64
72
|
if not self._validate_return_value_format(implementation):
|
|
65
73
|
return {
|
|
66
74
|
"success": False,
|
|
67
75
|
"stdout": "",
|
|
68
|
-
"stderr": "
|
|
76
|
+
"stderr": "生成的工具不符合要求的返回值格式"
|
|
69
77
|
}
|
|
70
78
|
spinner.text = "工具返回值格式验证完成"
|
|
71
79
|
spinner.ok("✅")
|
|
72
80
|
|
|
73
|
-
#
|
|
81
|
+
# 保存生成的新工具
|
|
74
82
|
with yaspin(text="正在保存工具...", color="cyan") as spinner:
|
|
75
83
|
tools_dir = Path.home() / ".jarvis" / "tools"
|
|
76
84
|
tools_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -83,7 +91,7 @@ class ToolGenerator:
|
|
|
83
91
|
|
|
84
92
|
return {
|
|
85
93
|
"success": True,
|
|
86
|
-
"stdout": f"
|
|
94
|
+
"stdout": f"工具成功生成于: {tool_file}",
|
|
87
95
|
"stderr": ""
|
|
88
96
|
}
|
|
89
97
|
|
|
@@ -91,11 +99,19 @@ class ToolGenerator:
|
|
|
91
99
|
return {
|
|
92
100
|
"success": False,
|
|
93
101
|
"stdout": "",
|
|
94
|
-
"stderr": f"
|
|
102
|
+
"stderr": f"工具生成失败: {str(e)}"
|
|
95
103
|
}
|
|
96
104
|
|
|
97
105
|
def _create_prompt(self, tool_name: str, description: str, input_spec: str) -> str:
|
|
98
|
-
"""
|
|
106
|
+
"""
|
|
107
|
+
创建用于工具生成的LLM提示
|
|
108
|
+
Args:
|
|
109
|
+
tool_name: 工具名称
|
|
110
|
+
description: 工具描述
|
|
111
|
+
input_spec: 输入规范
|
|
112
|
+
Returns:
|
|
113
|
+
格式化后的提示字符串
|
|
114
|
+
"""
|
|
99
115
|
example_code = '''
|
|
100
116
|
<TOOL>
|
|
101
117
|
from typing import Dict, Any
|
|
@@ -176,20 +192,32 @@ class CustomTool:
|
|
|
176
192
|
'''
|
|
177
193
|
|
|
178
194
|
def _extract_code(self, response: str) -> str:
|
|
179
|
-
"""
|
|
180
|
-
|
|
195
|
+
"""
|
|
196
|
+
从LLM响应中提取Python代码
|
|
197
|
+
Args:
|
|
198
|
+
response: LLM的响应字符串
|
|
199
|
+
Returns:
|
|
200
|
+
提取到的Python代码字符串
|
|
201
|
+
"""
|
|
202
|
+
# 查找第一个<TOOL>和</TOOL>标签之间的内容
|
|
181
203
|
sm = re.search(r'<TOOL>(.*?)</TOOL>', response, re.DOTALL)
|
|
182
204
|
if sm:
|
|
183
205
|
return sm.group(1)
|
|
184
206
|
return ""
|
|
185
207
|
|
|
186
208
|
def _validate_return_value_format(self, code: str) -> bool:
|
|
187
|
-
"""
|
|
209
|
+
"""
|
|
210
|
+
验证execute方法的返回值格式是否正确
|
|
211
|
+
Args:
|
|
212
|
+
code: 要验证的代码字符串
|
|
213
|
+
Returns:
|
|
214
|
+
布尔值,表示格式是否正确
|
|
215
|
+
"""
|
|
188
216
|
required_fields = ["success", "stdout", "stderr"]
|
|
189
|
-
#
|
|
217
|
+
# 检查execute方法是否存在
|
|
190
218
|
if "def execute(self, args: Dict) -> Dict:" not in code and \
|
|
191
219
|
"def execute(self, args: Dict) -> Dict[str, Any]:" not in code:
|
|
192
220
|
return False
|
|
193
221
|
|
|
194
|
-
#
|
|
195
|
-
return all(field in code for field in required_fields)
|
|
222
|
+
# 检查返回值中是否包含所有必需字段
|
|
223
|
+
return all(field in code for field in required_fields)
|