jarvis-ai-assistant 0.2.7__py3-none-any.whl → 0.3.0__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 (38) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +267 -240
  3. jarvis/jarvis_agent/agent_manager.py +85 -0
  4. jarvis/jarvis_agent/config_editor.py +53 -0
  5. jarvis/jarvis_agent/file_methodology_manager.py +105 -0
  6. jarvis/jarvis_agent/jarvis.py +37 -398
  7. jarvis/jarvis_agent/memory_manager.py +133 -0
  8. jarvis/jarvis_agent/methodology_share_manager.py +174 -0
  9. jarvis/jarvis_agent/prompts.py +18 -3
  10. jarvis/jarvis_agent/share_manager.py +176 -0
  11. jarvis/jarvis_agent/task_analyzer.py +126 -0
  12. jarvis/jarvis_agent/task_manager.py +111 -0
  13. jarvis/jarvis_agent/tool_share_manager.py +139 -0
  14. jarvis/jarvis_code_agent/code_agent.py +26 -20
  15. jarvis/jarvis_data/config_schema.json +37 -0
  16. jarvis/jarvis_platform/ai8.py +13 -1
  17. jarvis/jarvis_platform/base.py +20 -5
  18. jarvis/jarvis_platform/human.py +11 -1
  19. jarvis/jarvis_platform/kimi.py +10 -0
  20. jarvis/jarvis_platform/openai.py +20 -0
  21. jarvis/jarvis_platform/tongyi.py +14 -9
  22. jarvis/jarvis_platform/yuanbao.py +10 -0
  23. jarvis/jarvis_platform_manager/main.py +12 -12
  24. jarvis/jarvis_tools/registry.py +79 -20
  25. jarvis/jarvis_tools/retrieve_memory.py +36 -8
  26. jarvis/jarvis_utils/clipboard.py +90 -0
  27. jarvis/jarvis_utils/config.py +64 -0
  28. jarvis/jarvis_utils/git_utils.py +17 -7
  29. jarvis/jarvis_utils/globals.py +18 -12
  30. jarvis/jarvis_utils/input.py +118 -16
  31. jarvis/jarvis_utils/methodology.py +48 -5
  32. jarvis/jarvis_utils/utils.py +196 -106
  33. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/METADATA +1 -1
  34. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/RECORD +38 -28
  35. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/WHEEL +0 -0
  36. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/entry_points.txt +0 -0
  37. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/licenses/LICENSE +0 -0
  38. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,139 @@
