jarvis-ai-assistant 0.1.32__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 +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
jarvis/models/oyi.py
ADDED
@@ -0,0 +1,271 @@
|
|
1
|
+
import mimetypes
|
2
|
+
import os
|
3
|
+
from typing import Dict, List
|
4
|
+
from jarvis.models.base import BaseModel
|
5
|
+
from jarvis.utils import PrettyOutput, OutputType
|
6
|
+
import requests
|
7
|
+
import json
|
8
|
+
|
9
|
+
class OyiModel(BaseModel):
|
10
|
+
"""Oyi model implementation"""
|
11
|
+
|
12
|
+
model_name = "oyi"
|
13
|
+
BASE_URL = "https://api-10086.rcouyi.com"
|
14
|
+
|
15
|
+
def __init__(self):
|
16
|
+
"""Initialize model"""
|
17
|
+
self.messages = []
|
18
|
+
self.system_message = ""
|
19
|
+
self.conversation = None
|
20
|
+
self.upload_files = []
|
21
|
+
self.first_chat = True
|
22
|
+
self.model = os.getenv("OYI_MODEL") or "deepseek-chat"
|
23
|
+
self.token = os.getenv("OYI_API_KEY")
|
24
|
+
if not all([self.model, self.token]):
|
25
|
+
raise Exception("OYI_MODEL or OYI_API_KEY is not set")
|
26
|
+
|
27
|
+
|
28
|
+
def create_conversation(self) -> bool:
|
29
|
+
"""Create a new conversation"""
|
30
|
+
try:
|
31
|
+
headers = {
|
32
|
+
'Authorization': f'Bearer {self.token}',
|
33
|
+
'Content-Type': 'application/json',
|
34
|
+
'Accept': 'application/json',
|
35
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
|
36
|
+
}
|
37
|
+
|
38
|
+
payload = {
|
39
|
+
"id": 0,
|
40
|
+
"roleId": 0,
|
41
|
+
"title": "新对话",
|
42
|
+
"isLock": False,
|
43
|
+
"systemMessage": "",
|
44
|
+
"params": json.dumps({
|
45
|
+
"model": "gpt-4o-mini",
|
46
|
+
"is_webSearch": True,
|
47
|
+
"message": [],
|
48
|
+
"systemMessage": None,
|
49
|
+
"requestMsgCount": 1000,
|
50
|
+
"temperature": 0.8,
|
51
|
+
"speechVoice": "Alloy",
|
52
|
+
"max_tokens": 8192,
|
53
|
+
"chatPluginIds": []
|
54
|
+
})
|
55
|
+
}
|
56
|
+
|
57
|
+
response = requests.post(
|
58
|
+
f"{self.BASE_URL}/chatapi/chat/save",
|
59
|
+
headers=headers,
|
60
|
+
json=payload
|
61
|
+
)
|
62
|
+
|
63
|
+
if response.status_code == 200:
|
64
|
+
data = response.json()
|
65
|
+
if data['code'] == 200 and data['type'] == 'success':
|
66
|
+
self.conversation = data
|
67
|
+
PrettyOutput.print(f"创建会话成功: {data['result']['id']}", OutputType.SUCCESS)
|
68
|
+
return True
|
69
|
+
else:
|
70
|
+
PrettyOutput.print(f"创建会话失败: {data['message']}", OutputType.ERROR)
|
71
|
+
return False
|
72
|
+
else:
|
73
|
+
PrettyOutput.print(f"创建会话失败: {response.status_code}", OutputType.ERROR)
|
74
|
+
return False
|
75
|
+
|
76
|
+
except Exception as e:
|
77
|
+
PrettyOutput.print(f"创建会话异常: {str(e)}", OutputType.ERROR)
|
78
|
+
return False
|
79
|
+
|
80
|
+
def set_system_message(self, message: str):
|
81
|
+
"""Set system message"""
|
82
|
+
self.system_message = message
|
83
|
+
|
84
|
+
def chat(self, message: str) -> str:
|
85
|
+
"""Execute chat with the model
|
86
|
+
|
87
|
+
Args:
|
88
|
+
message: User input message
|
89
|
+
|
90
|
+
Returns:
|
91
|
+
str: Model response
|
92
|
+
"""
|
93
|
+
try:
|
94
|
+
# 确保有会话ID
|
95
|
+
if not self.conversation:
|
96
|
+
if not self.create_conversation():
|
97
|
+
raise Exception("Failed to create conversation")
|
98
|
+
|
99
|
+
# 1. 发送消息
|
100
|
+
headers = {
|
101
|
+
'Authorization': f'Bearer {self.token}',
|
102
|
+
'Content-Type': 'application/json',
|
103
|
+
'Accept': 'application/json, text/plain, */*',
|
104
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
105
|
+
'Origin': 'https://ai.rcouyi.com',
|
106
|
+
'Referer': 'https://ai.rcouyi.com/'
|
107
|
+
}
|
108
|
+
|
109
|
+
payload = {
|
110
|
+
"topicId": self.conversation['result']['id'],
|
111
|
+
"messages": self.messages,
|
112
|
+
"content": message,
|
113
|
+
"contentFiles": []
|
114
|
+
}
|
115
|
+
|
116
|
+
# 如果有上传的文件,添加到请求中
|
117
|
+
if self.first_chat:
|
118
|
+
if self.upload_files:
|
119
|
+
for file_data in self.upload_files:
|
120
|
+
file_info = {
|
121
|
+
"contentType": 1, # 1 表示图片
|
122
|
+
"fileUrl": file_data['result']['url'],
|
123
|
+
"fileId": file_data['result']['id'],
|
124
|
+
"fileName": file_data['result']['fileName']
|
125
|
+
}
|
126
|
+
payload["contentFiles"].append(file_info)
|
127
|
+
# 清空已使用的文件列表
|
128
|
+
self.upload_files = []
|
129
|
+
message = self.system_message + "\n" + message
|
130
|
+
payload["content"] = message
|
131
|
+
self.first_chat = False
|
132
|
+
|
133
|
+
self.messages.append({"role": "user", "content": message})
|
134
|
+
|
135
|
+
# 发送消息
|
136
|
+
response = requests.post(
|
137
|
+
f"{self.BASE_URL}/chatapi/chat/message",
|
138
|
+
headers=headers,
|
139
|
+
json=payload
|
140
|
+
)
|
141
|
+
|
142
|
+
if response.status_code != 200:
|
143
|
+
error_msg = f"聊天请求失败: {response.status_code}"
|
144
|
+
PrettyOutput.print(error_msg, OutputType.ERROR)
|
145
|
+
raise Exception(error_msg)
|
146
|
+
|
147
|
+
data = response.json()
|
148
|
+
if data['code'] != 200 or data['type'] != 'success':
|
149
|
+
error_msg = f"聊天失败: {data.get('message', '未知错误')}"
|
150
|
+
PrettyOutput.print(error_msg, OutputType.ERROR)
|
151
|
+
raise Exception(error_msg)
|
152
|
+
|
153
|
+
message_id = data['result'][-1]
|
154
|
+
|
155
|
+
# 获取响应内容
|
156
|
+
response = requests.post(
|
157
|
+
f"{self.BASE_URL}/chatapi/chat/message/{message_id}",
|
158
|
+
headers=headers
|
159
|
+
)
|
160
|
+
|
161
|
+
if response.status_code == 200:
|
162
|
+
PrettyOutput.print(response.text, OutputType.SYSTEM)
|
163
|
+
self.messages.append({"role": "assistant", "content": response.text})
|
164
|
+
return response.text
|
165
|
+
else:
|
166
|
+
error_msg = f"获取响应失败: {response.status_code}"
|
167
|
+
PrettyOutput.print(error_msg, OutputType.ERROR)
|
168
|
+
raise Exception(error_msg)
|
169
|
+
|
170
|
+
except Exception as e:
|
171
|
+
PrettyOutput.print(f"聊天异常: {str(e)}", OutputType.ERROR)
|
172
|
+
raise e
|
173
|
+
|
174
|
+
def name(self) -> str:
|
175
|
+
"""Return model name"""
|
176
|
+
return self.model_name
|
177
|
+
|
178
|
+
def reset(self):
|
179
|
+
"""Reset model state"""
|
180
|
+
self.messages = []
|
181
|
+
self.conversation = None
|
182
|
+
self.upload_files = []
|
183
|
+
self.first_chat = True
|
184
|
+
|
185
|
+
def delete_chat(self) -> bool:
|
186
|
+
"""Delete current chat session"""
|
187
|
+
try:
|
188
|
+
if not self.conversation:
|
189
|
+
return True
|
190
|
+
|
191
|
+
headers = {
|
192
|
+
'Authorization': f'Bearer {self.token}',
|
193
|
+
'Content-Type': 'application/json',
|
194
|
+
'Accept': 'application/json, text/plain, */*',
|
195
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
196
|
+
'Origin': 'https://ai.rcouyi.com',
|
197
|
+
'Referer': 'https://ai.rcouyi.com/'
|
198
|
+
}
|
199
|
+
|
200
|
+
response = requests.post(
|
201
|
+
f"{self.BASE_URL}/chatapi/chat/{self.conversation['result']['id']}",
|
202
|
+
headers=headers,
|
203
|
+
json={}
|
204
|
+
)
|
205
|
+
|
206
|
+
if response.status_code == 200:
|
207
|
+
data = response.json()
|
208
|
+
if data['code'] == 200 and data['type'] == 'success':
|
209
|
+
PrettyOutput.print("会话删除成功", OutputType.SUCCESS)
|
210
|
+
self.reset()
|
211
|
+
return True
|
212
|
+
else:
|
213
|
+
error_msg = f"删除会话失败: {data.get('message', '未知错误')}"
|
214
|
+
PrettyOutput.print(error_msg, OutputType.ERROR)
|
215
|
+
return False
|
216
|
+
else:
|
217
|
+
error_msg = f"删除会话请求失败: {response.status_code}"
|
218
|
+
PrettyOutput.print(error_msg, OutputType.ERROR)
|
219
|
+
return False
|
220
|
+
|
221
|
+
except Exception as e:
|
222
|
+
PrettyOutput.print(f"删除会话异常: {str(e)}", OutputType.ERROR)
|
223
|
+
return False
|
224
|
+
|
225
|
+
def upload_file(self, file_path: str) -> Dict:
|
226
|
+
"""Upload a file to OYI API
|
227
|
+
|
228
|
+
Args:
|
229
|
+
file_path: Path to the file to upload
|
230
|
+
|
231
|
+
Returns:
|
232
|
+
Dict: Upload response data
|
233
|
+
"""
|
234
|
+
try:
|
235
|
+
headers = {
|
236
|
+
'Authorization': f'Bearer {self.token}',
|
237
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
238
|
+
'Accept': '*/*',
|
239
|
+
'DNT': '1',
|
240
|
+
'Origin': 'https://ai.rcouyi.com',
|
241
|
+
'Referer': 'https://ai.rcouyi.com/'
|
242
|
+
}
|
243
|
+
|
244
|
+
with open(file_path, 'rb') as f:
|
245
|
+
files = {
|
246
|
+
'file': (os.path.basename(file_path), f, mimetypes.guess_type(file_path)[0]) # Adjust content-type based on file type
|
247
|
+
}
|
248
|
+
|
249
|
+
response = requests.post(
|
250
|
+
f"{self.BASE_URL}/chatapi/m_file/uploadfile",
|
251
|
+
headers=headers,
|
252
|
+
files=files
|
253
|
+
)
|
254
|
+
|
255
|
+
if response.status_code == 200:
|
256
|
+
data = response.json()
|
257
|
+
if data.get('code') == 200:
|
258
|
+
PrettyOutput.print("文件上传成功", OutputType.SUCCESS)
|
259
|
+
print(data)
|
260
|
+
self.upload_files.append(data)
|
261
|
+
return data
|
262
|
+
else:
|
263
|
+
PrettyOutput.print(f"文件上传失败: {data.get('message')}", OutputType.ERROR)
|
264
|
+
return None
|
265
|
+
else:
|
266
|
+
PrettyOutput.print(f"文件上传失败: {response.status_code}", OutputType.ERROR)
|
267
|
+
return None
|
268
|
+
|
269
|
+
except Exception as e:
|
270
|
+
PrettyOutput.print(f"文件上传异常: {str(e)}", OutputType.ERROR)
|
271
|
+
return None
|
@@ -0,0 +1,199 @@
|
|
1
|
+
import importlib
|
2
|
+
import inspect
|
3
|
+
import os
|
4
|
+
import sys
|
5
|
+
from typing import Dict, Type, Optional, List
|
6
|
+
from .base import BaseModel
|
7
|
+
from ..utils import PrettyOutput, OutputType
|
8
|
+
|
9
|
+
REQUIRED_METHODS = [
|
10
|
+
('chat', ['message']), # 方法名和参数列表
|
11
|
+
('name', []),
|
12
|
+
('delete_chat', []),
|
13
|
+
('reset', []),
|
14
|
+
('set_system_message', ['message'])
|
15
|
+
]
|
16
|
+
|
17
|
+
class ModelRegistry:
|
18
|
+
"""模型注册器"""
|
19
|
+
|
20
|
+
global_model_name = "kimi"
|
21
|
+
global_model_registry = None
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def get_models_dir() -> str:
|
25
|
+
user_models_dir = os.path.expanduser("~/.jarvis_models")
|
26
|
+
if not os.path.exists(user_models_dir):
|
27
|
+
try:
|
28
|
+
os.makedirs(user_models_dir)
|
29
|
+
# 创建 __init__.py 使其成为 Python 包
|
30
|
+
with open(os.path.join(user_models_dir, "__init__.py"), "w") as f:
|
31
|
+
pass
|
32
|
+
PrettyOutput.print(f"已创建模型目录: {user_models_dir}", OutputType.INFO)
|
33
|
+
except Exception as e:
|
34
|
+
PrettyOutput.print(f"创建模型目录失败: {str(e)}", OutputType.ERROR)
|
35
|
+
return ""
|
36
|
+
return user_models_dir
|
37
|
+
|
38
|
+
@staticmethod
|
39
|
+
def check_model_implementation(model_class: Type[BaseModel]) -> bool:
|
40
|
+
"""检查模型类是否实现了所有必要的方法
|
41
|
+
|
42
|
+
Args:
|
43
|
+
model_class: 要检查的模型类
|
44
|
+
|
45
|
+
Returns:
|
46
|
+
bool: 是否实现了所有必要的方法
|
47
|
+
"""
|
48
|
+
missing_methods = []
|
49
|
+
|
50
|
+
for method_name, params in REQUIRED_METHODS:
|
51
|
+
if not hasattr(model_class, method_name):
|
52
|
+
missing_methods.append(method_name)
|
53
|
+
continue
|
54
|
+
|
55
|
+
method = getattr(model_class, method_name)
|
56
|
+
if not callable(method):
|
57
|
+
missing_methods.append(method_name)
|
58
|
+
continue
|
59
|
+
|
60
|
+
# 检查方法参数
|
61
|
+
import inspect
|
62
|
+
sig = inspect.signature(method)
|
63
|
+
method_params = [p for p in sig.parameters if p != 'self']
|
64
|
+
if len(method_params) != len(params):
|
65
|
+
missing_methods.append(f"{method_name}(参数不匹配)")
|
66
|
+
|
67
|
+
if missing_methods:
|
68
|
+
PrettyOutput.print(
|
69
|
+
f"模型 {model_class.__name__} 缺少必要的方法: {', '.join(missing_methods)}",
|
70
|
+
OutputType.ERROR
|
71
|
+
)
|
72
|
+
return False
|
73
|
+
|
74
|
+
return True
|
75
|
+
|
76
|
+
@staticmethod
|
77
|
+
def load_models_from_dir(directory: str) -> Dict[str, Type[BaseModel]]:
|
78
|
+
"""从指定目录加载模型
|
79
|
+
|
80
|
+
Args:
|
81
|
+
directory: 模型目录路径
|
82
|
+
|
83
|
+
Returns:
|
84
|
+
Dict[str, Type[BaseModel]]: 模型名称到模型类的映射
|
85
|
+
"""
|
86
|
+
models = {}
|
87
|
+
|
88
|
+
# 确保目录存在
|
89
|
+
if not os.path.exists(directory):
|
90
|
+
PrettyOutput.print(f"模型目录不存在: {directory}", OutputType.ERROR)
|
91
|
+
return models
|
92
|
+
|
93
|
+
# 获取目录的包名
|
94
|
+
package_name = None
|
95
|
+
if directory == os.path.dirname(__file__):
|
96
|
+
package_name = "jarvis.models"
|
97
|
+
|
98
|
+
# 添加目录到Python路径
|
99
|
+
if directory not in sys.path:
|
100
|
+
sys.path.append(directory)
|
101
|
+
|
102
|
+
# 遍历目录下的所有.py文件
|
103
|
+
for filename in os.listdir(directory):
|
104
|
+
if filename.endswith('.py') and not filename.startswith('__'):
|
105
|
+
module_name = filename[:-3] # 移除.py后缀
|
106
|
+
try:
|
107
|
+
# 导入模块
|
108
|
+
if package_name:
|
109
|
+
module = importlib.import_module(f"{package_name}.{module_name}")
|
110
|
+
else:
|
111
|
+
module = importlib.import_module(module_name)
|
112
|
+
|
113
|
+
# 遍历模块中的所有类
|
114
|
+
for name, obj in inspect.getmembers(module):
|
115
|
+
# 检查是否是BaseModel的子类,但不是BaseModel本身
|
116
|
+
if (inspect.isclass(obj) and
|
117
|
+
issubclass(obj, BaseModel) and
|
118
|
+
obj != BaseModel and
|
119
|
+
hasattr(obj, 'model_name')):
|
120
|
+
# 检查模型实现
|
121
|
+
if not ModelRegistry.check_model_implementation(obj):
|
122
|
+
continue
|
123
|
+
models[obj.model_name] = obj
|
124
|
+
PrettyOutput.print(f"从 {directory} 加载模型: {obj.model_name}", OutputType.INFO)
|
125
|
+
break
|
126
|
+
except Exception as e:
|
127
|
+
PrettyOutput.print(f"加载模型 {module_name} 失败: {str(e)}", OutputType.ERROR)
|
128
|
+
|
129
|
+
return models
|
130
|
+
|
131
|
+
|
132
|
+
@staticmethod
|
133
|
+
def get_model_registry():
|
134
|
+
"""获取全局模型注册器"""
|
135
|
+
if ModelRegistry.global_model_registry is None:
|
136
|
+
ModelRegistry.global_model_registry = ModelRegistry()
|
137
|
+
|
138
|
+
# 从用户模型目录加载额外模型
|
139
|
+
models_dir = ModelRegistry.get_models_dir()
|
140
|
+
if models_dir and os.path.exists(models_dir):
|
141
|
+
for model_name, model_class in ModelRegistry.load_models_from_dir(models_dir).items():
|
142
|
+
ModelRegistry.global_model_registry.register_model(model_name, model_class)
|
143
|
+
models_dir = os.path.dirname(__file__)
|
144
|
+
if models_dir and os.path.exists(models_dir):
|
145
|
+
for model_name, model_class in ModelRegistry.load_models_from_dir(models_dir).items():
|
146
|
+
ModelRegistry.global_model_registry.register_model(model_name, model_class)
|
147
|
+
return ModelRegistry.global_model_registry
|
148
|
+
|
149
|
+
def __init__(self):
|
150
|
+
"""初始化模型注册器
|
151
|
+
"""
|
152
|
+
self.models: Dict[str, Type[BaseModel]] = {}
|
153
|
+
|
154
|
+
@staticmethod
|
155
|
+
def get_global_model() -> BaseModel:
|
156
|
+
"""获取全局模型实例"""
|
157
|
+
model = ModelRegistry.get_model_registry().create_model(ModelRegistry.global_model_name)
|
158
|
+
if not model:
|
159
|
+
raise Exception(f"Failed to create model: {ModelRegistry.global_model_name}")
|
160
|
+
return model
|
161
|
+
|
162
|
+
def register_model(self, name: str, model_class: Type[BaseModel]):
|
163
|
+
"""注册模型类
|
164
|
+
|
165
|
+
Args:
|
166
|
+
name: 模型名称
|
167
|
+
model_class: 模型类
|
168
|
+
"""
|
169
|
+
self.models[name] = model_class
|
170
|
+
PrettyOutput.print(f"已注册模型: {name}", OutputType.INFO)
|
171
|
+
|
172
|
+
def create_model(self, name: str) -> Optional[BaseModel]:
|
173
|
+
"""创建模型实例
|
174
|
+
|
175
|
+
Args:
|
176
|
+
name: 模型名称
|
177
|
+
|
178
|
+
Returns:
|
179
|
+
BaseModel: 模型实例
|
180
|
+
"""
|
181
|
+
if name not in self.models:
|
182
|
+
PrettyOutput.print(f"未找到模型: {name}", OutputType.ERROR)
|
183
|
+
return None
|
184
|
+
|
185
|
+
try:
|
186
|
+
model = self.models[name]()
|
187
|
+
PrettyOutput.print(f"已创建模型实例: {name}", OutputType.INFO)
|
188
|
+
return model
|
189
|
+
except Exception as e:
|
190
|
+
PrettyOutput.print(f"创建模型失败: {str(e)}", OutputType.ERROR)
|
191
|
+
return None
|
192
|
+
|
193
|
+
def get_available_models(self) -> List[str]:
|
194
|
+
"""获取可用模型列表"""
|
195
|
+
return list(self.models.keys())
|
196
|
+
|
197
|
+
def set_global_model(self, model_name: str):
|
198
|
+
"""设置全局模型"""
|
199
|
+
ModelRegistry.global_model_name = model_name
|
jarvis/tools/__init__.py
ADDED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
jarvis/tools/base.py
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
from typing import Dict, Any, Callable
|
2
|
+
import json
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
class Tool:
|
7
|
+
def __init__(self, name: str, description: str, parameters: Dict, func: Callable):
|
8
|
+
self.name = name
|
9
|
+
self.description = description
|
10
|
+
self.parameters = parameters
|
11
|
+
self.func = func
|
12
|
+
|
13
|
+
def to_dict(self) -> Dict:
|
14
|
+
"""转换为工具格式"""
|
15
|
+
return {
|
16
|
+
"name": self.name,
|
17
|
+
"description": self.description,
|
18
|
+
"parameters": json.dumps(self.parameters, ensure_ascii=False)
|
19
|
+
}
|
20
|
+
|
21
|
+
def execute(self, arguments: Dict) -> Dict[str, Any]:
|
22
|
+
"""执行工具函数"""
|
23
|
+
return self.func(arguments)
|
jarvis/tools/file_ops.py
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
from typing import Dict, Any, Protocol
|
2
|
+
import os
|
3
|
+
from enum import Enum
|
4
|
+
|
5
|
+
from jarvis.utils import OutputType, PrettyOutput
|
6
|
+
|
7
|
+
|
8
|
+
class FileOperationTool:
|
9
|
+
name = "file_operation"
|
10
|
+
description = "文件操作 (read/write/append/exists)"
|
11
|
+
parameters = {
|
12
|
+
"type": "object",
|
13
|
+
"properties": {
|
14
|
+
"operation": {
|
15
|
+
"type": "string",
|
16
|
+
"enum": ["read", "write", "append", "exists"],
|
17
|
+
"description": "Type of file operation to perform"
|
18
|
+
},
|
19
|
+
"filepath": {
|
20
|
+
"type": "string",
|
21
|
+
"description": "Absolute or relative path to the file"
|
22
|
+
},
|
23
|
+
"content": {
|
24
|
+
"type": "string",
|
25
|
+
"description": "Content to write (required for write/append operations)",
|
26
|
+
"default": ""
|
27
|
+
},
|
28
|
+
"encoding": {
|
29
|
+
"type": "string",
|
30
|
+
"description": "File encoding (default: utf-8)",
|
31
|
+
"default": "utf-8"
|
32
|
+
}
|
33
|
+
},
|
34
|
+
"required": ["operation", "filepath"]
|
35
|
+
}
|
36
|
+
|
37
|
+
|
38
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
39
|
+
"""执行文件操作"""
|
40
|
+
try:
|
41
|
+
operation = args["operation"]
|
42
|
+
filepath = args["filepath"]
|
43
|
+
encoding = args.get("encoding", "utf-8")
|
44
|
+
|
45
|
+
# 记录操作和完整路径
|
46
|
+
abs_path = os.path.abspath(filepath)
|
47
|
+
PrettyOutput.print(f"文件操作: {operation} - {abs_path}", OutputType.INFO)
|
48
|
+
|
49
|
+
if operation == "exists":
|
50
|
+
exists = os.path.exists(filepath)
|
51
|
+
return {
|
52
|
+
"success": True,
|
53
|
+
"stdout": str(exists),
|
54
|
+
"stderr": ""
|
55
|
+
}
|
56
|
+
|
57
|
+
elif operation == "read":
|
58
|
+
if not os.path.exists(filepath):
|
59
|
+
return {
|
60
|
+
"success": False,
|
61
|
+
"error": f"文件不存在: {filepath}"
|
62
|
+
}
|
63
|
+
|
64
|
+
# 检查文件大小
|
65
|
+
if os.path.getsize(filepath) > 10 * 1024 * 1024: # 10MB
|
66
|
+
return {
|
67
|
+
"success": False,
|
68
|
+
"error": "文件过大 (>10MB)"
|
69
|
+
}
|
70
|
+
|
71
|
+
with open(filepath, 'r', encoding=encoding) as f:
|
72
|
+
content = f.read()
|
73
|
+
return {
|
74
|
+
"success": True,
|
75
|
+
"stdout": content,
|
76
|
+
"stderr": ""
|
77
|
+
}
|
78
|
+
|
79
|
+
elif operation in ["write", "append"]:
|
80
|
+
if not args.get("content"):
|
81
|
+
return {
|
82
|
+
"success": False,
|
83
|
+
"error": "写入/追加操作需要提供content参数"
|
84
|
+
}
|
85
|
+
|
86
|
+
# 创建目录(如果不存在)
|
87
|
+
os.makedirs(os.path.dirname(os.path.abspath(filepath)), exist_ok=True)
|
88
|
+
|
89
|
+
mode = 'a' if operation == "append" else 'w'
|
90
|
+
with open(filepath, mode, encoding=encoding) as f:
|
91
|
+
f.write(args["content"])
|
92
|
+
|
93
|
+
return {
|
94
|
+
"success": True,
|
95
|
+
"stdout": f"成功{operation}内容到 {filepath}",
|
96
|
+
"stderr": ""
|
97
|
+
}
|
98
|
+
|
99
|
+
else:
|
100
|
+
return {
|
101
|
+
"success": False,
|
102
|
+
"error": f"未知操作: {operation}"
|
103
|
+
}
|
104
|
+
|
105
|
+
except Exception as e:
|
106
|
+
PrettyOutput.print(str(e), OutputType.ERROR)
|
107
|
+
return {
|
108
|
+
"success": False,
|
109
|
+
"error": f"文件操作失败: {str(e)}"
|
110
|
+
}
|