jarvis-ai-assistant 0.1.193__py3-none-any.whl → 0.1.195__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 +45 -41
- jarvis/jarvis_agent/builtin_input_handler.py +26 -4
- jarvis/jarvis_agent/jarvis.py +30 -19
- jarvis/jarvis_agent/main.py +20 -12
- jarvis/jarvis_agent/output_handler.py +7 -7
- jarvis/jarvis_agent/shell_input_handler.py +14 -11
- jarvis/jarvis_code_agent/code_agent.py +81 -79
- jarvis/jarvis_code_agent/lint.py +92 -105
- jarvis/jarvis_code_analysis/checklists/__init__.py +1 -1
- jarvis/jarvis_code_analysis/checklists/c_cpp.py +1 -1
- jarvis/jarvis_code_analysis/checklists/csharp.py +1 -1
- jarvis/jarvis_code_analysis/checklists/data_format.py +1 -1
- jarvis/jarvis_code_analysis/checklists/devops.py +1 -1
- jarvis/jarvis_code_analysis/checklists/docs.py +1 -1
- jarvis/jarvis_code_analysis/checklists/go.py +1 -1
- jarvis/jarvis_code_analysis/checklists/infrastructure.py +1 -1
- jarvis/jarvis_code_analysis/checklists/java.py +1 -1
- jarvis/jarvis_code_analysis/checklists/javascript.py +1 -1
- jarvis/jarvis_code_analysis/checklists/kotlin.py +1 -1
- jarvis/jarvis_code_analysis/checklists/loader.py +31 -29
- jarvis/jarvis_code_analysis/checklists/php.py +1 -1
- jarvis/jarvis_code_analysis/checklists/python.py +1 -1
- jarvis/jarvis_code_analysis/checklists/ruby.py +1 -1
- jarvis/jarvis_code_analysis/checklists/rust.py +1 -1
- jarvis/jarvis_code_analysis/checklists/shell.py +1 -1
- jarvis/jarvis_code_analysis/checklists/sql.py +1 -1
- jarvis/jarvis_code_analysis/checklists/swift.py +1 -1
- jarvis/jarvis_code_analysis/checklists/web.py +1 -1
- jarvis/jarvis_code_analysis/code_review.py +292 -190
- jarvis/jarvis_dev/main.py +73 -56
- jarvis/jarvis_git_details/main.py +29 -33
- jarvis/jarvis_git_squash/main.py +13 -11
- jarvis/jarvis_git_utils/git_commiter.py +15 -5
- jarvis/jarvis_mcp/__init__.py +8 -10
- jarvis/jarvis_mcp/sse_mcp_client.py +182 -205
- jarvis/jarvis_mcp/stdio_mcp_client.py +93 -120
- jarvis/jarvis_mcp/streamable_mcp_client.py +117 -142
- jarvis/jarvis_methodology/main.py +71 -39
- jarvis/jarvis_multi_agent/__init__.py +24 -16
- jarvis/jarvis_multi_agent/main.py +10 -4
- jarvis/jarvis_platform/__init__.py +1 -1
- jarvis/jarvis_platform/base.py +44 -18
- jarvis/jarvis_platform/human.py +15 -3
- jarvis/jarvis_platform/kimi.py +117 -81
- jarvis/jarvis_platform/openai.py +23 -28
- jarvis/jarvis_platform/registry.py +43 -29
- jarvis/jarvis_platform/tongyi.py +16 -10
- jarvis/jarvis_platform/yuanbao.py +197 -144
- jarvis/jarvis_platform_manager/main.py +4 -2
- jarvis/jarvis_smart_shell/main.py +35 -30
- jarvis/jarvis_tools/ask_user.py +8 -16
- jarvis/jarvis_tools/base.py +3 -2
- jarvis/jarvis_tools/chdir.py +7 -19
- jarvis/jarvis_tools/cli/main.py +14 -10
- jarvis/jarvis_tools/code_plan.py +10 -31
- jarvis/jarvis_tools/create_code_agent.py +6 -11
- jarvis/jarvis_tools/create_sub_agent.py +10 -22
- jarvis/jarvis_tools/edit_file.py +98 -76
- jarvis/jarvis_tools/execute_script.py +46 -46
- jarvis/jarvis_tools/file_analyzer.py +22 -34
- jarvis/jarvis_tools/file_operation.py +69 -62
- jarvis/jarvis_tools/generate_new_tool.py +0 -2
- jarvis/jarvis_tools/methodology.py +19 -23
- jarvis/jarvis_tools/read_code.py +35 -35
- jarvis/jarvis_tools/read_webpage.py +7 -16
- jarvis/jarvis_tools/registry.py +63 -30
- jarvis/jarvis_tools/rewrite_file.py +26 -29
- jarvis/jarvis_tools/search_web.py +5 -8
- jarvis/jarvis_tools/virtual_tty.py +133 -122
- jarvis/jarvis_utils/__init__.py +0 -1
- jarvis/jarvis_utils/builtin_replace_map.py +9 -9
- jarvis/jarvis_utils/config.py +60 -37
- jarvis/jarvis_utils/embedding.py +24 -19
- jarvis/jarvis_utils/file_processors.py +16 -9
- jarvis/jarvis_utils/git_utils.py +157 -107
- jarvis/jarvis_utils/globals.py +1 -1
- jarvis/jarvis_utils/input.py +85 -52
- jarvis/jarvis_utils/jarvis_history.py +43 -0
- jarvis/jarvis_utils/methodology.py +31 -24
- jarvis/jarvis_utils/output.py +164 -80
- jarvis/jarvis_utils/tag.py +2 -1
- jarvis/jarvis_utils/utils.py +84 -52
- {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/METADATA +362 -230
- jarvis_ai_assistant-0.1.195.dist-info/RECORD +98 -0
- jarvis/jarvis_agent/file_input_handler.py +0 -112
- jarvis/jarvis_event/__init__.py +0 -0
- jarvis_ai_assistant-0.1.193.dist-info/RECORD +0 -99
- {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/top_level.txt +0 -0
jarvis/jarvis_platform/kimi.py
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
# Kimi 平台实现模块
|
3
|
+
# 提供与 Moonshot AI 的 Kimi 大模型交互功能
|
2
4
|
import json
|
3
5
|
import mimetypes
|
4
6
|
import os
|
@@ -8,13 +10,17 @@ from typing import Dict, Generator, List, Tuple
|
|
8
10
|
import requests # type: ignore
|
9
11
|
|
10
12
|
from jarvis.jarvis_platform.base import BasePlatform
|
11
|
-
from jarvis.jarvis_utils.config import get_data_dir
|
12
13
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
13
14
|
from jarvis.jarvis_utils.utils import while_success
|
14
15
|
|
15
16
|
|
16
17
|
class KimiModel(BasePlatform):
|
17
|
-
"""Kimi
|
18
|
+
"""Kimi 大模型平台实现类
|
19
|
+
封装了与 Kimi 大模型交互的所有功能,包括:
|
20
|
+
- 会话管理
|
21
|
+
- 文件上传
|
22
|
+
- 消息收发
|
23
|
+
"""
|
18
24
|
|
19
25
|
platform_name = "kimi"
|
20
26
|
|
@@ -22,24 +28,24 @@ class KimiModel(BasePlatform):
|
|
22
28
|
"""Get model list"""
|
23
29
|
return [
|
24
30
|
("kimi", "基于网页的 Kimi,免费接口"),
|
25
|
-
("k1", "基于网页的 Kimi,深度思考模型")
|
26
|
-
|
31
|
+
("k1", "基于网页的 Kimi,深度思考模型"),
|
32
|
+
]
|
27
33
|
|
28
34
|
def __init__(self):
|
29
35
|
"""
|
30
36
|
Initialize Kimi model
|
31
37
|
"""
|
32
38
|
super().__init__()
|
33
|
-
self.chat_id = ""
|
34
|
-
self.api_key = os.getenv("KIMI_API_KEY")
|
39
|
+
self.chat_id = "" # 当前会话ID
|
40
|
+
self.api_key = os.getenv("KIMI_API_KEY") # 从环境变量获取API密钥
|
35
41
|
if not self.api_key:
|
36
42
|
PrettyOutput.print("KIMI_API_KEY 未设置", OutputType.WARNING)
|
37
|
-
self.auth_header = f"Bearer {self.api_key}"
|
43
|
+
self.auth_header = f"Bearer {self.api_key}" # 认证头信息
|
38
44
|
self.uploaded_files = [] # 存储已上传文件的信息
|
39
|
-
self.chat_id = ""
|
40
|
-
self.first_chat = True #
|
41
|
-
self.system_message = ""
|
42
|
-
self.model_name = "kimi"
|
45
|
+
self.chat_id = "" # 当前会话ID
|
46
|
+
self.first_chat = True # 标记是否是第一次对话
|
47
|
+
self.system_message = "" # 系统提示消息
|
48
|
+
self.model_name = "kimi" # 默认模型名称
|
43
49
|
|
44
50
|
def set_system_prompt(self, message: str):
|
45
51
|
"""Set system message"""
|
@@ -52,45 +58,47 @@ class KimiModel(BasePlatform):
|
|
52
58
|
def _create_chat(self) -> bool:
|
53
59
|
"""Create a new chat session"""
|
54
60
|
url = "https://kimi.moonshot.cn/api/chat"
|
55
|
-
payload = json.dumps(
|
56
|
-
"name": "Unnamed session",
|
57
|
-
|
58
|
-
"kimiplus_id": "kimi"
|
59
|
-
})
|
61
|
+
payload = json.dumps(
|
62
|
+
{"name": "Unnamed session", "is_example": False, "kimiplus_id": "kimi"}
|
63
|
+
)
|
60
64
|
headers = {
|
61
|
-
|
62
|
-
|
65
|
+
"Authorization": self.auth_header,
|
66
|
+
"Content-Type": "application/json",
|
63
67
|
}
|
64
68
|
try:
|
65
|
-
response = while_success(
|
69
|
+
response = while_success(
|
70
|
+
lambda: requests.request("POST", url, headers=headers, data=payload),
|
71
|
+
sleep_time=5,
|
72
|
+
)
|
66
73
|
if response.status_code != 200:
|
67
|
-
PrettyOutput.print(
|
74
|
+
PrettyOutput.print(
|
75
|
+
f"错误:创建会话失败:{response.json()}", OutputType.ERROR
|
76
|
+
)
|
68
77
|
return False
|
69
78
|
self.chat_id = response.json()["id"]
|
70
79
|
return True
|
71
80
|
except Exception as e:
|
72
81
|
PrettyOutput.print(f"错误:创建会话失败:{e}", OutputType.ERROR)
|
73
82
|
return False
|
74
|
-
|
83
|
+
|
75
84
|
def _get_presigned_url(self, filename: str, action: str) -> Dict:
|
76
85
|
"""Get presigned upload URL"""
|
77
86
|
url = "https://kimi.moonshot.cn/api/pre-sign-url"
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
"name": os.path.basename(filename)
|
84
|
-
}, ensure_ascii=False)
|
85
|
-
|
87
|
+
|
88
|
+
payload = json.dumps(
|
89
|
+
{"action": action, "name": os.path.basename(filename)}, ensure_ascii=False
|
90
|
+
)
|
91
|
+
|
86
92
|
headers = {
|
87
|
-
|
88
|
-
|
93
|
+
"Authorization": self.auth_header,
|
94
|
+
"Content-Type": "application/json",
|
89
95
|
}
|
90
|
-
|
91
|
-
response = while_success(
|
96
|
+
|
97
|
+
response = while_success(
|
98
|
+
lambda: requests.post(url, headers=headers, data=payload), sleep_time=5
|
99
|
+
)
|
92
100
|
return response.json()
|
93
|
-
|
101
|
+
|
94
102
|
def support_upload_files(self) -> bool:
|
95
103
|
"""Check if platform supports upload files"""
|
96
104
|
return True
|
@@ -98,9 +106,11 @@ class KimiModel(BasePlatform):
|
|
98
106
|
def _upload_file(self, file_path: str, presigned_url: str) -> bool:
|
99
107
|
"""Upload file to presigned URL"""
|
100
108
|
try:
|
101
|
-
with open(file_path,
|
109
|
+
with open(file_path, "rb") as f:
|
102
110
|
content = f.read()
|
103
|
-
response = while_success(
|
111
|
+
response = while_success(
|
112
|
+
lambda: requests.put(presigned_url, data=content), sleep_time=5
|
113
|
+
)
|
104
114
|
return response.status_code == 200
|
105
115
|
except Exception as e:
|
106
116
|
PrettyOutput.print(f"错误:上传文件失败:{e}", OutputType.ERROR)
|
@@ -109,45 +119,53 @@ class KimiModel(BasePlatform):
|
|
109
119
|
def _get_file_info(self, file_data: Dict, name: str, file_type: str) -> Dict:
|
110
120
|
"""Get file information"""
|
111
121
|
url = "https://kimi.moonshot.cn/api/file"
|
112
|
-
payload = json.dumps(
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
122
|
+
payload = json.dumps(
|
123
|
+
{
|
124
|
+
"type": file_type,
|
125
|
+
"name": name,
|
126
|
+
"object_name": file_data["object_name"],
|
127
|
+
"chat_id": self.chat_id,
|
128
|
+
"file_id": file_data.get("file_id", ""),
|
129
|
+
},
|
130
|
+
ensure_ascii=False,
|
131
|
+
)
|
132
|
+
|
120
133
|
headers = {
|
121
|
-
|
122
|
-
|
134
|
+
"Authorization": self.auth_header,
|
135
|
+
"Content-Type": "application/json",
|
123
136
|
}
|
124
|
-
|
125
|
-
response = while_success(
|
137
|
+
|
138
|
+
response = while_success(
|
139
|
+
lambda: requests.post(url, headers=headers, data=payload), sleep_time=5
|
140
|
+
)
|
126
141
|
return response.json()
|
127
142
|
|
128
143
|
def _wait_for_parse(self, file_id: str) -> bool:
|
129
144
|
"""Wait for file parsing to complete"""
|
130
145
|
url = "https://kimi.moonshot.cn/api/file/parse_process"
|
131
146
|
headers = {
|
132
|
-
|
133
|
-
|
147
|
+
"Authorization": self.auth_header,
|
148
|
+
"Content-Type": "application/json",
|
134
149
|
}
|
135
|
-
|
150
|
+
|
136
151
|
max_retries = 30
|
137
152
|
retry_count = 0
|
138
|
-
|
153
|
+
|
139
154
|
while retry_count < max_retries:
|
140
155
|
payload = json.dumps({"ids": [file_id]}, ensure_ascii=False)
|
141
|
-
response = while_success(
|
142
|
-
|
156
|
+
response = while_success(
|
157
|
+
lambda: requests.post(url, headers=headers, data=payload, stream=True),
|
158
|
+
sleep_time=5,
|
159
|
+
)
|
160
|
+
|
143
161
|
for line in response.iter_lines():
|
144
162
|
if not line:
|
145
163
|
continue
|
146
|
-
|
147
|
-
line = line.decode(
|
164
|
+
|
165
|
+
line = line.decode("utf-8")
|
148
166
|
if not line.startswith("data: "):
|
149
167
|
continue
|
150
|
-
|
168
|
+
|
151
169
|
try:
|
152
170
|
data = json.loads(line[6:])
|
153
171
|
if data.get("event") == "resp":
|
@@ -158,18 +176,19 @@ class KimiModel(BasePlatform):
|
|
158
176
|
return False
|
159
177
|
except json.JSONDecodeError:
|
160
178
|
continue
|
161
|
-
|
179
|
+
|
162
180
|
retry_count += 1
|
163
181
|
time.sleep(1)
|
164
|
-
|
182
|
+
|
165
183
|
return False
|
184
|
+
|
166
185
|
def upload_files(self, file_list: List[str]) -> bool:
|
167
186
|
"""Upload file list and return file information"""
|
168
187
|
if not file_list:
|
169
188
|
return True
|
170
189
|
|
171
190
|
from yaspin import yaspin # type: ignore
|
172
|
-
|
191
|
+
|
173
192
|
if not self.chat_id:
|
174
193
|
with yaspin(text="创建聊天会话...", color="yellow") as spinner:
|
175
194
|
if not self._create_chat():
|
@@ -182,22 +201,30 @@ class KimiModel(BasePlatform):
|
|
182
201
|
uploaded_files = []
|
183
202
|
for index, file_path in enumerate(file_list, 1):
|
184
203
|
file_name = os.path.basename(file_path)
|
185
|
-
with yaspin(
|
204
|
+
with yaspin(
|
205
|
+
text=f"处理文件 [{index}/{len(file_list)}]: {file_name}", color="yellow"
|
206
|
+
) as spinner:
|
186
207
|
try:
|
187
208
|
mime_type, _ = mimetypes.guess_type(file_path)
|
188
|
-
action =
|
189
|
-
|
209
|
+
action = (
|
210
|
+
"image"
|
211
|
+
if mime_type and mime_type.startswith("image/")
|
212
|
+
else "file"
|
213
|
+
)
|
214
|
+
|
190
215
|
# 获取预签名URL
|
191
216
|
spinner.text = f"获取上传URL: {file_name}"
|
192
217
|
presigned_data = self._get_presigned_url(file_path, action)
|
193
|
-
|
218
|
+
|
194
219
|
# 上传文件
|
195
220
|
spinner.text = f"上传文件: {file_name}"
|
196
221
|
if self._upload_file(file_path, presigned_data["url"]):
|
197
222
|
# 获取文件信息
|
198
223
|
spinner.text = f"获取文件信息: {file_name}"
|
199
|
-
file_info = self._get_file_info(
|
200
|
-
|
224
|
+
file_info = self._get_file_info(
|
225
|
+
presigned_data, file_name, action
|
226
|
+
)
|
227
|
+
|
201
228
|
# 只有文件需要解析
|
202
229
|
if action == "file":
|
203
230
|
spinner.text = f"等待文件解析: {file_name}"
|
@@ -211,23 +238,27 @@ class KimiModel(BasePlatform):
|
|
211
238
|
return False
|
212
239
|
else:
|
213
240
|
uploaded_files.append(file_info)
|
214
|
-
spinner.write(
|
241
|
+
spinner.write(f"✅图片处理完成: {file_name}")
|
215
242
|
else:
|
216
243
|
spinner.text = f"文件上传失败: {file_name}"
|
217
244
|
spinner.fail("❌")
|
218
245
|
return False
|
219
|
-
|
246
|
+
|
220
247
|
except Exception as e:
|
221
248
|
spinner.text = f"处理文件出错 {file_path}: {str(e)}"
|
222
249
|
spinner.fail("❌")
|
223
250
|
return False
|
224
|
-
|
251
|
+
|
225
252
|
self.uploaded_files = uploaded_files
|
226
253
|
return True
|
227
254
|
|
228
|
-
|
229
255
|
def chat(self, message: str) -> Generator[str, None, None]:
|
230
|
-
"""
|
256
|
+
"""发送消息并获取响应流
|
257
|
+
参数:
|
258
|
+
message: 要发送的消息内容
|
259
|
+
返回:
|
260
|
+
生成器,逐块返回模型响应
|
261
|
+
"""
|
231
262
|
if not self.chat_id:
|
232
263
|
if not self._create_chat():
|
233
264
|
raise Exception("Failed to create chat session")
|
@@ -258,18 +289,21 @@ class KimiModel(BasePlatform):
|
|
258
289
|
}
|
259
290
|
|
260
291
|
headers = {
|
261
|
-
|
262
|
-
|
292
|
+
"Authorization": self.auth_header,
|
293
|
+
"Content-Type": "application/json",
|
263
294
|
}
|
264
295
|
|
265
296
|
try:
|
266
|
-
response = while_success(
|
297
|
+
response = while_success(
|
298
|
+
lambda: requests.post(url, headers=headers, json=payload, stream=True),
|
299
|
+
sleep_time=5,
|
300
|
+
)
|
267
301
|
# 如果禁止输出,则静默处理
|
268
302
|
for line in response.iter_lines():
|
269
303
|
if not line:
|
270
304
|
continue
|
271
305
|
|
272
|
-
line = line.decode(
|
306
|
+
line = line.decode("utf-8")
|
273
307
|
if not line.startswith("data: "):
|
274
308
|
continue
|
275
309
|
|
@@ -285,7 +319,6 @@ class KimiModel(BasePlatform):
|
|
285
319
|
except json.JSONDecodeError:
|
286
320
|
continue
|
287
321
|
|
288
|
-
|
289
322
|
return None
|
290
323
|
|
291
324
|
except Exception as e:
|
@@ -298,25 +331,28 @@ class KimiModel(BasePlatform):
|
|
298
331
|
|
299
332
|
url = f"https://kimi.moonshot.cn/api/chat/{self.chat_id}"
|
300
333
|
headers = {
|
301
|
-
|
302
|
-
|
334
|
+
"Authorization": self.auth_header,
|
335
|
+
"Content-Type": "application/json",
|
303
336
|
}
|
304
337
|
|
305
338
|
try:
|
306
|
-
response = while_success(
|
339
|
+
response = while_success(
|
340
|
+
lambda: requests.delete(url, headers=headers), sleep_time=5
|
341
|
+
)
|
307
342
|
if response.status_code == 200:
|
308
343
|
self.chat_id = ""
|
309
344
|
self.uploaded_files = []
|
310
345
|
self.first_chat = True # 重置first_chat标记
|
311
346
|
return True
|
312
347
|
else:
|
313
|
-
PrettyOutput.print(
|
348
|
+
PrettyOutput.print(
|
349
|
+
f"删除会话失败: HTTP {response.status_code}", OutputType.WARNING
|
350
|
+
)
|
314
351
|
return False
|
315
352
|
except Exception as e:
|
316
353
|
PrettyOutput.print(f"删除会话时发生错误: {str(e)}", OutputType.ERROR)
|
317
354
|
return False
|
318
355
|
|
319
|
-
|
320
356
|
def name(self) -> str:
|
321
357
|
"""Model name"""
|
322
358
|
return self.model_name
|
jarvis/jarvis_platform/openai.py
CHANGED
@@ -22,23 +22,19 @@ class OpenAIModel(BasePlatform):
|
|
22
22
|
PrettyOutput.print("OPENAI_API_KEY 未设置", OutputType.WARNING)
|
23
23
|
|
24
24
|
self.base_url = os.getenv("OPENAI_API_BASE", "https://api.openai.com/v1")
|
25
|
-
self.model_name =
|
25
|
+
self.model_name = os.getenv("JARVIS_MODEL") or "gpt-4o"
|
26
26
|
|
27
|
-
|
28
|
-
self.client = OpenAI(
|
29
|
-
api_key=self.api_key,
|
30
|
-
base_url=self.base_url
|
31
|
-
)
|
27
|
+
self.client = OpenAI(api_key=self.api_key, base_url=self.base_url)
|
32
28
|
self.messages: List[Dict[str, str]] = []
|
33
29
|
self.system_message = ""
|
34
30
|
|
35
31
|
def upload_files(self, file_list: List[str]) -> bool:
|
36
32
|
"""
|
37
33
|
上传文件到OpenAI平台
|
38
|
-
|
34
|
+
|
39
35
|
参数:
|
40
36
|
file_list: 需要上传的文件路径列表
|
41
|
-
|
37
|
+
|
42
38
|
返回:
|
43
39
|
bool: 上传是否成功 (当前实现始终返回False)
|
44
40
|
"""
|
@@ -47,10 +43,10 @@ class OpenAIModel(BasePlatform):
|
|
47
43
|
def get_model_list(self) -> List[Tuple[str, str]]:
|
48
44
|
"""
|
49
45
|
获取可用的OpenAI模型列表
|
50
|
-
|
46
|
+
|
51
47
|
返回:
|
52
48
|
List[Tuple[str, str]]: 模型ID和名称的元组列表
|
53
|
-
|
49
|
+
|
54
50
|
异常:
|
55
51
|
当API调用失败时会打印错误信息并返回空列表
|
56
52
|
"""
|
@@ -67,7 +63,7 @@ class OpenAIModel(BasePlatform):
|
|
67
63
|
def set_model_name(self, model_name: str):
|
68
64
|
"""
|
69
65
|
设置当前使用的模型名称
|
70
|
-
|
66
|
+
|
71
67
|
参数:
|
72
68
|
model_name: 要设置的模型名称
|
73
69
|
"""
|
@@ -77,10 +73,10 @@ class OpenAIModel(BasePlatform):
|
|
77
73
|
def set_system_prompt(self, message: str):
|
78
74
|
"""
|
79
75
|
设置系统消息(角色设定)
|
80
|
-
|
76
|
+
|
81
77
|
参数:
|
82
78
|
message: 系统消息内容
|
83
|
-
|
79
|
+
|
84
80
|
说明:
|
85
81
|
设置后会立即添加到消息历史中
|
86
82
|
"""
|
@@ -90,13 +86,13 @@ class OpenAIModel(BasePlatform):
|
|
90
86
|
def chat(self, message: str) -> Generator[str, None, None]:
|
91
87
|
"""
|
92
88
|
执行对话并返回生成器
|
93
|
-
|
89
|
+
|
94
90
|
参数:
|
95
91
|
message: 用户输入的消息内容
|
96
|
-
|
92
|
+
|
97
93
|
返回:
|
98
94
|
Generator[str, None, None]: 生成器,逐块返回AI响应内容
|
99
|
-
|
95
|
+
|
100
96
|
异常:
|
101
97
|
当API调用失败时会抛出异常并打印错误信息
|
102
98
|
"""
|
@@ -107,9 +103,9 @@ class OpenAIModel(BasePlatform):
|
|
107
103
|
|
108
104
|
response = self.client.chat.completions.create(
|
109
105
|
model=self.model_name, # Use the configured model name
|
110
|
-
messages=self.messages,
|
111
|
-
stream=True
|
112
|
-
)
|
106
|
+
messages=self.messages, # type: ignore
|
107
|
+
stream=True,
|
108
|
+
) # type: ignore
|
113
109
|
|
114
110
|
full_response = ""
|
115
111
|
for chunk in response:
|
@@ -130,20 +126,19 @@ class OpenAIModel(BasePlatform):
|
|
130
126
|
def name(self) -> str:
|
131
127
|
"""
|
132
128
|
获取当前使用的模型名称
|
133
|
-
|
129
|
+
|
134
130
|
返回:
|
135
131
|
str: 当前配置的模型名称
|
136
132
|
"""
|
137
133
|
return self.model_name
|
138
134
|
|
139
|
-
|
140
|
-
def delete_chat(self)->bool:
|
135
|
+
def delete_chat(self) -> bool:
|
141
136
|
"""
|
142
137
|
删除当前对话历史
|
143
|
-
|
138
|
+
|
144
139
|
返回:
|
145
140
|
bool: 操作是否成功
|
146
|
-
|
141
|
+
|
147
142
|
说明:
|
148
143
|
如果设置了系统消息,会保留系统消息
|
149
144
|
"""
|
@@ -156,17 +151,17 @@ class OpenAIModel(BasePlatform):
|
|
156
151
|
def support_web(self) -> bool:
|
157
152
|
"""
|
158
153
|
检查是否支持网页访问功能
|
159
|
-
|
154
|
+
|
160
155
|
返回:
|
161
156
|
bool: 当前是否支持网页访问 (OpenAI平台始终返回False)
|
162
157
|
"""
|
163
158
|
return False
|
164
|
-
|
159
|
+
|
165
160
|
def support_upload_files(self) -> bool:
|
166
161
|
"""
|
167
162
|
检查是否支持上传文件功能
|
168
|
-
|
163
|
+
|
169
164
|
返回:
|
170
165
|
bool: 当前是否支持上传文件 (OpenAI平台始终返回False)
|
171
166
|
"""
|
172
|
-
return False
|
167
|
+
return False
|
@@ -13,20 +13,21 @@ from jarvis.jarvis_utils.config import (get_data_dir, get_normal_model_name,
|
|
13
13
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
14
14
|
|
15
15
|
REQUIRED_METHODS = [
|
16
|
-
(
|
17
|
-
(
|
18
|
-
(
|
19
|
-
(
|
20
|
-
(
|
21
|
-
(
|
22
|
-
(
|
16
|
+
("chat", ["message"]), # 方法名和参数列表
|
17
|
+
("name", []),
|
18
|
+
("delete_chat", []),
|
19
|
+
("set_system_prompt", ["message"]),
|
20
|
+
("set_model_name", ["model_name"]),
|
21
|
+
("get_model_list", []),
|
22
|
+
("upload_files", ["file_list"]),
|
23
23
|
]
|
24
24
|
|
25
|
+
|
25
26
|
class PlatformRegistry:
|
26
27
|
"""Platform registry"""
|
27
28
|
|
28
29
|
global_platform_name: str = "yuanbao"
|
29
|
-
global_platform_registry: Optional[
|
30
|
+
global_platform_registry: Optional["PlatformRegistry"] = None
|
30
31
|
|
31
32
|
@staticmethod
|
32
33
|
def get_platform_dir() -> str:
|
@@ -35,7 +36,9 @@ class PlatformRegistry:
|
|
35
36
|
try:
|
36
37
|
os.makedirs(user_platform_dir)
|
37
38
|
# 创建 __init__.py 使其成为 Python 包
|
38
|
-
with open(
|
39
|
+
with open(
|
40
|
+
os.path.join(user_platform_dir, "__init__.py"), "w", errors="ignore"
|
41
|
+
) as f:
|
39
42
|
pass
|
40
43
|
except Exception as e:
|
41
44
|
PrettyOutput.print(f"创建平台目录失败: {str(e)}", OutputType.ERROR)
|
@@ -66,15 +69,16 @@ class PlatformRegistry:
|
|
66
69
|
|
67
70
|
# 检查方法参数
|
68
71
|
import inspect
|
72
|
+
|
69
73
|
sig = inspect.signature(method)
|
70
|
-
method_params = [p for p in sig.parameters if p !=
|
74
|
+
method_params = [p for p in sig.parameters if p != "self"]
|
71
75
|
if len(method_params) != len(params):
|
72
76
|
missing_methods.append(f"{method_name}(parameter mismatch)")
|
73
77
|
|
74
78
|
if missing_methods:
|
75
79
|
PrettyOutput.print(
|
76
80
|
f"平台 {platform_class.__name__} 缺少必要的方法: {', '.join(missing_methods)}",
|
77
|
-
OutputType.WARNING
|
81
|
+
OutputType.WARNING,
|
78
82
|
)
|
79
83
|
return False
|
80
84
|
|
@@ -108,35 +112,40 @@ class PlatformRegistry:
|
|
108
112
|
|
109
113
|
# 遍历目录下的所有.py文件
|
110
114
|
for filename in os.listdir(directory):
|
111
|
-
if filename.endswith(
|
115
|
+
if filename.endswith(".py") and not filename.startswith("__"):
|
112
116
|
module_name = filename[:-3] # 移除.py后缀
|
113
117
|
try:
|
114
118
|
# 导入模块
|
115
119
|
if package_name:
|
116
|
-
module = importlib.import_module(
|
120
|
+
module = importlib.import_module(
|
121
|
+
f"{package_name}.{module_name}"
|
122
|
+
)
|
117
123
|
else:
|
118
124
|
module = importlib.import_module(module_name)
|
119
125
|
|
120
126
|
# 遍历模块中的所有类
|
121
127
|
for _, obj in inspect.getmembers(module):
|
122
128
|
# 检查是否是BasePlatform的子类,但不是BasePlatform本身
|
123
|
-
if (
|
124
|
-
|
125
|
-
obj
|
126
|
-
|
129
|
+
if (
|
130
|
+
inspect.isclass(obj)
|
131
|
+
and issubclass(obj, BasePlatform)
|
132
|
+
and obj != BasePlatform
|
133
|
+
and hasattr(obj, "platform_name")
|
134
|
+
):
|
127
135
|
# 检查平台实现
|
128
136
|
if not PlatformRegistry.check_platform_implementation(obj):
|
129
137
|
continue
|
130
|
-
platforms[obj.platform_name] = obj
|
138
|
+
platforms[obj.platform_name] = obj # type: ignore
|
131
139
|
break
|
132
140
|
except Exception as e:
|
133
|
-
PrettyOutput.print(
|
141
|
+
PrettyOutput.print(
|
142
|
+
f"加载平台 {module_name} 失败: {str(e)}", OutputType.ERROR
|
143
|
+
)
|
134
144
|
|
135
145
|
return platforms
|
136
146
|
|
137
|
-
|
138
147
|
@staticmethod
|
139
|
-
def get_global_platform_registry() ->
|
148
|
+
def get_global_platform_registry() -> "PlatformRegistry":
|
140
149
|
"""Get global platform registry"""
|
141
150
|
if PlatformRegistry.global_platform_registry is None:
|
142
151
|
PlatformRegistry.global_platform_registry = PlatformRegistry()
|
@@ -148,27 +157,32 @@ class PlatformRegistry:
|
|
148
157
|
# 从用户平台目录加载额外平台
|
149
158
|
platform_dir = PlatformRegistry.get_platform_dir()
|
150
159
|
if platform_dir and os.path.exists(platform_dir):
|
151
|
-
for
|
160
|
+
for (
|
161
|
+
platform_name,
|
162
|
+
platform_class,
|
163
|
+
) in PlatformRegistry.load_platform_from_dir(platform_dir).items():
|
152
164
|
self.register_platform(platform_name, platform_class)
|
153
165
|
platform_dir = os.path.dirname(__file__)
|
154
166
|
if platform_dir and os.path.exists(platform_dir):
|
155
|
-
for
|
167
|
+
for (
|
168
|
+
platform_name,
|
169
|
+
platform_class,
|
170
|
+
) in PlatformRegistry.load_platform_from_dir(platform_dir).items():
|
156
171
|
self.register_platform(platform_name, platform_class)
|
157
172
|
|
158
|
-
|
159
173
|
def get_normal_platform(self) -> BasePlatform:
|
160
174
|
platform_name = get_normal_platform_name()
|
161
175
|
model_name = get_normal_model_name()
|
162
176
|
platform = self.create_platform(platform_name)
|
163
|
-
platform.set_model_name(model_name)
|
164
|
-
return platform
|
177
|
+
platform.set_model_name(model_name) # type: ignore
|
178
|
+
return platform # type: ignore
|
165
179
|
|
166
180
|
def get_thinking_platform(self) -> BasePlatform:
|
167
181
|
platform_name = get_thinking_platform_name()
|
168
182
|
model_name = get_thinking_model_name()
|
169
183
|
platform = self.create_platform(platform_name)
|
170
|
-
platform.set_model_name(model_name)
|
171
|
-
return platform
|
184
|
+
platform.set_model_name(model_name) # type: ignore
|
185
|
+
return platform # type: ignore
|
172
186
|
|
173
187
|
def register_platform(self, name: str, platform_class: Type[BasePlatform]) -> None:
|
174
188
|
"""Register platform class
|
@@ -203,4 +217,4 @@ class PlatformRegistry:
|
|
203
217
|
|
204
218
|
def get_available_platforms(self) -> List[str]:
|
205
219
|
"""Get available platform list"""
|
206
|
-
return list(self.platforms.keys())
|
220
|
+
return list(self.platforms.keys())
|