1
+ # -*- coding: utf-8 -*-
2
+ """工具分享管理模块"""
3
+ import os
4
+ import glob
5
+ import shutil
6
+ from typing import List, Dict, Any, Set
7
+
8
+ import typer
9
+
10
+ from jarvis.jarvis_agent import OutputType, PrettyOutput, user_confirm
11
+ from jarvis.jarvis_agent.share_manager import ShareManager
12
+ from jarvis.jarvis_utils.config import get_central_tool_repo, get_data_dir
13
+
14
+
15
+ class ToolShareManager(ShareManager):
16
+ """工具分享管理器"""
17
+
18
+ def __init__(self):
19
+ central_repo = get_central_tool_repo()
20
+ if not central_repo:
21
+ PrettyOutput.print(
22
+ "错误:未配置中心工具仓库(JARVIS_CENTRAL_TOOL_REPO)",
23
+ OutputType.ERROR,
24
+ )
25
+ PrettyOutput.print(
26
+ "请在配置文件中设置中心工具仓库的Git地址", OutputType.INFO
27
+ )
28
+ raise typer.Exit(code=1)
29
+
30
+ super().__init__(central_repo, "central_tool_repo")
31
+
32
+ def get_resource_type(self) -> str:
33
+ """获取资源类型名称"""
34
+ return "工具"
35
+
36
+ def format_resource_display(self, resource: Dict[str, Any]) -> str:
37
+ """格式化资源显示"""
38
+ return f"{resource['tool_name']} ({resource['filename']})"
39
+
40
+ def get_existing_resources(self) -> Set[str]:
41
+ """获取中心仓库中已有的工具文件名"""
42
+ existing_tools = set()
43
+ for filepath in glob.glob(os.path.join(self.repo_path, "*.py")):
44
+ existing_tools.add(os.path.basename(filepath))
45
+ return existing_tools
46
+
47
+ def get_local_resources(self) -> List[Dict[str, Any]]:
48
+ """获取本地工具"""
49
+ # 获取中心仓库中已有的工具文件名
50
+ existing_tools = self.get_existing_resources()
51
+
52
+ # 只从数据目录的tools目录获取工具
53
+ local_tools_dir = os.path.join(get_data_dir(), "tools")
54
+ if not os.path.exists(local_tools_dir):
55
+ PrettyOutput.print(
56
+ f"本地工具目录不存在: {local_tools_dir}",
57
+ OutputType.WARNING,
58
+ )
59
+ return []
60
+
61
+ # 收集本地工具文件(排除已存在的)
62
+ tool_files = []
63
+ for filepath in glob.glob(os.path.join(local_tools_dir, "*.py")):
64
+ filename = os.path.basename(filepath)
65
+ # 跳过__init__.py和已存在的文件
66
+ if filename == "__init__.py" or filename in existing_tools:
67
+ continue
68
+
69
+ # 尝试获取工具名称(通过简单解析)
70
+ tool_name = filename[:-3] # 移除.py后缀
71
+ tool_files.append(
72
+ {
73
+ "path": filepath,
74
+ "filename": filename,
75
+ "tool_name": tool_name,
76
+ }
77
+ )
78
+
79
+ return tool_files
80
+
81
+ def share_resources(self, resources: List[Dict[str, Any]]) -> List[str]:
82
+ """分享工具到中心仓库"""
83
+ # 确认操作
84
+ share_list = ["\n将要分享以下工具到中心仓库(注意:文件将被移动而非复制):"]
85
+ for tool in resources:
86
+ share_list.append(f"- {tool['tool_name']} ({tool['filename']})")
87
+ PrettyOutput.print("\n".join(share_list), OutputType.WARNING)
88
+
89
+ if not user_confirm("确认移动这些工具到中心仓库吗?(原文件将被删除)"):
90
+ return []
91
+
92
+ # 移动选中的工具到中心仓库
93
+ moved_list = []
94
+ for tool in resources:
95
+ src_file = tool["path"]
96
+ dst_file = os.path.join(self.repo_path, tool["filename"])
97
+ shutil.move(src_file, dst_file) # 使用move而不是copy
98
+ moved_list.append(f"已移动: {tool['tool_name']}")
99
+
100
+ return moved_list
101
+
102
+ def run(self) -> None:
103
+ """执行工具分享流程"""
104
+ try:
105
+ # 更新中心仓库
106
+ self.update_central_repo()
107
+
108
+ # 获取本地资源
109
+ local_resources = self.get_local_resources()
110
+ if not local_resources:
111
+ PrettyOutput.print(
112
+ "没有找到新的工具文件(所有工具可能已存在于中心仓库)",
113
+ OutputType.WARNING,
114
+ )
115
+ raise typer.Exit(code=0)
116
+
117
+ # 选择要分享的资源
118
+ selected_resources = self.select_resources(local_resources)
119
+ if not selected_resources:
120
+ raise typer.Exit(code=0)
121
+
122
+ # 分享资源
123
+ moved_list = self.share_resources(selected_resources)
124
+ if moved_list:
125
+ # 一次性显示所有移动结果
126
+ PrettyOutput.print("\n".join(moved_list), OutputType.SUCCESS)
127
+
128
+ # 提交并推送
129
+ self.commit_and_push(len(selected_resources))
130
+
131
+ PrettyOutput.print("\n工具已成功分享到中心仓库!", OutputType.SUCCESS)
132
+ PrettyOutput.print(
133
+ f"原文件已从 {os.path.join(get_data_dir(), 'tools')} 移动到中心仓库",
134
+ OutputType.INFO,
135
+ )
136
+
137
+ except Exception as e:
138
+ PrettyOutput.print(f"分享工具时出错: {str(e)}", OutputType.ERROR)
139
+ raise typer.Exit(code=1)
@@ -17,7 +17,6 @@ from jarvis.jarvis_agent.edit_file_handler import EditFileHandler
17
17
  from jarvis.jarvis_agent.shell_input_handler import shell_input_handler
