jarvis-ai-assistant 0.1.96__py3-none-any.whl → 0.1.98__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.
Potentially problematic release.
This version of jarvis-ai-assistant might be problematic. Click here for more details.
- jarvis/__init__.py +1 -1
- jarvis/agent.py +138 -144
- jarvis/jarvis_codebase/main.py +87 -54
- jarvis/jarvis_coder/git_utils.py +22 -25
- jarvis/jarvis_coder/main.py +166 -171
- jarvis/jarvis_coder/patch_handler.py +153 -453
- jarvis/jarvis_coder/plan_generator.py +76 -48
- jarvis/jarvis_platform/main.py +39 -39
- jarvis/jarvis_rag/main.py +182 -182
- jarvis/jarvis_smart_shell/main.py +34 -34
- jarvis/main.py +24 -24
- jarvis/models/ai8.py +22 -22
- jarvis/models/base.py +17 -13
- jarvis/models/kimi.py +31 -31
- jarvis/models/ollama.py +28 -28
- jarvis/models/openai.py +22 -24
- jarvis/models/oyi.py +25 -25
- jarvis/models/registry.py +33 -34
- jarvis/tools/ask_user.py +5 -5
- jarvis/tools/base.py +2 -2
- jarvis/tools/chdir.py +9 -9
- jarvis/tools/codebase_qa.py +4 -4
- jarvis/tools/coder.py +4 -4
- jarvis/tools/file_ops.py +1 -1
- jarvis/tools/generator.py +23 -23
- jarvis/tools/methodology.py +4 -4
- jarvis/tools/rag.py +4 -4
- jarvis/tools/registry.py +38 -38
- jarvis/tools/search.py +42 -42
- jarvis/tools/shell.py +13 -13
- jarvis/tools/sub_agent.py +16 -16
- jarvis/tools/thinker.py +41 -41
- jarvis/tools/webpage.py +17 -17
- jarvis/utils.py +59 -60
- {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.98.dist-info}/METADATA +1 -1
- jarvis_ai_assistant-0.1.98.dist-info/RECORD +47 -0
- jarvis_ai_assistant-0.1.96.dist-info/RECORD +0 -47
- {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.98.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.98.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.98.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.98.dist-info}/top_level.txt +0 -0
|
@@ -11,33 +11,33 @@ from jarvis.models.registry import PlatformRegistry
|
|
|
11
11
|
from jarvis.utils import PrettyOutput, OutputType, load_env_from_file
|
|
12
12
|
|
|
13
13
|
def execute_command(command: str) -> None:
|
|
14
|
-
"""
|
|
14
|
+
"""Show command and allow user to edit, then execute, Ctrl+C to cancel"""
|
|
15
15
|
try:
|
|
16
|
-
print("\
|
|
17
|
-
#
|
|
16
|
+
print("\nGenerated command (can be edited, press Enter to execute, Ctrl+C to cancel):")
|
|
17
|
+
# Pre-fill input line
|
|
18
18
|
readline.set_startup_hook(lambda: readline.insert_text(command))
|
|
19
19
|
try:
|
|
20
20
|
edited_command = input("> ")
|
|
21
|
-
if edited_command.strip(): #
|
|
21
|
+
if edited_command.strip(): # Ensure command is not empty
|
|
22
22
|
os.system(edited_command)
|
|
23
23
|
except KeyboardInterrupt:
|
|
24
|
-
print("\
|
|
24
|
+
print("\nExecution cancelled")
|
|
25
25
|
finally:
|
|
26
|
-
readline.set_startup_hook() #
|
|
26
|
+
readline.set_startup_hook() # Clear pre-filled
|
|
27
27
|
except Exception as e:
|
|
28
|
-
PrettyOutput.print(f"
|
|
28
|
+
PrettyOutput.print(f"Failed to execute command: {str(e)}", OutputType.ERROR)
|
|
29
29
|
|
|
30
30
|
def process_request(request: str) -> Optional[str]:
|
|
31
|
-
"""
|
|
31
|
+
"""Process user request and return corresponding shell command
|
|
32
32
|
|
|
33
33
|
Args:
|
|
34
|
-
request:
|
|
34
|
+
request: User's natural language request
|
|
35
35
|
|
|
36
36
|
Returns:
|
|
37
|
-
Optional[str]:
|
|
37
|
+
Optional[str]: Corresponding shell command, return None if processing fails
|
|
38
38
|
"""
|
|
39
39
|
try:
|
|
40
|
-
#
|
|
40
|
+
# Get language model instance
|
|
41
41
|
PlatformRegistry.suppress_output = True
|
|
42
42
|
model = PlatformRegistry.get_global_platform_registry().get_normal_platform()
|
|
43
43
|
model.set_suppress_output(True)
|
|
@@ -45,35 +45,35 @@ def process_request(request: str) -> Optional[str]:
|
|
|
45
45
|
shell = os.environ.get("SHELL") or "bash"
|
|
46
46
|
current_path = os.getcwd()
|
|
47
47
|
|
|
48
|
-
#
|
|
49
|
-
system_message = f"""
|
|
48
|
+
# Set system prompt
|
|
49
|
+
system_message = f"""You are a shell command generation assistant.
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
Your only task is to convert user's natural language requirements into corresponding shell commands.
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
1.
|
|
55
|
-
2.
|
|
56
|
-
3.
|
|
57
|
-
4.
|
|
58
|
-
5.
|
|
53
|
+
Strict requirements:
|
|
54
|
+
1. Only return the shell command itself
|
|
55
|
+
2. Do not add any markers (like ```, /**/, // etc.)
|
|
56
|
+
3. Do not add any explanations or descriptions
|
|
57
|
+
4. Do not add any line breaks or extra spaces
|
|
58
|
+
5. If multiple commands are needed, connect them with &&
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
"
|
|
60
|
+
Example input:
|
|
61
|
+
"Find all Python files in the current directory"
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
Example output:
|
|
64
64
|
find . -name "*.py"
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
Remember: Only return the command itself, without any additional content.
|
|
67
67
|
"""
|
|
68
68
|
model.set_system_message(system_message)
|
|
69
69
|
|
|
70
|
-
prefix = f"
|
|
71
|
-
prefix += f"
|
|
70
|
+
prefix = f"Current path: {current_path}\n"
|
|
71
|
+
prefix += f"Current shell: {shell}\n"
|
|
72
72
|
|
|
73
73
|
# 使用yaspin显示Thinking状态
|
|
74
74
|
with yaspin(Spinners.dots, text="Thinking", color="yellow") as spinner:
|
|
75
75
|
# 处理请求
|
|
76
|
-
result = model.
|
|
76
|
+
result = model.chat_until_success(prefix + request)
|
|
77
77
|
|
|
78
78
|
# 提取命令
|
|
79
79
|
if result and isinstance(result, str):
|
|
@@ -85,26 +85,26 @@ find . -name "*.py"
|
|
|
85
85
|
return None
|
|
86
86
|
|
|
87
87
|
except Exception as e:
|
|
88
|
-
PrettyOutput.print(f"
|
|
88
|
+
PrettyOutput.print(f"Failed to process request: {str(e)}", OutputType.ERROR)
|
|
89
89
|
return None
|
|
90
90
|
|
|
91
91
|
def main():
|
|
92
92
|
# 创建参数解析器
|
|
93
93
|
load_env_from_file()
|
|
94
94
|
parser = argparse.ArgumentParser(
|
|
95
|
-
description="
|
|
95
|
+
description="Convert natural language requirements to shell commands",
|
|
96
96
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
97
97
|
epilog="""
|
|
98
|
-
|
|
99
|
-
%(prog)s "
|
|
100
|
-
%(prog)s "
|
|
101
|
-
%(prog)s "
|
|
98
|
+
Example:
|
|
99
|
+
%(prog)s "Find all Python files in the current directory"
|
|
100
|
+
%(prog)s "Compress all jpg images"
|
|
101
|
+
%(prog)s "Find documents modified in the last week"
|
|
102
102
|
""")
|
|
103
103
|
|
|
104
104
|
# 添加参数
|
|
105
105
|
parser.add_argument(
|
|
106
106
|
"request",
|
|
107
|
-
help="
|
|
107
|
+
help="Describe the operation you want to perform in natural language"
|
|
108
108
|
)
|
|
109
109
|
|
|
110
110
|
# 解析参数
|
jarvis/main.py
CHANGED
|
@@ -10,7 +10,7 @@ from prompt_toolkit import prompt
|
|
|
10
10
|
|
|
11
11
|
from jarvis.models.registry import PlatformRegistry
|
|
12
12
|
|
|
13
|
-
#
|
|
13
|
+
# Add parent directory to Python path to support imports
|
|
14
14
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
15
15
|
|
|
16
16
|
from jarvis.agent import Agent
|
|
@@ -22,7 +22,7 @@ def load_tasks() -> dict:
|
|
|
22
22
|
"""Load tasks from .jarvis files in user home and current directory."""
|
|
23
23
|
tasks = {}
|
|
24
24
|
|
|
25
|
-
#
|
|
25
|
+
# Check .jarvis in user directory
|
|
26
26
|
user_jarvis = os.path.expanduser("~/.jarvis")
|
|
27
27
|
if os.path.exists(user_jarvis):
|
|
28
28
|
try:
|
|
@@ -30,39 +30,39 @@ def load_tasks() -> dict:
|
|
|
30
30
|
user_tasks = yaml.safe_load(f)
|
|
31
31
|
|
|
32
32
|
if isinstance(user_tasks, dict):
|
|
33
|
-
#
|
|
33
|
+
# Validate and add user directory tasks
|
|
34
34
|
for name, desc in user_tasks.items():
|
|
35
|
-
if desc: #
|
|
35
|
+
if desc: # Ensure description is not empty
|
|
36
36
|
tasks[str(name)] = str(desc)
|
|
37
37
|
else:
|
|
38
38
|
PrettyOutput.print("Warning: ~/.jarvis file should contain a dictionary of task_name: task_description", OutputType.ERROR)
|
|
39
39
|
except Exception as e:
|
|
40
40
|
PrettyOutput.print(f"Error loading ~/.jarvis file: {str(e)}", OutputType.ERROR)
|
|
41
41
|
|
|
42
|
-
#
|
|
42
|
+
# Check .jarvis in current directory
|
|
43
43
|
if os.path.exists(".jarvis"):
|
|
44
44
|
try:
|
|
45
45
|
with open(".jarvis", "r", encoding="utf-8") as f:
|
|
46
46
|
local_tasks = yaml.safe_load(f)
|
|
47
47
|
|
|
48
48
|
if isinstance(local_tasks, dict):
|
|
49
|
-
#
|
|
49
|
+
# Validate and add current directory tasks, overwrite user directory tasks if there is a name conflict
|
|
50
50
|
for name, desc in local_tasks.items():
|
|
51
|
-
if desc: #
|
|
51
|
+
if desc: # Ensure description is not empty
|
|
52
52
|
tasks[str(name)] = str(desc)
|
|
53
53
|
else:
|
|
54
54
|
PrettyOutput.print("Warning: .jarvis file should contain a dictionary of task_name: task_description", OutputType.ERROR)
|
|
55
55
|
except Exception as e:
|
|
56
56
|
PrettyOutput.print(f"Error loading .jarvis file: {str(e)}", OutputType.ERROR)
|
|
57
57
|
|
|
58
|
-
#
|
|
58
|
+
# Read methodology
|
|
59
59
|
method_path = os.path.expanduser("~/.jarvis_methodology")
|
|
60
60
|
if os.path.exists(method_path):
|
|
61
61
|
with open(method_path, "r", encoding="utf-8") as f:
|
|
62
62
|
methodology = yaml.safe_load(f)
|
|
63
63
|
if isinstance(methodology, dict):
|
|
64
64
|
for name, desc in methodology.items():
|
|
65
|
-
tasks[f"
|
|
65
|
+
tasks[f"Run Methodology: {str(name)}\n {str(desc)}" ] = str(desc)
|
|
66
66
|
|
|
67
67
|
return tasks
|
|
68
68
|
|
|
@@ -77,13 +77,13 @@ def select_task(tasks: dict) -> str:
|
|
|
77
77
|
PrettyOutput.print("\nAvailable tasks:", OutputType.INFO)
|
|
78
78
|
for i, name in enumerate(task_names, 1):
|
|
79
79
|
PrettyOutput.print(f"[{i}] {name}", OutputType.INFO)
|
|
80
|
-
PrettyOutput.print("[0]
|
|
80
|
+
PrettyOutput.print("[0] Skip predefined tasks", OutputType.INFO)
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
while True:
|
|
84
84
|
try:
|
|
85
85
|
choice = prompt(
|
|
86
|
-
"\
|
|
86
|
+
"\nPlease select a task number (0 to skip): ",
|
|
87
87
|
).strip()
|
|
88
88
|
|
|
89
89
|
if not choice:
|
|
@@ -103,16 +103,16 @@ def select_task(tasks: dict) -> str:
|
|
|
103
103
|
except EOFError:
|
|
104
104
|
return "" # Return empty on Ctrl+D
|
|
105
105
|
except Exception as e:
|
|
106
|
-
PrettyOutput.print(f"
|
|
106
|
+
PrettyOutput.print(f"Failed to select task: {str(e)}", OutputType.ERROR)
|
|
107
107
|
continue
|
|
108
108
|
|
|
109
109
|
def main():
|
|
110
|
-
"""Jarvis
|
|
111
|
-
#
|
|
110
|
+
"""Jarvis main entry point"""
|
|
111
|
+
# Add argument parser
|
|
112
112
|
load_env_from_file()
|
|
113
|
-
parser = argparse.ArgumentParser(description='Jarvis AI
|
|
114
|
-
parser.add_argument('-f', '--files', nargs='*', help='
|
|
115
|
-
parser.add_argument('--keep-history', action='store_true', help='
|
|
113
|
+
parser = argparse.ArgumentParser(description='Jarvis AI assistant')
|
|
114
|
+
parser.add_argument('-f', '--files', nargs='*', help='List of files to process')
|
|
115
|
+
parser.add_argument('--keep-history', action='store_true', help='Keep chat history (do not delete session)')
|
|
116
116
|
args = parser.parse_args()
|
|
117
117
|
|
|
118
118
|
try:
|
|
@@ -121,32 +121,32 @@ def main():
|
|
|
121
121
|
|
|
122
122
|
# 如果用户传入了模型参数,则更换当前模型为用户指定的模型
|
|
123
123
|
|
|
124
|
-
#
|
|
125
|
-
PrettyOutput.print(f"Jarvis
|
|
124
|
+
# Welcome information
|
|
125
|
+
PrettyOutput.print(f"Jarvis initialized - With {agent.model.name()}", OutputType.SYSTEM)
|
|
126
126
|
if args.keep_history:
|
|
127
|
-
PrettyOutput.print("
|
|
127
|
+
PrettyOutput.print("History preservation mode enabled", OutputType.INFO)
|
|
128
128
|
|
|
129
129
|
# 加载预定义任务
|
|
130
130
|
tasks = load_tasks()
|
|
131
131
|
if tasks:
|
|
132
132
|
selected_task = select_task(tasks)
|
|
133
133
|
if selected_task:
|
|
134
|
-
PrettyOutput.print(f"\
|
|
134
|
+
PrettyOutput.print(f"\nExecute task: {selected_task}", OutputType.INFO)
|
|
135
135
|
agent.run(selected_task, args.files)
|
|
136
136
|
return 0
|
|
137
137
|
|
|
138
138
|
# 如果没有选择预定义任务,进入交互模式
|
|
139
139
|
while True:
|
|
140
140
|
try:
|
|
141
|
-
user_input = get_multiline_input("
|
|
141
|
+
user_input = get_multiline_input("Please enter your task (input empty line to exit):")
|
|
142
142
|
if not user_input or user_input == "__interrupt__":
|
|
143
143
|
break
|
|
144
144
|
agent.run(user_input, args.files)
|
|
145
145
|
except Exception as e:
|
|
146
|
-
PrettyOutput.print(f"
|
|
146
|
+
PrettyOutput.print(f"Error: {str(e)}", OutputType.ERROR)
|
|
147
147
|
|
|
148
148
|
except Exception as e:
|
|
149
|
-
PrettyOutput.print(f"
|
|
149
|
+
PrettyOutput.print(f"Initialization error: {str(e)}", OutputType.ERROR)
|
|
150
150
|
return 1
|
|
151
151
|
|
|
152
152
|
return 0
|
jarvis/models/ai8.py
CHANGED
|
@@ -26,16 +26,16 @@ class AI8Model(BasePlatform):
|
|
|
26
26
|
|
|
27
27
|
self.token = os.getenv("AI8_API_KEY")
|
|
28
28
|
if not self.token:
|
|
29
|
-
PrettyOutput.print("AI8_API_KEY
|
|
29
|
+
PrettyOutput.print("AI8_API_KEY is not set", OutputType.WARNING)
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
self.model_name = os.getenv("JARVIS_MODEL") or "deepseek-chat"
|
|
33
33
|
if self.model_name not in self.get_available_models():
|
|
34
|
-
PrettyOutput.print(f"
|
|
34
|
+
PrettyOutput.print(f"Warning: The selected model {self.model_name} is not in the available list", OutputType.WARNING)
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def set_model_name(self, model_name: str):
|
|
38
|
-
"""
|
|
38
|
+
"""Set model name"""
|
|
39
39
|
|
|
40
40
|
self.model_name = model_name
|
|
41
41
|
|
|
@@ -59,12 +59,12 @@ class AI8Model(BasePlatform):
|
|
|
59
59
|
)
|
|
60
60
|
|
|
61
61
|
if response.status_code != 200:
|
|
62
|
-
PrettyOutput.print(f"
|
|
62
|
+
PrettyOutput.print(f"Failed to create session: {response.status_code}", OutputType.ERROR)
|
|
63
63
|
return False
|
|
64
64
|
|
|
65
65
|
data = response.json()
|
|
66
66
|
if data['code'] != 0:
|
|
67
|
-
PrettyOutput.print(f"
|
|
67
|
+
PrettyOutput.print(f"Failed to create session: {data.get('msg', 'Unknown error')}", OutputType.ERROR)
|
|
68
68
|
return False
|
|
69
69
|
|
|
70
70
|
self.conversation = data['data']
|
|
@@ -92,14 +92,14 @@ class AI8Model(BasePlatform):
|
|
|
92
92
|
self.conversation = data['data']
|
|
93
93
|
return True
|
|
94
94
|
else:
|
|
95
|
-
PrettyOutput.print(f"
|
|
95
|
+
PrettyOutput.print(f"Failed to update session settings: {data.get('msg', 'Unknown error')}", OutputType.ERROR)
|
|
96
96
|
return False
|
|
97
97
|
else:
|
|
98
|
-
PrettyOutput.print(f"
|
|
98
|
+
PrettyOutput.print(f"Failed to update session settings: {response.status_code}", OutputType.ERROR)
|
|
99
99
|
return False
|
|
100
100
|
|
|
101
101
|
except Exception as e:
|
|
102
|
-
PrettyOutput.print(f"
|
|
102
|
+
PrettyOutput.print(f"Failed to create session: {str(e)}", OutputType.ERROR)
|
|
103
103
|
return False
|
|
104
104
|
|
|
105
105
|
def upload_files(self, file_list: List[str]) -> List[Dict]:
|
|
@@ -119,7 +119,7 @@ class AI8Model(BasePlatform):
|
|
|
119
119
|
self.system_message = message
|
|
120
120
|
|
|
121
121
|
def chat(self, message: str) -> str:
|
|
122
|
-
"""
|
|
122
|
+
"""Execute conversation"""
|
|
123
123
|
try:
|
|
124
124
|
|
|
125
125
|
# 确保有会话ID
|
|
@@ -160,7 +160,7 @@ class AI8Model(BasePlatform):
|
|
|
160
160
|
)
|
|
161
161
|
|
|
162
162
|
if response.status_code != 200:
|
|
163
|
-
error_msg = f"
|
|
163
|
+
error_msg = f"Failed to chat: {response.status_code}"
|
|
164
164
|
PrettyOutput.print(error_msg, OutputType.ERROR)
|
|
165
165
|
raise Exception(error_msg)
|
|
166
166
|
|
|
@@ -188,7 +188,7 @@ class AI8Model(BasePlatform):
|
|
|
188
188
|
return full_response
|
|
189
189
|
|
|
190
190
|
except Exception as e:
|
|
191
|
-
PrettyOutput.print(f"
|
|
191
|
+
PrettyOutput.print(f"Chat exception: {str(e)}", OutputType.ERROR)
|
|
192
192
|
raise e
|
|
193
193
|
|
|
194
194
|
def name(self) -> str:
|
|
@@ -227,23 +227,23 @@ class AI8Model(BasePlatform):
|
|
|
227
227
|
self.reset()
|
|
228
228
|
return True
|
|
229
229
|
else:
|
|
230
|
-
error_msg = f"
|
|
230
|
+
error_msg = f"Failed to delete session: {data.get('msg', 'Unknown error')}"
|
|
231
231
|
PrettyOutput.print(error_msg, OutputType.ERROR)
|
|
232
232
|
return False
|
|
233
233
|
else:
|
|
234
|
-
error_msg = f"
|
|
234
|
+
error_msg = f"Failed to delete session request: {response.status_code}"
|
|
235
235
|
PrettyOutput.print(error_msg, OutputType.ERROR)
|
|
236
236
|
return False
|
|
237
237
|
|
|
238
238
|
except Exception as e:
|
|
239
|
-
PrettyOutput.print(f"
|
|
239
|
+
PrettyOutput.print(f"Failed to delete session: {str(e)}", OutputType.ERROR)
|
|
240
240
|
return False
|
|
241
241
|
|
|
242
242
|
def get_available_models(self) -> List[str]:
|
|
243
|
-
"""
|
|
243
|
+
"""Get available model list
|
|
244
244
|
|
|
245
245
|
Returns:
|
|
246
|
-
List[str]:
|
|
246
|
+
List[str]: Available model name list
|
|
247
247
|
"""
|
|
248
248
|
try:
|
|
249
249
|
if self.models:
|
|
@@ -264,12 +264,12 @@ class AI8Model(BasePlatform):
|
|
|
264
264
|
)
|
|
265
265
|
|
|
266
266
|
if response.status_code != 200:
|
|
267
|
-
PrettyOutput.print(f"
|
|
267
|
+
PrettyOutput.print(f"Failed to get model list: {response.status_code}", OutputType.ERROR)
|
|
268
268
|
return []
|
|
269
269
|
|
|
270
270
|
data = response.json()
|
|
271
271
|
if data['code'] != 0:
|
|
272
|
-
PrettyOutput.print(f"
|
|
272
|
+
PrettyOutput.print(f"Failed to get model list: {data.get('msg', 'Unknown error')}", OutputType.ERROR)
|
|
273
273
|
return []
|
|
274
274
|
|
|
275
275
|
# 保存模型信息
|
|
@@ -292,11 +292,11 @@ class AI8Model(BasePlatform):
|
|
|
292
292
|
# 添加特性标记
|
|
293
293
|
features = []
|
|
294
294
|
if model['attr'].get('multimodal'):
|
|
295
|
-
features.append("
|
|
295
|
+
features.append("Multimodal")
|
|
296
296
|
if model['attr'].get('plugin'):
|
|
297
|
-
features.append("
|
|
297
|
+
features.append("Plugin support")
|
|
298
298
|
if model['attr'].get('onlyImg'):
|
|
299
|
-
features.append("
|
|
299
|
+
features.append("Image support")
|
|
300
300
|
if features:
|
|
301
301
|
model_str += f" [{'|'.join(features)}]"
|
|
302
302
|
|
|
@@ -308,6 +308,6 @@ class AI8Model(BasePlatform):
|
|
|
308
308
|
return list(self.models.keys())
|
|
309
309
|
|
|
310
310
|
except Exception as e:
|
|
311
|
-
PrettyOutput.print(f"
|
|
311
|
+
PrettyOutput.print(f"Failed to get model list: {str(e)}", OutputType.WARNING)
|
|
312
312
|
return []
|
|
313
313
|
|
jarvis/models/base.py
CHANGED
|
@@ -1,59 +1,63 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from typing import Dict, List, Tuple
|
|
3
3
|
|
|
4
|
+
from jarvis.utils import OutputType, PrettyOutput, while_success, while_true
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
class BasePlatform(ABC):
|
|
6
|
-
"""
|
|
8
|
+
"""Base class for large language models"""
|
|
7
9
|
|
|
8
10
|
def __init__(self):
|
|
9
|
-
"""
|
|
11
|
+
"""Initialize model"""
|
|
10
12
|
self.suppress_output = False # 添加输出控制标志
|
|
11
|
-
pass
|
|
12
13
|
|
|
13
14
|
def __del__(self):
|
|
14
|
-
"""
|
|
15
|
+
"""Destroy model"""
|
|
15
16
|
self.delete_chat()
|
|
16
17
|
|
|
17
18
|
@abstractmethod
|
|
18
19
|
def set_model_name(self, model_name: str):
|
|
19
|
-
"""
|
|
20
|
+
"""Set model name"""
|
|
20
21
|
raise NotImplementedError("set_model_name is not implemented")
|
|
21
22
|
|
|
22
23
|
@abstractmethod
|
|
23
24
|
def chat(self, message: str) -> str:
|
|
24
|
-
"""
|
|
25
|
+
"""Execute conversation"""
|
|
25
26
|
raise NotImplementedError("chat is not implemented")
|
|
26
27
|
|
|
28
|
+
def chat_until_success(self, message: str) -> str:
|
|
29
|
+
return while_true(lambda: while_success(lambda: self.chat(message), 5), 5)
|
|
30
|
+
|
|
27
31
|
@abstractmethod
|
|
28
32
|
def upload_files(self, file_list: List[str]) -> List[Dict]:
|
|
29
|
-
"""
|
|
33
|
+
"""Upload files"""
|
|
30
34
|
raise NotImplementedError("upload_files is not implemented")
|
|
31
35
|
|
|
32
36
|
@abstractmethod
|
|
33
37
|
def reset(self):
|
|
34
|
-
"""
|
|
38
|
+
"""Reset model"""
|
|
35
39
|
raise NotImplementedError("reset is not implemented")
|
|
36
40
|
|
|
37
41
|
@abstractmethod
|
|
38
42
|
def name(self) -> str:
|
|
39
|
-
"""
|
|
43
|
+
"""Model name"""
|
|
40
44
|
raise NotImplementedError("name is not implemented")
|
|
41
45
|
|
|
42
46
|
@abstractmethod
|
|
43
47
|
def delete_chat(self)->bool:
|
|
44
|
-
"""
|
|
48
|
+
"""Delete chat"""
|
|
45
49
|
raise NotImplementedError("delete_chat is not implemented")
|
|
46
50
|
|
|
47
51
|
@abstractmethod
|
|
48
52
|
def set_system_message(self, message: str):
|
|
49
|
-
"""
|
|
53
|
+
"""Set system message"""
|
|
50
54
|
raise NotImplementedError("set_system_message is not implemented")
|
|
51
55
|
|
|
52
56
|
@abstractmethod
|
|
53
57
|
def get_model_list(self) -> List[Tuple[str, str]]:
|
|
54
|
-
"""
|
|
58
|
+
"""Get model list"""
|
|
55
59
|
raise NotImplementedError("get_model_list is not implemented")
|
|
56
60
|
|
|
57
61
|
def set_suppress_output(self, suppress: bool):
|
|
58
|
-
"""
|
|
62
|
+
"""Set whether to suppress output"""
|
|
59
63
|
self.suppress_output = suppress
|
jarvis/models/kimi.py
CHANGED
|
@@ -9,38 +9,38 @@ from jarvis.utils import PrettyOutput, OutputType
|
|
|
9
9
|
from jarvis.utils import while_success
|
|
10
10
|
|
|
11
11
|
class KimiModel(BasePlatform):
|
|
12
|
-
"""Kimi
|
|
12
|
+
"""Kimi model implementation"""
|
|
13
13
|
|
|
14
14
|
platform_name = "kimi"
|
|
15
15
|
|
|
16
16
|
def get_model_list(self) -> List[Tuple[str, str]]:
|
|
17
|
-
"""
|
|
18
|
-
return [("kimi", "
|
|
17
|
+
"""Get model list"""
|
|
18
|
+
return [("kimi", "Based on the web Kimi, free interface")]
|
|
19
19
|
|
|
20
20
|
def __init__(self):
|
|
21
21
|
"""
|
|
22
|
-
|
|
22
|
+
Initialize Kimi model
|
|
23
23
|
"""
|
|
24
24
|
super().__init__()
|
|
25
25
|
self.chat_id = ""
|
|
26
26
|
self.api_key = os.getenv("KIMI_API_KEY")
|
|
27
27
|
if not self.api_key:
|
|
28
|
-
PrettyOutput.print("\
|
|
29
|
-
PrettyOutput.print("\n1.
|
|
30
|
-
PrettyOutput.print(" •
|
|
31
|
-
PrettyOutput.print(" •
|
|
32
|
-
PrettyOutput.print(" •
|
|
33
|
-
PrettyOutput.print(" •
|
|
34
|
-
PrettyOutput.print(" •
|
|
35
|
-
PrettyOutput.print(" •
|
|
36
|
-
PrettyOutput.print(" •
|
|
37
|
-
PrettyOutput.print("\n2.
|
|
38
|
-
PrettyOutput.print("
|
|
28
|
+
PrettyOutput.print("\nNeed to set KIMI_API_KEY to use Jarvis. Please follow the steps below:", OutputType.INFO)
|
|
29
|
+
PrettyOutput.print("\n1. Get Kimi API Key:", OutputType.INFO)
|
|
30
|
+
PrettyOutput.print(" • Visit Kimi AI platform: https://kimi.moonshot.cn", OutputType.INFO)
|
|
31
|
+
PrettyOutput.print(" • Login to your account", OutputType.INFO)
|
|
32
|
+
PrettyOutput.print(" • Open browser developer tools (F12 or right-click -> Inspect)", OutputType.INFO)
|
|
33
|
+
PrettyOutput.print(" • Switch to the Network tab", OutputType.INFO)
|
|
34
|
+
PrettyOutput.print(" • Send any message", OutputType.INFO)
|
|
35
|
+
PrettyOutput.print(" • Find the Authorization header in the request", OutputType.INFO)
|
|
36
|
+
PrettyOutput.print(" • Copy the token value (remove the 'Bearer ' prefix)", OutputType.INFO)
|
|
37
|
+
PrettyOutput.print("\n2. Set environment variable:", OutputType.INFO)
|
|
38
|
+
PrettyOutput.print(" • Method 1: Create or edit ~/.jarvis_env file:", OutputType.INFO)
|
|
39
39
|
PrettyOutput.print(" echo 'KIMI_API_KEY=your_key_here' > ~/.jarvis_env", OutputType.INFO)
|
|
40
|
-
PrettyOutput.print("\n
|
|
40
|
+
PrettyOutput.print("\n • Method 2: Set environment variable directly:", OutputType.INFO)
|
|
41
41
|
PrettyOutput.print(" export KIMI_API_KEY=your_key_here", OutputType.INFO)
|
|
42
|
-
PrettyOutput.print("\
|
|
43
|
-
PrettyOutput.print("KIMI_API_KEY
|
|
42
|
+
PrettyOutput.print("\nAfter setting, run Jarvis again.", OutputType.INFO)
|
|
43
|
+
PrettyOutput.print("KIMI_API_KEY is not set", OutputType.WARNING)
|
|
44
44
|
self.auth_header = f"Bearer {self.api_key}"
|
|
45
45
|
self.chat_id = ""
|
|
46
46
|
self.uploaded_files = [] # 存储已上传文件的信息
|
|
@@ -48,18 +48,18 @@ class KimiModel(BasePlatform):
|
|
|
48
48
|
self.system_message = ""
|
|
49
49
|
|
|
50
50
|
def set_system_message(self, message: str):
|
|
51
|
-
"""
|
|
51
|
+
"""Set system message"""
|
|
52
52
|
self.system_message = message
|
|
53
53
|
|
|
54
54
|
def set_model_name(self, model_name: str):
|
|
55
|
-
"""
|
|
55
|
+
"""Set model name"""
|
|
56
56
|
pass
|
|
57
57
|
|
|
58
58
|
def _create_chat(self) -> bool:
|
|
59
|
-
"""
|
|
59
|
+
"""Create a new chat session"""
|
|
60
60
|
url = "https://kimi.moonshot.cn/api/chat"
|
|
61
61
|
payload = json.dumps({
|
|
62
|
-
"name": "
|
|
62
|
+
"name": "Unnamed session",
|
|
63
63
|
"is_example": False,
|
|
64
64
|
"kimiplus_id": "kimi"
|
|
65
65
|
})
|
|
@@ -76,7 +76,7 @@ class KimiModel(BasePlatform):
|
|
|
76
76
|
return False
|
|
77
77
|
|
|
78
78
|
def _get_presigned_url(self, filename: str, action: str) -> Dict:
|
|
79
|
-
"""
|
|
79
|
+
"""Get presigned upload URL"""
|
|
80
80
|
url = "https://kimi.moonshot.cn/api/pre-sign-url"
|
|
81
81
|
|
|
82
82
|
|
|
@@ -95,7 +95,7 @@ class KimiModel(BasePlatform):
|
|
|
95
95
|
return response.json()
|
|
96
96
|
|
|
97
97
|
def _upload_file(self, file_path: str, presigned_url: str) -> bool:
|
|
98
|
-
"""
|
|
98
|
+
"""Upload file to presigned URL"""
|
|
99
99
|
try:
|
|
100
100
|
with open(file_path, 'rb') as f:
|
|
101
101
|
content = f.read()
|
|
@@ -106,7 +106,7 @@ class KimiModel(BasePlatform):
|
|
|
106
106
|
return False
|
|
107
107
|
|
|
108
108
|
def _get_file_info(self, file_data: Dict, name: str, file_type: str) -> Dict:
|
|
109
|
-
"""
|
|
109
|
+
"""Get file information"""
|
|
110
110
|
url = "https://kimi.moonshot.cn/api/file"
|
|
111
111
|
payload = json.dumps({
|
|
112
112
|
"type": file_type,
|
|
@@ -125,7 +125,7 @@ class KimiModel(BasePlatform):
|
|
|
125
125
|
return response.json()
|
|
126
126
|
|
|
127
127
|
def _wait_for_parse(self, file_id: str) -> bool:
|
|
128
|
-
"""
|
|
128
|
+
"""Wait for file parsing to complete"""
|
|
129
129
|
url = "https://kimi.moonshot.cn/api/file/parse_process"
|
|
130
130
|
headers = {
|
|
131
131
|
'Authorization': self.auth_header,
|
|
@@ -163,7 +163,7 @@ class KimiModel(BasePlatform):
|
|
|
163
163
|
|
|
164
164
|
return False
|
|
165
165
|
def upload_files(self, file_list: List[str]) -> List[Dict]:
|
|
166
|
-
"""
|
|
166
|
+
"""Upload file list and return file information"""
|
|
167
167
|
if not file_list:
|
|
168
168
|
return []
|
|
169
169
|
|
|
@@ -209,7 +209,7 @@ class KimiModel(BasePlatform):
|
|
|
209
209
|
return uploaded_files
|
|
210
210
|
|
|
211
211
|
def chat(self, message: str) -> str:
|
|
212
|
-
"""
|
|
212
|
+
"""Send message and get response"""
|
|
213
213
|
if not self.chat_id:
|
|
214
214
|
if not self._create_chat():
|
|
215
215
|
raise Exception("Failed to create chat session")
|
|
@@ -353,7 +353,7 @@ class KimiModel(BasePlatform):
|
|
|
353
353
|
raise Exception(f"Chat failed: {str(e)}")
|
|
354
354
|
|
|
355
355
|
def delete_chat(self) -> bool:
|
|
356
|
-
"""
|
|
356
|
+
"""Delete current session"""
|
|
357
357
|
if not self.chat_id:
|
|
358
358
|
return True # 如果没有会话ID,视为删除成功
|
|
359
359
|
|
|
@@ -376,11 +376,11 @@ class KimiModel(BasePlatform):
|
|
|
376
376
|
return False
|
|
377
377
|
|
|
378
378
|
def reset(self):
|
|
379
|
-
"""
|
|
379
|
+
"""Reset chat"""
|
|
380
380
|
self.chat_id = ""
|
|
381
381
|
self.uploaded_files = []
|
|
382
382
|
self.first_chat = True # 重置first_chat标记
|
|
383
383
|
|
|
384
384
|
def name(self) -> str:
|
|
385
|
-
"""
|
|
385
|
+
"""Model name"""
|
|
386
386
|
return "kimi"
|