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