18
18
  from jarvis.jarvis_code_agent.lint import get_lint_tools
19
19
  from jarvis.jarvis_git_utils.git_commiter import GitCommitTool
20
- from jarvis.jarvis_platform.registry import PlatformRegistry
21
20
  from jarvis.jarvis_tools.registry import ToolRegistry
22
21
  from jarvis.jarvis_utils.config import (
23
22
  is_confirm_before_apply_patch,
@@ -70,7 +69,25 @@ class CodeAgent:
70
69
  "clear_memory",
71
70
  ]
72
71
  )
73
- code_system_prompt = """
72
+ code_system_prompt = self._get_system_prompt()
73
+ self.agent = Agent(
74
+ system_prompt=code_system_prompt,
75
+ name="CodeAgent",
76
+ auto_complete=False,
77
+ output_handler=[tool_registry, EditFileHandler()], # type: ignore
78
+ llm_type=llm_type,
79
+ model_group=model_group,
80
+ input_handler=[shell_input_handler, builtin_input_handler],
81
+ need_summary=need_summary,
82
+ use_methodology=False, # 禁用方法论
83
+ use_analysis=False, # 禁用分析
84
+ )
85
+
86
+ self.agent.set_after_tool_call_cb(self.after_tool_call_cb)
87
+
88
+ def _get_system_prompt(self) -> str:
89
+ """获取代码工程师的系统提示词"""
90
+ return """
74
91
  <code_engineer_guide>
75
92
  ## 角色定位
76
93
  你是Jarvis系统的代码工程师,一个专业的代码分析和修改助手。你的职责是:
@@ -124,20 +141,6 @@ class CodeAgent:
124
141
  10. 我不订阅闲 AI
125
142
  </say_to_llm>
