jarvis-ai-assistant 0.1.32__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- jarvis/__init__.py +3 -0
- jarvis/__pycache__/__init__.cpython-313.pyc +0 -0
- jarvis/__pycache__/agent.cpython-313.pyc +0 -0
- jarvis/__pycache__/main.cpython-313.pyc +0 -0
- jarvis/__pycache__/models.cpython-313.pyc +0 -0
- jarvis/__pycache__/tools.cpython-313.pyc +0 -0
- jarvis/__pycache__/utils.cpython-313.pyc +0 -0
- jarvis/__pycache__/zte_llm.cpython-313.pyc +0 -0
- jarvis/agent.py +289 -0
- jarvis/main.py +148 -0
- jarvis/models/__init__.py +3 -0
- jarvis/models/__pycache__/__init__.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/base.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/kimi.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/openai.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/oyi.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/registry.cpython-313.pyc +0 -0
- jarvis/models/base.py +39 -0
- jarvis/models/kimi.py +389 -0
- jarvis/models/openai.py +96 -0
- jarvis/models/oyi.py +271 -0
- jarvis/models/registry.py +199 -0
- jarvis/tools/__init__.py +5 -0
- jarvis/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/base.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/bing_search.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/calculator.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/calculator_tool.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/file_ops.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/generator.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/methodology.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/python_script.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/rag.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/registry.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/search.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/shell.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/sub_agent.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/user_confirmation.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/user_input.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/user_interaction.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/webpage.cpython-313.pyc +0 -0
- jarvis/tools/base.py +23 -0
- jarvis/tools/file_ops.py +110 -0
- jarvis/tools/generator.py +172 -0
- jarvis/tools/methodology.py +145 -0
- jarvis/tools/registry.py +183 -0
- jarvis/tools/shell.py +78 -0
- jarvis/tools/sub_agent.py +82 -0
- jarvis/utils.py +202 -0
- jarvis_ai_assistant-0.1.32.dist-info/LICENSE +21 -0
- jarvis_ai_assistant-0.1.32.dist-info/METADATA +345 -0
- jarvis_ai_assistant-0.1.32.dist-info/RECORD +55 -0
- jarvis_ai_assistant-0.1.32.dist-info/WHEEL +5 -0
- jarvis_ai_assistant-0.1.32.dist-info/entry_points.txt +2 -0
- jarvis_ai_assistant-0.1.32.dist-info/top_level.txt +1 -0
@@ -0,0 +1,172 @@
|
|
1
|
+
from typing import Dict, Any
|
2
|
+
from pathlib import Path
|
3
|
+
from jarvis.models.registry import ModelRegistry
|
4
|
+
from jarvis.tools.registry import ToolRegistry
|
5
|
+
from jarvis.utils import OutputType, PrettyOutput
|
6
|
+
|
7
|
+
class ToolGeneratorTool:
|
8
|
+
name = "generate_tool"
|
9
|
+
description = "生成新的工具代码并自动注册到Jarvis,自动扩充Jarvis的能力"
|
10
|
+
parameters = {
|
11
|
+
"type": "object",
|
12
|
+
"properties": {
|
13
|
+
"tool_name": {
|
14
|
+
"type": "string",
|
15
|
+
"description": "工具的名称(snake_case格式)"
|
16
|
+
},
|
17
|
+
"class_name": {
|
18
|
+
"type": "string",
|
19
|
+
"description": "工具类的名称(PascalCase格式)"
|
20
|
+
},
|
21
|
+
"description": {
|
22
|
+
"type": "string",
|
23
|
+
"description": "工具的功能描述"
|
24
|
+
},
|
25
|
+
"parameters": {
|
26
|
+
"type": "object",
|
27
|
+
"description": "工具参数的JSON Schema定义"
|
28
|
+
}
|
29
|
+
},
|
30
|
+
"required": ["tool_name", "class_name", "description", "parameters"]
|
31
|
+
}
|
32
|
+
|
33
|
+
def __init__(self):
|
34
|
+
"""初始化工具生成器
|
35
|
+
"""
|
36
|
+
# 设置工具目录
|
37
|
+
self.tools_dir = Path.home() / '.jarvis_tools'
|
38
|
+
|
39
|
+
# 确保工具目录存在
|
40
|
+
self.tools_dir.mkdir(parents=True, exist_ok=True)
|
41
|
+
|
42
|
+
def _generate_tool_code(self, tool_name: str, class_name: str, description: str, parameters: Dict) -> str:
|
43
|
+
"""使用大模型生成工具代码"""
|
44
|
+
model = ModelRegistry.get_global_model()
|
45
|
+
|
46
|
+
prompt = f"""请生成一个Python工具类的代码,要求如下,除了代码,不要输出任何内容:
|
47
|
+
|
48
|
+
1. 类名: {class_name}
|
49
|
+
2. 工具名称: {tool_name}
|
50
|
+
3. 功能描述: {description}
|
51
|
+
4. 参数定义: {parameters}
|
52
|
+
|
53
|
+
严格按照以下格式生成代码(各函数的参数和返回值一定要与示例一致):
|
54
|
+
|
55
|
+
```python
|
56
|
+
from typing import Dict, Any, Protocol, Optional
|
57
|
+
from jarvis.utils import OutputType, PrettyOutput
|
58
|
+
from jarvis.models.registry import ModelRegistry
|
59
|
+
|
60
|
+
class ExampleTool:
|
61
|
+
name = "example_tool"
|
62
|
+
description = "示例工具"
|
63
|
+
parameters = {{
|
64
|
+
"type": "object",
|
65
|
+
"properties": {{
|
66
|
+
"param1": {{"type": "string"}}
|
67
|
+
}},
|
68
|
+
"required": ["param1"]
|
69
|
+
}}
|
70
|
+
|
71
|
+
def __init__(self):
|
72
|
+
self.model = ModelRegistry.get_global_model()
|
73
|
+
|
74
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
75
|
+
try:
|
76
|
+
# 验证参数示例
|
77
|
+
if "param1" not in args:
|
78
|
+
return {{"success": False, "error": "缺少必需参数: param1"}}
|
79
|
+
|
80
|
+
# 记录操作示例
|
81
|
+
PrettyOutput.print(f"处理参数: {{args['param1']}}", OutputType.INFO)
|
82
|
+
|
83
|
+
# 使用大模型示例
|
84
|
+
response = self.model.chat("prompt")
|
85
|
+
|
86
|
+
# 实现具体功能
|
87
|
+
result = "处理结果"
|
88
|
+
|
89
|
+
return {{
|
90
|
+
"success": True,
|
91
|
+
"stdout": result,
|
92
|
+
"stderr": ""
|
93
|
+
}}
|
94
|
+
except Exception as e:
|
95
|
+
PrettyOutput.print(str(e), OutputType.ERROR)
|
96
|
+
return {{
|
97
|
+
"success": False,
|
98
|
+
"error": str(e)
|
99
|
+
}}
|
100
|
+
```"""
|
101
|
+
|
102
|
+
# 调用模型生成代码
|
103
|
+
response = model.chat(prompt)
|
104
|
+
model.delete_chat()
|
105
|
+
|
106
|
+
# 提取代码块
|
107
|
+
code_start = response.find("```python")
|
108
|
+
code_end = response.find("```", code_start + 9)
|
109
|
+
|
110
|
+
if code_start == -1 or code_end == -1:
|
111
|
+
# 如果没有找到代码块标记,假设整个响应都是代码
|
112
|
+
return response
|
113
|
+
|
114
|
+
# 提取代码块内容(去掉```python和```标记)
|
115
|
+
code = response[code_start + 9:code_end].strip()
|
116
|
+
return code
|
117
|
+
|
118
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
119
|
+
"""生成工具代码"""
|
120
|
+
try:
|
121
|
+
tool_name = args["tool_name"]
|
122
|
+
class_name = args["class_name"]
|
123
|
+
description = args["description"]
|
124
|
+
parameters = args["parameters"]
|
125
|
+
|
126
|
+
PrettyOutput.print(f"开始生成工具: {tool_name}", OutputType.INFO)
|
127
|
+
|
128
|
+
# 生成工具代码
|
129
|
+
tool_code = self._generate_tool_code(
|
130
|
+
tool_name,
|
131
|
+
class_name,
|
132
|
+
description,
|
133
|
+
parameters
|
134
|
+
)
|
135
|
+
|
136
|
+
# 获取工具文件路径
|
137
|
+
tool_file = self.tools_dir / f"{tool_name}.py"
|
138
|
+
|
139
|
+
# 写入工具文件
|
140
|
+
with open(tool_file, "w", encoding="utf-8") as f:
|
141
|
+
f.write(tool_code)
|
142
|
+
|
143
|
+
# 创建或更新 __init__.py
|
144
|
+
init_file = self.tools_dir / "__init__.py"
|
145
|
+
if not init_file.exists():
|
146
|
+
with open(init_file, "w", encoding="utf-8") as f:
|
147
|
+
f.write("# Jarvis Tools\n")
|
148
|
+
|
149
|
+
# 注册工具
|
150
|
+
success = ToolRegistry.get_global_tool_registry().register_tool_by_file(tool_file)
|
151
|
+
if not success:
|
152
|
+
return {
|
153
|
+
"success": False,
|
154
|
+
"error": "工具生成成功但注册失败"
|
155
|
+
}
|
156
|
+
|
157
|
+
return {
|
158
|
+
"success": True,
|
159
|
+
"stdout": f"工具已生成并注册到Jarvis\n"
|
160
|
+
f"工具目录: {self.tools_dir}\n"
|
161
|
+
f"工具名称: {tool_name}\n"
|
162
|
+
f"工具描述: {description}\n"
|
163
|
+
f"工具参数: {parameters}",
|
164
|
+
"stderr": ""
|
165
|
+
}
|
166
|
+
|
167
|
+
except Exception as e:
|
168
|
+
PrettyOutput.print(str(e), OutputType.ERROR)
|
169
|
+
return {
|
170
|
+
"success": False,
|
171
|
+
"error": f"生成工具失败: {str(e)}"
|
172
|
+
}
|
@@ -0,0 +1,145 @@
|
|
1
|
+
import os
|
2
|
+
import yaml
|
3
|
+
from typing import Dict, Optional, Any
|
4
|
+
from jarvis.utils import OutputType, PrettyOutput
|
5
|
+
|
6
|
+
|
7
|
+
class MethodologyTool:
|
8
|
+
"""经验管理工具"""
|
9
|
+
|
10
|
+
name = "methodology"
|
11
|
+
description = "管理问题处理方法论,支持添加、更新、删除操作"
|
12
|
+
parameters = {
|
13
|
+
"type": "object",
|
14
|
+
"properties": {
|
15
|
+
"operation": {
|
16
|
+
"type": "string",
|
17
|
+
"description": "操作类型 (delete/update/add)",
|
18
|
+
"enum": ["delete", "update", "add"]
|
19
|
+
},
|
20
|
+
"problem_type": {
|
21
|
+
"type": "string",
|
22
|
+
"description": "问题类型,例如:code_review, bug_fix 等"
|
23
|
+
},
|
24
|
+
"content": {
|
25
|
+
"type": "string",
|
26
|
+
"description": "方法论内容 (update/add 时必需)",
|
27
|
+
"optional": True
|
28
|
+
}
|
29
|
+
},
|
30
|
+
"required": ["operation", "problem_type"]
|
31
|
+
}
|
32
|
+
|
33
|
+
def __init__(self):
|
34
|
+
"""初始化经验管理工具"""
|
35
|
+
self.methodology_file = os.path.expanduser("~/.jarvis_methodology")
|
36
|
+
self._ensure_file_exists()
|
37
|
+
|
38
|
+
def _ensure_file_exists(self):
|
39
|
+
"""确保方法论文件存在"""
|
40
|
+
if not os.path.exists(self.methodology_file):
|
41
|
+
try:
|
42
|
+
with open(self.methodology_file, 'w', encoding='utf-8') as f:
|
43
|
+
yaml.safe_dump({}, f, allow_unicode=True)
|
44
|
+
except Exception as e:
|
45
|
+
PrettyOutput.print(f"创建方法论文件失败: {str(e)}", OutputType.ERROR)
|
46
|
+
|
47
|
+
def _load_methodologies(self) -> Dict:
|
48
|
+
"""加载所有方法论"""
|
49
|
+
try:
|
50
|
+
with open(self.methodology_file, 'r', encoding='utf-8') as f:
|
51
|
+
return yaml.safe_load(f) or {}
|
52
|
+
except Exception as e:
|
53
|
+
PrettyOutput.print(f"加载方法论失败: {str(e)}", OutputType.ERROR)
|
54
|
+
return {}
|
55
|
+
|
56
|
+
def _save_methodologies(self, methodologies: Dict):
|
57
|
+
"""保存所有方法论"""
|
58
|
+
try:
|
59
|
+
with open(self.methodology_file, 'w', encoding='utf-8') as f:
|
60
|
+
yaml.safe_dump(methodologies, f, allow_unicode=True)
|
61
|
+
except Exception as e:
|
62
|
+
PrettyOutput.print(f"保存方法论失败: {str(e)}", OutputType.ERROR)
|
63
|
+
|
64
|
+
def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
65
|
+
"""执行方法论管理操作
|
66
|
+
|
67
|
+
Args:
|
68
|
+
args: 包含操作参数的字典
|
69
|
+
- operation: 操作类型 (delete/update/add)
|
70
|
+
- problem_type: 问题类型
|
71
|
+
- content: 方法论内容 (update/add 时必需)
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
Dict[str, Any]: 包含执行结果的字典
|
75
|
+
"""
|
76
|
+
operation = args.get("operation")
|
77
|
+
problem_type = args.get("problem_type")
|
78
|
+
content = args.get("content")
|
79
|
+
|
80
|
+
if not operation or not problem_type:
|
81
|
+
return {
|
82
|
+
"success": False,
|
83
|
+
"error": "缺少必要参数: operation 和 problem_type"
|
84
|
+
}
|
85
|
+
|
86
|
+
methodologies = self._load_methodologies()
|
87
|
+
|
88
|
+
try:
|
89
|
+
if operation == "delete":
|
90
|
+
if problem_type in methodologies:
|
91
|
+
del methodologies[problem_type]
|
92
|
+
self._save_methodologies(methodologies)
|
93
|
+
return {
|
94
|
+
"success": True,
|
95
|
+
"stdout": f"已删除问题类型 '{problem_type}' 的方法论"
|
96
|
+
}
|
97
|
+
else:
|
98
|
+
return {
|
99
|
+
"success": False,
|
100
|
+
"error": f"未找到问题类型 '{problem_type}' 的方法论"
|
101
|
+
}
|
102
|
+
|
103
|
+
elif operation in ["update", "add"]:
|
104
|
+
if not content:
|
105
|
+
return {
|
106
|
+
"success": False,
|
107
|
+
"error": f"{operation} 操作需要提供方法论内容"
|
108
|
+
}
|
109
|
+
|
110
|
+
if operation == "update" and problem_type not in methodologies:
|
111
|
+
return {
|
112
|
+
"success": False,
|
113
|
+
"error": f"未找到问题类型 '{problem_type}' 的方法论,无法更新"
|
114
|
+
}
|
115
|
+
|
116
|
+
methodologies[problem_type] = content
|
117
|
+
self._save_methodologies(methodologies)
|
118
|
+
return {
|
119
|
+
"success": True,
|
120
|
+
"stdout": f"已{'更新' if operation == 'update' else '添加'}问题类型 '{problem_type}' 的方法论"
|
121
|
+
}
|
122
|
+
|
123
|
+
else:
|
124
|
+
return {
|
125
|
+
"success": False,
|
126
|
+
"error": f"不支持的操作类型: {operation}"
|
127
|
+
}
|
128
|
+
|
129
|
+
except Exception as e:
|
130
|
+
return {
|
131
|
+
"success": False,
|
132
|
+
"error": f"执行失败: {str(e)}"
|
133
|
+
}
|
134
|
+
|
135
|
+
def get_methodology(self, problem_type: str) -> Optional[str]:
|
136
|
+
"""获取指定问题类型的方法论
|
137
|
+
|
138
|
+
Args:
|
139
|
+
problem_type: 问题类型
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
Optional[str]: 方法论内容,如果不存在则返回 None
|
143
|
+
"""
|
144
|
+
methodologies = self._load_methodologies()
|
145
|
+
return methodologies.get(problem_type)
|
jarvis/tools/registry.py
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
|
2
|
+
import importlib
|
3
|
+
import json
|
4
|
+
from pathlib import Path
|
5
|
+
import sys
|
6
|
+
from typing import Any, Callable, Dict, List, Optional
|
7
|
+
|
8
|
+
from jarvis.models.registry import ModelRegistry
|
9
|
+
from jarvis.tools.base import Tool
|
10
|
+
from jarvis.utils import OutputType, PrettyOutput
|
11
|
+
|
12
|
+
|
13
|
+
class ToolRegistry:
|
14
|
+
global_tool_registry = None # type: ignore
|
15
|
+
def __init__(self):
|
16
|
+
"""初始化工具注册器
|
17
|
+
"""
|
18
|
+
self.tools: Dict[str, Tool] = {}
|
19
|
+
# 加载内置工具和外部工具
|
20
|
+
self._load_builtin_tools()
|
21
|
+
self._load_external_tools()
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def get_global_tool_registry():
|
25
|
+
"""获取全局工具注册器"""
|
26
|
+
if ToolRegistry.global_tool_registry is None:
|
27
|
+
ToolRegistry.global_tool_registry = ToolRegistry()
|
28
|
+
return ToolRegistry.global_tool_registry
|
29
|
+
|
30
|
+
def _load_builtin_tools(self):
|
31
|
+
"""从内置tools目录加载工具"""
|
32
|
+
tools_dir = Path(__file__).parent
|
33
|
+
|
34
|
+
# 遍历目录下的所有.py文件
|
35
|
+
for file_path in tools_dir.glob("*.py"):
|
36
|
+
# 跳过基础文件和__init__.py
|
37
|
+
if file_path.name in ["base.py", "__init__.py", "registry.py"]:
|
38
|
+
continue
|
39
|
+
|
40
|
+
self.register_tool_by_file(file_path)
|
41
|
+
|
42
|
+
def _load_external_tools(self):
|
43
|
+
"""从~/.jarvis_tools加载外部工具"""
|
44
|
+
external_tools_dir = Path.home() / '.jarvis_tools'
|
45
|
+
if not external_tools_dir.exists():
|
46
|
+
return
|
47
|
+
|
48
|
+
# 遍历目录下的所有.py文件
|
49
|
+
for file_path in external_tools_dir.glob("*.py"):
|
50
|
+
# 跳过__init__.py
|
51
|
+
if file_path.name == "__init__.py":
|
52
|
+
continue
|
53
|
+
|
54
|
+
self.register_tool_by_file(file_path)
|
55
|
+
|
56
|
+
def register_tool_by_file(self, file_path: str):
|
57
|
+
"""从指定文件加载并注册工具
|
58
|
+
|
59
|
+
Args:
|
60
|
+
file_path: 工具文件的路径
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
bool: 是否成功加载工具
|
64
|
+
"""
|
65
|
+
try:
|
66
|
+
file_path = Path(file_path).resolve() # 获取绝对路径
|
67
|
+
if not file_path.exists() or not file_path.is_file():
|
68
|
+
PrettyOutput.print(f"文件不存在: {file_path}", OutputType.ERROR)
|
69
|
+
return False
|
70
|
+
|
71
|
+
# 动态导入模块
|
72
|
+
module_name = file_path.stem
|
73
|
+
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
74
|
+
if not spec or not spec.loader:
|
75
|
+
PrettyOutput.print(f"无法加载模块: {file_path}", OutputType.ERROR)
|
76
|
+
return False
|
77
|
+
|
78
|
+
module = importlib.util.module_from_spec(spec)
|
79
|
+
sys.modules[module_name] = module # 添加到 sys.modules 以支持相对导入
|
80
|
+
spec.loader.exec_module(module)
|
81
|
+
|
82
|
+
# 查找模块中的工具类
|
83
|
+
tool_found = False
|
84
|
+
for item_name in dir(module):
|
85
|
+
item = getattr(module, item_name)
|
86
|
+
# 检查是否是类,并且有必要的属性
|
87
|
+
if (isinstance(item, type) and
|
88
|
+
hasattr(item, 'name') and
|
89
|
+
hasattr(item, 'description') and
|
90
|
+
hasattr(item, 'parameters')):
|
91
|
+
|
92
|
+
# 实例化工具类,传入模型和输出处理器
|
93
|
+
tool_instance = item()
|
94
|
+
|
95
|
+
# 注册工具
|
96
|
+
self.register_tool(
|
97
|
+
name=tool_instance.name,
|
98
|
+
description=tool_instance.description,
|
99
|
+
parameters=tool_instance.parameters,
|
100
|
+
func=tool_instance.execute
|
101
|
+
)
|
102
|
+
PrettyOutput.print(f"已加载工具: {tool_instance.name}: {tool_instance.description}", OutputType.INFO)
|
103
|
+
tool_found = True
|
104
|
+
|
105
|
+
if not tool_found:
|
106
|
+
PrettyOutput.print(f"文件中未找到有效的工具类: {file_path}", OutputType.WARNING)
|
107
|
+
return False
|
108
|
+
|
109
|
+
return True
|
110
|
+
|
111
|
+
except Exception as e:
|
112
|
+
PrettyOutput.print(f"加载工具失败 {file_path.name}: {str(e)}", OutputType.ERROR)
|
113
|
+
return False
|
114
|
+
|
115
|
+
def register_tool(self, name: str, description: str, parameters: Dict, func: Callable):
|
116
|
+
"""注册新工具"""
|
117
|
+
self.tools[name] = Tool(name, description, parameters, func)
|
118
|
+
|
119
|
+
def get_tool(self, name: str) -> Optional[Tool]:
|
120
|
+
"""获取工具"""
|
121
|
+
return self.tools.get(name)
|
122
|
+
|
123
|
+
def get_all_tools(self) -> List[Dict]:
|
124
|
+
"""获取所有工具的Ollama格式定义"""
|
125
|
+
return [tool.to_dict() for tool in self.tools.values()]
|
126
|
+
|
127
|
+
def execute_tool(self, name: str, arguments: Dict) -> Dict[str, Any]:
|
128
|
+
"""执行指定工具"""
|
129
|
+
tool = self.get_tool(name)
|
130
|
+
if tool is None:
|
131
|
+
return {"success": False, "error": f"Tool {name} does not exist"}
|
132
|
+
return tool.execute(arguments)
|
133
|
+
|
134
|
+
def handle_tool_calls(self, tool_calls: List[Dict]) -> str:
|
135
|
+
"""处理工具调用,只处理第一个工具"""
|
136
|
+
try:
|
137
|
+
if not tool_calls:
|
138
|
+
return ""
|
139
|
+
|
140
|
+
# 只处理第一个工具调用
|
141
|
+
tool_call = tool_calls[0]
|
142
|
+
name = tool_call["name"]
|
143
|
+
args = tool_call["arguments"]
|
144
|
+
|
145
|
+
if isinstance(args, str):
|
146
|
+
try:
|
147
|
+
args = json.loads(args)
|
148
|
+
except json.JSONDecodeError:
|
149
|
+
PrettyOutput.print(f"工具参数格式无效: {name}", OutputType.ERROR)
|
150
|
+
return ""
|
151
|
+
|
152
|
+
# 显示工具调用信息
|
153
|
+
PrettyOutput.section(f"执行工具: {name}", OutputType.TOOL)
|
154
|
+
if isinstance(args, dict):
|
155
|
+
for key, value in args.items():
|
156
|
+
PrettyOutput.print(f"参数: {key} = {value}", OutputType.DEBUG)
|
157
|
+
else:
|
158
|
+
PrettyOutput.print(f"参数: {args}", OutputType.DEBUG)
|
159
|
+
|
160
|
+
# 执行工具调用
|
161
|
+
result = self.execute_tool(name, args)
|
162
|
+
|
163
|
+
# 处理结果
|
164
|
+
if result["success"]:
|
165
|
+
stdout = result["stdout"]
|
166
|
+
stderr = result.get("stderr", "")
|
167
|
+
output_parts = []
|
168
|
+
if stdout:
|
169
|
+
output_parts.append(f"输出:\n{stdout}")
|
170
|
+
if stderr:
|
171
|
+
output_parts.append(f"错误:\n{stderr}")
|
172
|
+
output = "\n\n".join(output_parts)
|
173
|
+
output = "没有输出和错误" if not output else output
|
174
|
+
PrettyOutput.section("执行成功", OutputType.SUCCESS)
|
175
|
+
else:
|
176
|
+
error_msg = result["error"]
|
177
|
+
output = f"执行失败: {error_msg}"
|
178
|
+
PrettyOutput.section("执行失败", OutputType.ERROR)
|
179
|
+
|
180
|
+
return output
|
181
|
+
except Exception as e:
|
182
|
+
PrettyOutput.print(f"执行工具失败: {str(e)}", OutputType.ERROR)
|
183
|
+
return f"Tool call failed: {str(e)}"
|
jarvis/tools/shell.py
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
from typing import Dict, Any
|
2
|
+
import os
|
3
|
+
import tempfile
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
from jarvis.utils import OutputType, PrettyOutput
|
7
|
+
|
8
|
+
|
9
|
+
class ShellTool:
|
10
|
+
name = "execute_shell"
|
11
|
+
description = """执行shell命令并返回结果"""
|
12
|
+
|
13
|
+
parameters = {
|
14
|
+
"type": "object",
|
15
|
+
"properties": {
|
16
|
+
"command": {
|
17
|
+
"type": "string",
|
18
|
+
"description": "Shell command to execute"
|
19
|
+
}
|
20
|
+
},
|
21
|
+
"required": ["command"]
|
22
|
+
}
|
23
|
+
|
24
|
+
|
25
|
+
def _escape_command(self, cmd: str) -> str:
|
26
|
+
"""转义命令中的特殊字符"""
|
27
|
+
return cmd.replace("'", "'\"'\"'")
|
28
|
+
|
29
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
30
|
+
"""执行shell命令"""
|
31
|
+
try:
|
32
|
+
command = args["command"]
|
33
|
+
|
34
|
+
# 生成临时文件名
|
35
|
+
output_file = os.path.join(tempfile.gettempdir(), f"jarvis_shell_{os.getpid()}.log")
|
36
|
+
|
37
|
+
# 转义命令中的特殊字符
|
38
|
+
escaped_command = self._escape_command(command)
|
39
|
+
|
40
|
+
# 修改命令以使用script
|
41
|
+
tee_command = f"script -q -c '{escaped_command}' {output_file}"
|
42
|
+
|
43
|
+
PrettyOutput.print(f"执行命令: {command}", OutputType.INFO)
|
44
|
+
|
45
|
+
# 执行命令
|
46
|
+
return_code = os.system(tee_command)
|
47
|
+
|
48
|
+
# 读取输出文件
|
49
|
+
try:
|
50
|
+
with open(output_file, 'r', encoding='utf-8', errors='replace') as f:
|
51
|
+
output = f.read()
|
52
|
+
# 移除script命令添加的头尾
|
53
|
+
if output:
|
54
|
+
lines = output.splitlines()
|
55
|
+
if len(lines) > 2:
|
56
|
+
output = "\n".join(lines[1:-1])
|
57
|
+
except Exception as e:
|
58
|
+
output = f"读取输出文件失败: {str(e)}"
|
59
|
+
finally:
|
60
|
+
# 清理临时文件
|
61
|
+
Path(output_file).unlink(missing_ok=True)
|
62
|
+
|
63
|
+
return {
|
64
|
+
"success": return_code == 0,
|
65
|
+
"stdout": output,
|
66
|
+
"stderr": "",
|
67
|
+
"return_code": return_code
|
68
|
+
}
|
69
|
+
|
70
|
+
except Exception as e:
|
71
|
+
# 确保清理临时文件
|
72
|
+
if 'output_file' in locals():
|
73
|
+
Path(output_file).unlink(missing_ok=True)
|
74
|
+
PrettyOutput.print(str(e), OutputType.ERROR)
|
75
|
+
return {
|
76
|
+
"success": False,
|
77
|
+
"error": str(e)
|
78
|
+
}
|
@@ -0,0 +1,82 @@
|
|
1
|
+
from typing import Dict, Any
|
2
|
+
|
3
|
+
|
4
|
+
from jarvis.agent import Agent
|
5
|
+
from jarvis.utils import OutputType, PrettyOutput
|
6
|
+
|
7
|
+
|
8
|
+
class SubAgentTool:
|
9
|
+
name = "create_sub_agent"
|
10
|
+
description = "创建一个子代理来处理特定任务,子代理会生成任务总结报告"
|
11
|
+
parameters = {
|
12
|
+
"type": "object",
|
13
|
+
"properties": {
|
14
|
+
"agent_name": {
|
15
|
+
"type": "string",
|
16
|
+
"description": "子代理的名称"
|
17
|
+
},
|
18
|
+
"task": {
|
19
|
+
"type": "string",
|
20
|
+
"description": "需要完成的具体任务"
|
21
|
+
},
|
22
|
+
"context": {
|
23
|
+
"type": "string",
|
24
|
+
"description": "任务相关的上下文信息",
|
25
|
+
"default": ""
|
26
|
+
},
|
27
|
+
"goal": {
|
28
|
+
"type": "string",
|
29
|
+
"description": "任务的完成目标",
|
30
|
+
"default": ""
|
31
|
+
},
|
32
|
+
"files": {
|
33
|
+
"type": "array",
|
34
|
+
"items": {"type": "string"},
|
35
|
+
"description": "相关文件路径列表,用于文件问答和处理",
|
36
|
+
"default": []
|
37
|
+
}
|
38
|
+
},
|
39
|
+
"required": ["agent_name", "task", "context", "goal"]
|
40
|
+
}
|
41
|
+
|
42
|
+
|
43
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
44
|
+
"""创建并运行子代理"""
|
45
|
+
try:
|
46
|
+
agent_name = args["agent_name"]
|
47
|
+
task = args["task"]
|
48
|
+
context = args.get("context", "")
|
49
|
+
goal = args.get("goal", "")
|
50
|
+
files = args.get("files", [])
|
51
|
+
|
52
|
+
PrettyOutput.print(f"创建子代理: {agent_name}", OutputType.INFO)
|
53
|
+
|
54
|
+
# 构建任务描述
|
55
|
+
task_description = task
|
56
|
+
if context:
|
57
|
+
task_description = f"上下文信息:\n{context}\n\n任务:\n{task}"
|
58
|
+
if goal:
|
59
|
+
task_description += f"\n\n完成目标:\n{goal}"
|
60
|
+
|
61
|
+
# 创建子代理
|
62
|
+
sub_agent = Agent(
|
63
|
+
name=agent_name,
|
64
|
+
is_sub_agent=True
|
65
|
+
)
|
66
|
+
|
67
|
+
# 运行子代理,传入文件列表
|
68
|
+
PrettyOutput.print("子代理开始执行任务...", OutputType.INFO)
|
69
|
+
result = sub_agent.run(task_description, file_list=files)
|
70
|
+
|
71
|
+
return {
|
72
|
+
"success": True,
|
73
|
+
"stdout": f"子代理任务完成\n\n{result}",
|
74
|
+
"stderr": ""
|
75
|
+
}
|
76
|
+
|
77
|
+
except Exception as e:
|
78
|
+
PrettyOutput.print(str(e), OutputType.ERROR)
|
79
|
+
return {
|
80
|
+
"success": False,
|
81
|
+
"error": f"子代理执行失败: {str(e)}"
|
82
|
+
}
|