126
143
  """
127
- self.agent = Agent(
128
- system_prompt=code_system_prompt,
129
- name="CodeAgent",
130
- auto_complete=False,
131
- output_handler=[tool_registry, EditFileHandler()], # type: ignore
132
- llm_type=llm_type,
133
- model_group=model_group,
134
- input_handler=[shell_input_handler, builtin_input_handler],
135
- need_summary=need_summary,
136
- use_methodology=False, # 禁用方法论
137
- use_analysis=False, # 禁用分析
138
- )
139
-
140
- self.agent.set_after_tool_call_cb(self.after_tool_call_cb)
141
144
 
142
145
  def _check_git_config(self) -> None:
143
146
  """检查 git username 和 email 是否已设置,如果没有则提示并退出"""
@@ -209,11 +212,11 @@ class CodeAgent:
209
212
  jarvis_ignore = ".jarvis"
210
213
 
211
214
  if not os.path.exists(gitignore_path):
212
- with open(gitignore_path, "w") as f:
215
+ with open(gitignore_path, "w", encoding="utf-8") as f:
213
216
  f.write(f"{jarvis_ignore}\n")
214
217
  print(f"✅ 已创建.gitignore文件并添加'{jarvis_ignore}'")
215
218
  else:
216
- with open(gitignore_path, "r+") as f:
219
+ with open(gitignore_path, "r+", encoding="utf-8") as f:
217
220
  content = f.read()
218
221
  if jarvis_ignore not in content.splitlines():
219
222
  f.write(f"\n{jarvis_ignore}\n")
@@ -260,7 +263,10 @@ class CodeAgent:
260
263
  current_settings = {}
261
264
  for key, target_value in target_settings.items():
262
265
  result = subprocess.run(
263
- ["git", "config", "--get", key], capture_output=True, text=True
266
+ ["git", "config", "--get", key],
267
+ capture_output=True,
268
+ text=True,
269
+ check=False,
264
270
  )
265
271
  current_value = result.stdout.strip()
266
272
  current_settings[key] = current_value
@@ -522,7 +528,7 @@ class CodeAgent:
522
528
 
523
529
  if project_info:
524
530
  enhanced_input = (
525
- f"项目概况:\n"
531
+ "项目概况:\n"
526
532
  + "\n\n".join(project_info)
527
533
  + "\n\n"
528
534
  + first_tip
@@ -230,6 +230,11 @@
230
230
  "description": "中心方法论Git仓库地址,该仓库会自动添加到方法论加载路径中",
231
231
  "default": ""
232
232
  },
233
+ "JARVIS_CENTRAL_TOOL_REPO": {
234
+ "type": "string",
235
+ "description": "中心工具库Git仓库地址,该仓库会自动克隆到数据目录并加载其中的工具",
236
+ "default": ""
237
+ },
233
238
  "JARVIS_PRINT_PROMPT": {
234
239
  "type": "boolean",
235
240
  "description": "是否打印提示",
@@ -240,6 +245,38 @@
240
245
  "description": "是否启用静态代码分析",
241
246
  "default": true
242
247
  },
248
+ "JARVIS_TOOL_GROUP": {
249
+ "type": "string",
250
+ "description": "选择一个预定义的工具配置组",
251
+ "default": ""
252
+ },
253
+ "JARVIS_TOOL_GROUPS": {
254
+ "type": "array",
255
+ "description": "预定义的工具配置组",
256
+ "default": [],
257
+ "items": {
258
+ "type": "object",
259
+ "additionalProperties": {
260
+ "type": "object",
261
+ "properties": {
262
+ "use": {
263
+ "type": "array",
264
+ "description": "要使用的工具名称列表",
265
+ "items": {
266
+ "type": "string"
267
+ }
268
+ },
269
+ "dont_use": {
270
+ "type": "array",
271
+ "description": "不使用的工具名称列表",
272
+ "items": {
273
+ "type": "string"
274
+ }
275
+ }
276
+ }
277
+ }
278
+ }
279
+ },
243
280
  "JARVIS_RAG_GROUP": {
244
281
  "type": "string",
245
282
  "description": "选择一个预定义的RAG配置组",
@@ -250,7 +250,9 @@ class AI8Model(BasePlatform):
250
250
  PrettyOutput.print(f"会话文件未找到: {file_path}", OutputType.ERROR)
251
251
  return False
252
252
  except KeyError as e:
253
- PrettyOutput.print(f"恢复失败: 会话文件格式不正确,缺少键 {e}", OutputType.ERROR)
253
+ PrettyOutput.print(
254
+ f"恢复失败: 会话文件格式不正确,缺少键 {e}", OutputType.ERROR
255
+ )
254
256
  return False
255
257
  except Exception as e:
256
258
  PrettyOutput.print(f"恢复会话失败: {str(e)}", OutputType.ERROR)
@@ -322,3 +324,13 @@ class AI8Model(BasePlatform):
322
324
 
323
325
  def upload_files(self, file_list: List[str]) -> bool:
324
326
  return False
327
+
328
+ @classmethod
329
+ def get_required_env_keys(cls) -> List[str]:
330
+ """
331
+ 获取AI8平台所需的环境变量键列表
332
+
333
+ 返回:
334
+ List[str]: 环境变量键的列表
335
+ """
336
+ return ["AI8_API_KEY"]
@@ -2,7 +2,7 @@
2
2
  import re
3
3
  from abc import ABC, abstractmethod
4
4
  from types import TracebackType
5
- from typing import Generator, List, Optional, Tuple, Type
5
+ from typing import Dict, Generator, List, Optional, Tuple, Type
6
6
 
7
7
  from typing_extensions import Self
8
8
 
@@ -87,7 +87,7 @@ class BasePlatform(ABC):
87
87
  prefix_prompt = f"""
88
88
  我将分多次提供大量内容,在我明确告诉你内容已经全部提供完毕之前,每次仅需要输出"已收到",明白请输出"开始接收输入"。
89
89
  """
90
- while_true(lambda: while_success(lambda: self.chat(prefix_prompt), 5), 5)
90
+ while_true(lambda: while_success(lambda: self._chat(prefix_prompt), 5), 5)
91
91
  submit_count = 0
92
92
  length = 0
93
93
  response = ""
@@ -101,7 +101,7 @@ class BasePlatform(ABC):
101
101
  response += "\n"
102
102
  for trunk in while_true(
103
103
  lambda: while_success(
104
- lambda: self.chat(
104
+ lambda: self._chat(
105
105
  f"<part_content>{input}</part_content>\n\n请返回<已收到>,不需要返回其他任何内容"
106
106
  ),
107
107
  5,
@@ -110,10 +110,14 @@ class BasePlatform(ABC):
110
110
  ):
111
111
  response += trunk
112
112
 
113
- print(f"📤 提交第{submit_count}部分完成,当前进度:{length}/{len(message)}")
113
+ print(
114
+ f"📤 提交第{submit_count}部分完成,当前进度:{length}/{len(message)}"
115
+ )
114
116
  print("✅ 提交完成")
115
117
  response += "\n" + while_true(
116
- lambda: while_success(lambda: self._chat("内容已经全部提供完毕,请根据内容继续"), 5),
118
+ lambda: while_success(
119
+ lambda: self._chat("内容已经全部提供完毕,请根据内容继续"), 5
120
+ ),
117
121
  5,
118
122
  )
119
123
  else:
@@ -240,6 +244,17 @@ class BasePlatform(ABC):
240
244
  """Get model list"""
241
245
  raise NotImplementedError("get_model_list is not implemented")
242
246
 
247
+ @classmethod
248
+ @abstractmethod
249
+ def get_required_env_keys(cls) -> List[str]:
250
+ """Get required env keys"""
251
+ raise NotImplementedError("get_required_env_keys is not implemented")
252
+
253
+ @classmethod
254
+ def get_env_defaults(cls) -> Dict[str, str]:
255
+ """Get env default values"""
256
+ return {}
257
+
243
258
  def set_suppress_output(self, suppress: bool):
244
259
  """Set whether to suppress output"""
245
260
  self.suppress_output = suppress
@@ -10,9 +10,9 @@ import string
10
10
  from typing import Generator, List, Tuple
11
11
 
12
12
  from jarvis.jarvis_platform.base import BasePlatform
13
+ from jarvis.jarvis_utils.clipboard import copy_to_clipboard
13
14
  from jarvis.jarvis_utils.input import get_multiline_input
14
15
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
15
- from jarvis.jarvis_utils.utils import copy_to_clipboard
16
16
 
17
17
 
18
18
  class HumanPlatform(BasePlatform):
@@ -131,3 +131,13 @@ class HumanPlatform(BasePlatform):
131
131
  def support_upload_files(self) -> bool:
132
132
  """是否支持文件上传功能"""
133
133
  return False
134
+
135
+ @classmethod
136
+ def get_required_env_keys(cls) -> List[str]:
137
+ """
138
+ 获取Human平台所需的环境变量键列表
139
+
140
+ 返回:
141
+ List[str]: 环境变量键的列表
142
+ """
143
+ return []
@@ -395,3 +395,13 @@ class KimiModel(BasePlatform):
395
395
  def support_web(self) -> bool:
396
396
  """Kimi平台支持web功能"""
397
397
  return True
398
+
399
+ @classmethod
400
+ def get_required_env_keys(cls) -> List[str]:
401
+ """
402
+ 获取Kimi平台所需的环境变量键列表
403
+
404
+ 返回:
405
+ List[str]: 环境变量键的列表
406
+ """
407
+ return ["KIMI_API_KEY"]
@@ -211,3 +211,23 @@ class OpenAIModel(BasePlatform):
211
211
  bool: 当前是否支持上传文件 (OpenAI平台始终返回False)
212
212
  """
213
213
  return False
214
+
215
+ @classmethod
216
+ def get_required_env_keys(cls) -> List[str]:
217
+ """
218
+ 获取OpenAI平台所需的环境变量键列表
219
+
220
+ 返回:
221
+ List[str]: 环境变量键的列表
222
+ """
223
+ return ["OPENAI_API_KEY", "OPENAI_API_BASE"]
224
+
225
+ @classmethod
226
+ def get_env_defaults(cls) -> Dict[str, str]:
227
+ """
228
+ 获取OpenAI平台环境变量的默认值
229
+
230
+ 返回:
231
+ Dict[str, str]: 环境变量默认值的字典
232
+ """
233
+ return {"OPENAI_API_BASE": "https://api.openai.com/v1"}
@@ -11,6 +11,7 @@ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
11
11
  from jarvis.jarvis_utils.tag import ot, ct
12
12
  from jarvis.jarvis_utils.utils import while_success
13
13
 
14
+
14
15
  class TongyiPlatform(BasePlatform):
15
16
  """Tongyi platform implementation"""
16
17
 
@@ -200,12 +201,8 @@ class TongyiPlatform(BasePlatform):
200
201
  tmp_content = json.loads(
201
202
  content.get("content")
202
203
  )["content"]
203
- if len(thinking_content) < len(
204
- tmp_content
205
- ):
206
- yield tmp_content[
207
- len(thinking_content) :
208
- ]
204
+ if len(thinking_content) < len(tmp_content):
205
+ yield tmp_content[len(thinking_content) :]
209
206
  thinking_content = tmp_content
210
207
  else:
211
208
  yield f"\r\n{ct('think')}\n"[
@@ -224,9 +221,7 @@ class TongyiPlatform(BasePlatform):
224
221
  else:
225
222
  tmp_content = content.get("content")
226
223
  if len(text_content) < len(tmp_content):
227
- yield tmp_content[
228
- len(text_content) :
229
- ]
224
+ yield tmp_content[len(text_content) :]
230
225
  text_content = tmp_content
231
226
 
232
227
  except json.JSONDecodeError:
@@ -562,3 +557,13 @@ class TongyiPlatform(BasePlatform):
562
557
  bool: True if web is supported, False otherwise
563
558
  """
564
559
  return True
560
+
561
+ @classmethod
562
+ def get_required_env_keys(cls) -> List[str]:
563
+ """
564
+ 获取通义平台所需的环境变量键列表
565
+
566
+ 返回:
567
+ List[str]: 环境变量键的列表
568
+ """
569
+ return ["TONGYI_COOKIES"]
@@ -623,3 +623,13 @@ class YuanbaoPlatform(BasePlatform):
623
623
  def support_web(self) -> bool:
624
624
  """Yuanbao平台支持web功能"""
625
625
  return True
626
+
627
+ @classmethod
628
+ def get_required_env_keys(cls) -> List[str]:
629
+ """
630
+ 获取元宝平台所需的环境变量键列表
631
+
632
+ 返回:
633
+ List[str]: 环境变量键的列表
634
+ """
635
+ return ["YUANBAO_COOKIES"]
@@ -27,13 +27,13 @@ def list_platforms() -> None:
27
27
  PrettyOutput.section("Supported platforms and models", OutputType.SUCCESS)
28
28
 
29
29
  for platform_name in platforms:
30
- # Create platform instance
31
- platform = registry.create_platform(platform_name)
32
- if not platform:
33
- continue
34
-
35
- # Get the list of models supported by the platform
36
30
  try:
31
+ # Create platform instance
32
+ platform = registry.create_platform(platform_name)
33
+ if not platform:
34
+ continue
35
+
36
+ # Get the list of models supported by the platform
37
37
  models = platform.get_model_list()
38
38
 
39
39
  # Print platform name
@@ -51,10 +51,8 @@ def list_platforms() -> None:
51
51
  else:
52
52
  PrettyOutput.print(" • 没有可用的模型信息", OutputType.WARNING)
53
53
 
54
- except Exception as exc:
55
- PrettyOutput.print(
56
- f"获取 {platform_name} 的模型列表失败: {str(exc)}", OutputType.WARNING
57
- )
54
+ except Exception:
55
+ PrettyOutput.print(f"创建 {platform_name} 平台失败", OutputType.WARNING)
58
56
 
59
57
 
60
58
  def chat_with_model(platform_name: str, model_name: str, system_prompt: str) -> None:
@@ -330,13 +328,15 @@ def validate_platform_model(platform: Optional[str], model: Optional[str]) -> bo
330
328
 
331
329
  @app.command("chat")
332
330
  def chat_command(
333
- platform: Optional[str] = typer.Option(None, "--platform", "-p", help="指定要使用的平台"),
331
+ platform: Optional[str] = typer.Option(
332
+ None, "--platform", "-p", help="指定要使用的平台"
333
+ ),
334
334
  model: Optional[str] = typer.Option(None, "--model", "-m", help="指定要使用的模型"),
335
335
  ) -> None:
336
336
  """与指定平台和模型聊天。"""
337
337
  if not validate_platform_model(platform, model):
338
338
  return
339
- chat_with_model(platform, model, "") # type: ignore
339
+ chat_with_model(platform, model, "") # type: ignore
340
340
 
341
341
 
342
342
  @app.command("service")