jarvis-ai-assistant 0.1.207__py3-none-any.whl → 0.1.209__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 +63 -103
- jarvis/jarvis_agent/edit_file_handler.py +43 -47
- jarvis/jarvis_agent/jarvis.py +33 -39
- jarvis/jarvis_code_agent/code_agent.py +74 -30
- jarvis/jarvis_code_agent/lint.py +6 -6
- jarvis/jarvis_code_analysis/code_review.py +164 -175
- jarvis/jarvis_data/config_schema.json +0 -25
- jarvis/jarvis_git_utils/git_commiter.py +148 -153
- jarvis/jarvis_methodology/main.py +70 -81
- jarvis/jarvis_platform/base.py +21 -17
- jarvis/jarvis_platform/kimi.py +59 -64
- jarvis/jarvis_platform/tongyi.py +118 -131
- jarvis/jarvis_platform/yuanbao.py +117 -122
- jarvis/jarvis_platform_manager/main.py +102 -502
- jarvis/jarvis_platform_manager/service.py +432 -0
- jarvis/jarvis_smart_shell/main.py +99 -33
- jarvis/jarvis_tools/ask_user.py +0 -1
- jarvis/jarvis_tools/edit_file.py +64 -55
- jarvis/jarvis_tools/file_analyzer.py +17 -28
- jarvis/jarvis_tools/read_code.py +80 -81
- jarvis/jarvis_utils/builtin_replace_map.py +1 -36
- jarvis/jarvis_utils/config.py +13 -48
- jarvis/jarvis_utils/embedding.py +6 -51
- jarvis/jarvis_utils/git_utils.py +93 -43
- jarvis/jarvis_utils/http.py +104 -0
- jarvis/jarvis_utils/methodology.py +12 -17
- jarvis/jarvis_utils/utils.py +186 -63
- {jarvis_ai_assistant-0.1.207.dist-info → jarvis_ai_assistant-0.1.209.dist-info}/METADATA +4 -19
- {jarvis_ai_assistant-0.1.207.dist-info → jarvis_ai_assistant-0.1.209.dist-info}/RECORD +34 -40
- {jarvis_ai_assistant-0.1.207.dist-info → jarvis_ai_assistant-0.1.209.dist-info}/entry_points.txt +1 -1
- jarvis/jarvis_data/huggingface.tar.gz +0 -0
- jarvis/jarvis_dev/main.py +0 -1247
- jarvis/jarvis_tools/chdir.py +0 -72
- jarvis/jarvis_tools/code_plan.py +0 -218
- jarvis/jarvis_tools/create_code_agent.py +0 -95
- jarvis/jarvis_tools/create_sub_agent.py +0 -82
- jarvis/jarvis_tools/file_operation.py +0 -238
- jarvis/jarvis_utils/jarvis_history.py +0 -98
- {jarvis_ai_assistant-0.1.207.dist-info → jarvis_ai_assistant-0.1.209.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.207.dist-info → jarvis_ai_assistant-0.1.209.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.207.dist-info → jarvis_ai_assistant-0.1.209.dist-info}/top_level.txt +0 -0
@@ -1,22 +1,21 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
-
|
2
|
+
"""Jarvis Platform Manager Main Module.
|
3
|
+
|
4
|
+
This module provides the main entry point for the Jarvis platform manager.
|
5
|
+
"""
|
6
|
+
import argparse
|
3
7
|
import os
|
4
8
|
from typing import Any, Dict, List, Optional
|
5
9
|
|
6
|
-
import uvicorn
|
7
|
-
from fastapi import FastAPI, HTTPException
|
8
|
-
from fastapi.middleware.cors import CORSMiddleware
|
9
|
-
from fastapi.responses import StreamingResponse
|
10
|
-
from pydantic import BaseModel, Field
|
11
|
-
|
12
10
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
13
|
-
from jarvis.jarvis_utils.input import get_multiline_input
|
11
|
+
from jarvis.jarvis_utils.input import get_multiline_input, get_single_line_input
|
14
12
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
15
13
|
from jarvis.jarvis_utils.utils import init_env
|
14
|
+
from jarvis.jarvis_platform_manager.service import start_service
|
16
15
|
|
17
16
|
|
18
|
-
def list_platforms():
|
19
|
-
"""List all supported platforms and models"""
|
17
|
+
def list_platforms() -> None:
|
18
|
+
"""List all supported platforms and models."""
|
20
19
|
registry = PlatformRegistry.get_global_platform_registry()
|
21
20
|
platforms = registry.get_available_platforms()
|
22
21
|
|
@@ -47,16 +46,16 @@ def list_platforms():
|
|
47
46
|
else:
|
48
47
|
PrettyOutput.print(" • 没有可用的模型信息", OutputType.WARNING)
|
49
48
|
|
50
|
-
except Exception as
|
49
|
+
except Exception as exc:
|
51
50
|
PrettyOutput.print(
|
52
|
-
f"获取 {platform_name} 的模型列表失败: {str(
|
51
|
+
f"获取 {platform_name} 的模型列表失败: {str(exc)}", OutputType.WARNING
|
53
52
|
)
|
54
53
|
|
55
54
|
|
56
|
-
def chat_with_model(platform_name: str, model_name: str):
|
57
|
-
"""Chat with specified platform and model"""
|
55
|
+
def chat_with_model(platform_name: str, model_name: str, system_prompt: str) -> None:
|
56
|
+
"""Chat with specified platform and model."""
|
58
57
|
registry = PlatformRegistry.get_global_platform_registry()
|
59
|
-
conversation_history = [] # 存储对话记录
|
58
|
+
conversation_history: List[Dict[str, str]] = [] # 存储对话记录
|
60
59
|
|
61
60
|
# Create platform instance
|
62
61
|
platform = registry.create_platform(platform_name)
|
@@ -67,12 +66,15 @@ def chat_with_model(platform_name: str, model_name: str):
|
|
67
66
|
try:
|
68
67
|
# Set model
|
69
68
|
platform.set_model_name(model_name)
|
69
|
+
if system_prompt:
|
70
|
+
platform.set_system_prompt(system_prompt)
|
70
71
|
platform.set_suppress_output(False)
|
71
72
|
PrettyOutput.print(
|
72
73
|
f"连接到 {platform_name} 平台 {model_name} 模型", OutputType.SUCCESS
|
73
74
|
)
|
74
75
|
PrettyOutput.print(
|
75
|
-
"可用命令: /bye - 退出聊天, /clear - 清除会话, /upload - 上传文件,
|
76
|
+
"可用命令: /bye - 退出聊天, /clear - 清除会话, /upload - 上传文件, "
|
77
|
+
"/shell - 执行shell命令, /save - 保存当前对话, /saveall - 保存所有对话",
|
76
78
|
OutputType.INFO,
|
77
79
|
)
|
78
80
|
|
@@ -91,7 +93,8 @@ def chat_with_model(platform_name: str, model_name: str):
|
|
91
93
|
|
92
94
|
# Check if input is empty
|
93
95
|
if not user_input.strip():
|
94
|
-
|
96
|
+
PrettyOutput.print("检测到空输入,退出聊天", OutputType.INFO)
|
97
|
+
break
|
95
98
|
|
96
99
|
# Check if it is a clear session command
|
97
100
|
if user_input.strip() == "/clear":
|
@@ -100,8 +103,8 @@ def chat_with_model(platform_name: str, model_name: str):
|
|
100
103
|
platform.set_model_name(model_name) # Reinitialize session
|
101
104
|
conversation_history = [] # 重置对话记录
|
102
105
|
PrettyOutput.print("会话已清除", OutputType.SUCCESS)
|
103
|
-
except Exception as
|
104
|
-
PrettyOutput.print(f"清除会话失败: {str(
|
106
|
+
except Exception as exc:
|
107
|
+
PrettyOutput.print(f"清除会话失败: {str(exc)}", OutputType.ERROR)
|
105
108
|
continue
|
106
109
|
|
107
110
|
# Check if it is an upload command
|
@@ -130,8 +133,8 @@ def chat_with_model(platform_name: str, model_name: str):
|
|
130
133
|
PrettyOutput.print("文件上传成功", OutputType.SUCCESS)
|
131
134
|
else:
|
132
135
|
PrettyOutput.print("文件上传失败", OutputType.ERROR)
|
133
|
-
except Exception as
|
134
|
-
PrettyOutput.print(f"上传文件失败: {str(
|
136
|
+
except Exception as exc:
|
137
|
+
PrettyOutput.print(f"上传文件失败: {str(exc)}", OutputType.ERROR)
|
135
138
|
continue
|
136
139
|
|
137
140
|
# Check if it is a save command
|
@@ -153,16 +156,16 @@ def chat_with_model(platform_name: str, model_name: str):
|
|
153
156
|
|
154
157
|
# Write last message content to file
|
155
158
|
if conversation_history:
|
156
|
-
with open(file_path, "w", encoding="utf-8") as
|
159
|
+
with open(file_path, "w", encoding="utf-8") as file_obj:
|
157
160
|
last_entry = conversation_history[-1]
|
158
|
-
|
161
|
+
file_obj.write(f"{last_entry['content']}\n")
|
159
162
|
PrettyOutput.print(
|
160
163
|
f"最后一条消息内容已保存到 {file_path}", OutputType.SUCCESS
|
161
164
|
)
|
162
165
|
else:
|
163
166
|
PrettyOutput.print("没有可保存的消息", OutputType.WARNING)
|
164
|
-
except Exception as
|
165
|
-
PrettyOutput.print(f"保存消息失败: {str(
|
167
|
+
except Exception as exc:
|
168
|
+
PrettyOutput.print(f"保存消息失败: {str(exc)}", OutputType.ERROR)
|
166
169
|
continue
|
167
170
|
|
168
171
|
# Check if it is a saveall command
|
@@ -183,15 +186,13 @@ def chat_with_model(platform_name: str, model_name: str):
|
|
183
186
|
file_path = file_path[1:-1]
|
184
187
|
|
185
188
|
# Write full conversation history to file
|
186
|
-
with open(file_path, "w", encoding="utf-8") as
|
189
|
+
with open(file_path, "w", encoding="utf-8") as file_obj:
|
187
190
|
for entry in conversation_history:
|
188
|
-
|
191
|
+
file_obj.write(f"{entry['role']}: {entry['content']}\n\n")
|
189
192
|
|
190
|
-
PrettyOutput.print(
|
191
|
-
|
192
|
-
)
|
193
|
-
except Exception as e:
|
194
|
-
PrettyOutput.print(f"保存所有对话失败: {str(e)}", OutputType.ERROR)
|
193
|
+
PrettyOutput.print(f"所有对话已保存到 {file_path}", OutputType.SUCCESS)
|
194
|
+
except Exception as exc:
|
195
|
+
PrettyOutput.print(f"保存所有对话失败: {str(exc)}", OutputType.ERROR)
|
195
196
|
continue
|
196
197
|
|
197
198
|
# Check if it is a shell command
|
@@ -213,8 +214,8 @@ def chat_with_model(platform_name: str, model_name: str):
|
|
213
214
|
PrettyOutput.print(
|
214
215
|
f"命令执行失败(返回码: {return_code})", OutputType.ERROR
|
215
216
|
)
|
216
|
-
except Exception as
|
217
|
-
PrettyOutput.print(f"执行命令失败: {str(
|
217
|
+
except Exception as exc:
|
218
|
+
PrettyOutput.print(f"执行命令失败: {str(exc)}", OutputType.ERROR)
|
218
219
|
continue
|
219
220
|
|
220
221
|
try:
|
@@ -227,21 +228,28 @@ def chat_with_model(platform_name: str, model_name: str):
|
|
227
228
|
{"role": "assistant", "content": response}
|
228
229
|
) # 记录模型回复
|
229
230
|
|
230
|
-
except Exception as
|
231
|
-
PrettyOutput.print(f"聊天失败: {str(
|
231
|
+
except Exception as exc:
|
232
|
+
PrettyOutput.print(f"聊天失败: {str(exc)}", OutputType.ERROR)
|
232
233
|
|
233
|
-
except Exception as
|
234
|
-
PrettyOutput.print(f"初始化会话失败: {str(
|
234
|
+
except Exception as exc:
|
235
|
+
PrettyOutput.print(f"初始化会话失败: {str(exc)}", OutputType.ERROR)
|
235
236
|
finally:
|
236
237
|
# Clean up resources
|
237
238
|
try:
|
238
239
|
platform.reset()
|
239
|
-
except:
|
240
|
+
except Exception:
|
240
241
|
pass
|
241
242
|
|
242
243
|
|
243
|
-
|
244
|
-
|
244
|
+
def validate_platform_model(args: argparse.Namespace) -> bool:
|
245
|
+
"""Validate platform and model arguments.
|
246
|
+
|
247
|
+
Args:
|
248
|
+
args: Command line arguments.
|
249
|
+
|
250
|
+
Returns:
|
251
|
+
bool: True if platform and model are valid, False otherwise.
|
252
|
+
"""
|
245
253
|
if not args.platform or not args.model:
|
246
254
|
PrettyOutput.print(
|
247
255
|
"请指定平台和模型。使用 'jarvis info' 查看可用平台和模型。",
|
@@ -251,62 +259,41 @@ def validate_platform_model(args):
|
|
251
259
|
return True
|
252
260
|
|
253
261
|
|
254
|
-
def chat_command(args):
|
255
|
-
"""Process chat subcommand
|
262
|
+
def chat_command(args: argparse.Namespace) -> None:
|
263
|
+
"""Process chat subcommand.
|
264
|
+
|
265
|
+
Args:
|
266
|
+
args: Command line arguments.
|
267
|
+
"""
|
256
268
|
if not validate_platform_model(args):
|
257
269
|
return
|
258
|
-
chat_with_model(args.platform, args.model)
|
259
|
-
|
270
|
+
chat_with_model(args.platform, args.model, "")
|
260
271
|
|
261
|
-
def info_command(args):
|
262
|
-
"""Process info subcommand"""
|
263
|
-
list_platforms()
|
264
|
-
|
265
|
-
|
266
|
-
# New models for OpenAI-compatible API
|
267
|
-
class ChatMessage(BaseModel):
|
268
|
-
role: str
|
269
|
-
content: str
|
270
|
-
|
271
|
-
|
272
|
-
class ChatCompletionRequest(BaseModel):
|
273
|
-
model: str
|
274
|
-
messages: List[ChatMessage]
|
275
|
-
stream: bool = False
|
276
|
-
temperature: Optional[float] = None
|
277
|
-
max_tokens: Optional[int] = None
|
278
272
|
|
273
|
+
def info_command(args: argparse.Namespace) -> None:
|
274
|
+
"""Process info subcommand.
|
279
275
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
276
|
+
Args:
|
277
|
+
args: Command line arguments.
|
278
|
+
"""
|
279
|
+
list_platforms()
|
285
280
|
|
286
|
-
class ChatCompletionChunk(BaseModel):
|
287
|
-
id: str
|
288
|
-
object: str = "chat.completion.chunk"
|
289
|
-
created: int
|
290
|
-
model: str
|
291
|
-
choices: List[Dict[str, Any]]
|
292
281
|
|
282
|
+
def service_command(args: argparse.Namespace) -> None:
|
283
|
+
"""Process service subcommand - start OpenAI-compatible API server.
|
293
284
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
"prompt_tokens": 0,
|
303
|
-
"completion_tokens": 0,
|
304
|
-
"total_tokens": 0,
|
305
|
-
}
|
285
|
+
Args:
|
286
|
+
args: Command line arguments.
|
287
|
+
"""
|
288
|
+
start_service(
|
289
|
+
host=args.host,
|
290
|
+
port=args.port,
|
291
|
+
default_platform=args.platform,
|
292
|
+
default_model=args.model,
|
306
293
|
)
|
307
294
|
|
308
295
|
|
309
|
-
def load_role_config(config_path: str) ->
|
296
|
+
def load_role_config(config_path: str) -> Dict[str, Any]:
|
310
297
|
"""从YAML文件加载角色配置
|
311
298
|
|
312
299
|
参数:
|
@@ -321,17 +308,21 @@ def load_role_config(config_path: str) -> dict:
|
|
321
308
|
PrettyOutput.print(f"角色配置文件 {config_path} 不存在", OutputType.ERROR)
|
322
309
|
return {}
|
323
310
|
|
324
|
-
with open(config_path, "r", encoding="utf-8", errors="ignore") as
|
311
|
+
with open(config_path, "r", encoding="utf-8", errors="ignore") as file_obj:
|
325
312
|
try:
|
326
|
-
config = yaml.safe_load(
|
313
|
+
config = yaml.safe_load(file_obj)
|
327
314
|
return config if config else {}
|
328
|
-
except yaml.YAMLError as
|
329
|
-
PrettyOutput.print(f"角色配置文件解析失败: {str(
|
315
|
+
except yaml.YAMLError as exc:
|
316
|
+
PrettyOutput.print(f"角色配置文件解析失败: {str(exc)}", OutputType.ERROR)
|
330
317
|
return {}
|
331
318
|
|
332
319
|
|
333
|
-
def role_command(args):
|
334
|
-
"""Process role subcommand - load role config and start chat
|
320
|
+
def role_command(args: argparse.Namespace) -> None:
|
321
|
+
"""Process role subcommand - load role config and start chat.
|
322
|
+
|
323
|
+
Args:
|
324
|
+
args: Command line arguments.
|
325
|
+
"""
|
335
326
|
config = load_role_config(args.config)
|
336
327
|
if not config or "roles" not in config:
|
337
328
|
PrettyOutput.print("无效的角色配置文件", OutputType.ERROR)
|
@@ -339,14 +330,17 @@ def role_command(args):
|
|
339
330
|
|
340
331
|
# 显示可选角色列表
|
341
332
|
PrettyOutput.section("可用角色", OutputType.SUCCESS)
|
342
|
-
|
343
|
-
|
344
|
-
f"{i}. {role['name']} - {role.get('description', '')}"
|
345
|
-
|
333
|
+
output_str = "\n".join(
|
334
|
+
[
|
335
|
+
f"{i}. {role['name']} - {role.get('description', '')}"
|
336
|
+
for i, role in enumerate(config["roles"], 1)
|
337
|
+
]
|
338
|
+
)
|
339
|
+
PrettyOutput.print(output_str, OutputType.INFO)
|
346
340
|
|
347
341
|
# 让用户选择角色
|
348
342
|
try:
|
349
|
-
choice = int(
|
343
|
+
choice = int(get_single_line_input("请选择角色(输入编号): "))
|
350
344
|
selected_role = config["roles"][choice - 1]
|
351
345
|
except (ValueError, IndexError):
|
352
346
|
PrettyOutput.print("无效的选择", OutputType.ERROR)
|
@@ -357,401 +351,13 @@ def role_command(args):
|
|
357
351
|
model_name = selected_role["model"]
|
358
352
|
system_prompt = selected_role.get("system_prompt", "")
|
359
353
|
|
360
|
-
registry = PlatformRegistry.get_global_platform_registry()
|
361
|
-
platform = registry.create_platform(platform_name)
|
362
|
-
if not platform:
|
363
|
-
PrettyOutput.print(f"创建平台 {platform_name} 失败", OutputType.WARNING)
|
364
|
-
return
|
365
|
-
|
366
|
-
platform.set_model_name(model_name)
|
367
|
-
if system_prompt:
|
368
|
-
platform.set_system_prompt(system_prompt)
|
369
|
-
|
370
354
|
# 开始对话
|
371
355
|
PrettyOutput.print(f"已选择角色: {selected_role['name']}", OutputType.SUCCESS)
|
372
|
-
chat_with_model(platform_name, model_name)
|
373
|
-
|
374
|
-
|
375
|
-
def service_command(args):
|
376
|
-
"""Process service subcommand - start OpenAI-compatible API server"""
|
377
|
-
import os
|
378
|
-
import time
|
379
|
-
import uuid
|
380
|
-
from datetime import datetime
|
381
|
-
|
382
|
-
host = args.host
|
383
|
-
port = args.port
|
384
|
-
default_platform = args.platform
|
385
|
-
default_model = args.model
|
386
|
-
|
387
|
-
# Create logs directory if it doesn't exist
|
388
|
-
logs_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logs")
|
389
|
-
os.makedirs(logs_dir, exist_ok=True)
|
390
|
-
|
391
|
-
app = FastAPI(title="Jarvis API Server")
|
392
|
-
|
393
|
-
# 添加 CORS 中间件
|
394
|
-
app.add_middleware(
|
395
|
-
CORSMiddleware,
|
396
|
-
allow_origins=["*"], # 允许所有来源,生产环境应更严格
|
397
|
-
allow_credentials=True,
|
398
|
-
allow_methods=["*"], # 允许所有方法
|
399
|
-
allow_headers=["*"], # 允许所有头
|
400
|
-
)
|
401
|
-
|
402
|
-
registry = PlatformRegistry.get_global_platform_registry()
|
403
|
-
|
404
|
-
PrettyOutput.print(
|
405
|
-
f"Starting Jarvis API server on {host}:{port}", OutputType.SUCCESS
|
406
|
-
)
|
407
|
-
PrettyOutput.print("This server provides an OpenAI-compatible API", OutputType.INFO)
|
408
|
-
|
409
|
-
if default_platform and default_model:
|
410
|
-
PrettyOutput.print(
|
411
|
-
f"Default platform: {default_platform}, model: {default_model}",
|
412
|
-
OutputType.INFO,
|
413
|
-
)
|
356
|
+
chat_with_model(platform_name, model_name, system_prompt)
|
414
357
|
|
415
|
-
PrettyOutput.print("Available platforms:", OutputType.INFO)
|
416
|
-
|
417
|
-
# Print available platforms and models
|
418
|
-
platforms = registry.get_available_platforms()
|
419
|
-
list_platforms()
|
420
|
-
|
421
|
-
# Platform and model cache
|
422
|
-
platform_instances = {}
|
423
|
-
|
424
|
-
# Chat history storage
|
425
|
-
chat_histories = {}
|
426
|
-
|
427
|
-
def get_platform_instance(platform_name: str, model_name: str):
|
428
|
-
"""Get or create a platform instance"""
|
429
|
-
key = f"{platform_name}:{model_name}"
|
430
|
-
if key not in platform_instances:
|
431
|
-
platform = registry.create_platform(platform_name)
|
432
|
-
if not platform:
|
433
|
-
raise HTTPException(
|
434
|
-
status_code=400, detail=f"Platform {platform_name} not found"
|
435
|
-
)
|
436
|
-
|
437
|
-
platform.set_model_name(model_name)
|
438
|
-
platform_instances[key] = platform
|
439
|
-
|
440
|
-
return platform_instances[key]
|
441
|
-
|
442
|
-
def log_conversation(conversation_id, messages, model, response=None):
|
443
|
-
"""Log conversation to file in plain text format."""
|
444
|
-
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
445
|
-
log_file = os.path.join(
|
446
|
-
logs_dir, f"conversation_{conversation_id}_{timestamp}.txt"
|
447
|
-
)
|
448
|
-
|
449
|
-
with open(log_file, "w", encoding="utf-8", errors="ignore") as f:
|
450
|
-
f.write(f"Conversation ID: {conversation_id}\n")
|
451
|
-
f.write(f"Timestamp: {timestamp}\n")
|
452
|
-
f.write(f"Model: {model}\n\n")
|
453
|
-
f.write("Messages:\n")
|
454
|
-
for message in messages:
|
455
|
-
f.write(f"{message['role']}: {message['content']}\n")
|
456
|
-
if response:
|
457
|
-
f.write(f"\nResponse:\n{response}\n")
|
458
|
-
|
459
|
-
PrettyOutput.print(f"Conversation logged to {log_file}", OutputType.INFO)
|
460
|
-
|
461
|
-
@app.get("/v1/models")
|
462
|
-
async def list_models():
|
463
|
-
"""List available models for the specified platform in OpenAI-compatible format"""
|
464
|
-
model_list = []
|
465
|
-
|
466
|
-
# Only get models for the currently set platform
|
467
|
-
if default_platform:
|
468
|
-
try:
|
469
|
-
platform = registry.create_platform(default_platform)
|
470
|
-
if platform:
|
471
|
-
models = platform.get_model_list()
|
472
|
-
if models:
|
473
|
-
for model_name, _ in models:
|
474
|
-
full_name = f"{default_platform}/{model_name}"
|
475
|
-
model_list.append(
|
476
|
-
{
|
477
|
-
"id": full_name,
|
478
|
-
"object": "model",
|
479
|
-
"created": int(time.time()),
|
480
|
-
"owned_by": default_platform,
|
481
|
-
}
|
482
|
-
)
|
483
|
-
except Exception as e:
|
484
|
-
print(f"Error getting models for {default_platform}: {str(e)}")
|
485
|
-
|
486
|
-
# Return model list
|
487
|
-
return {"object": "list", "data": model_list}
|
488
|
-
|
489
|
-
@app.post("/v1/chat/completions")
|
490
|
-
@app.options("/v1/chat/completions") # 添加 OPTIONS 方法支持
|
491
|
-
async def create_chat_completion(request: ChatCompletionRequest):
|
492
|
-
"""Create a chat completion in OpenAI-compatible format"""
|
493
|
-
model = request.model
|
494
|
-
messages = request.messages
|
495
|
-
stream = request.stream
|
496
|
-
|
497
|
-
# Generate a conversation ID if this is a new conversation
|
498
|
-
conversation_id = str(uuid.uuid4())
|
499
|
-
|
500
|
-
# Extract platform and model name
|
501
|
-
if "/" in model:
|
502
|
-
platform_name, model_name = model.split("/", 1)
|
503
|
-
else:
|
504
|
-
# Use default platform and model if not specified
|
505
|
-
if default_platform and default_model:
|
506
|
-
platform_name, model_name = default_platform, default_model
|
507
|
-
else:
|
508
|
-
platform_name, model_name = "oyi", model # Default to OYI platform
|
509
|
-
|
510
|
-
# Get platform instance
|
511
|
-
platform = get_platform_instance(platform_name, model_name)
|
512
|
-
|
513
|
-
# Convert messages to text format for the platform
|
514
|
-
message_text = ""
|
515
|
-
for msg in messages:
|
516
|
-
role = msg.role
|
517
|
-
content = msg.content
|
518
|
-
|
519
|
-
if role == "system":
|
520
|
-
message_text += f"System: {content}\n\n"
|
521
|
-
elif role == "user":
|
522
|
-
message_text += f"User: {content}\n\n"
|
523
|
-
elif role == "assistant":
|
524
|
-
message_text += f"Assistant: {content}\n\n"
|
525
|
-
|
526
|
-
# Store messages in chat history
|
527
|
-
chat_histories[conversation_id] = {
|
528
|
-
"model": model,
|
529
|
-
"messages": [{"role": m.role, "content": m.content} for m in messages],
|
530
|
-
}
|
531
|
-
|
532
|
-
# Log the conversation
|
533
|
-
log_conversation(
|
534
|
-
conversation_id,
|
535
|
-
[{"role": m.role, "content": m.content} for m in messages],
|
536
|
-
model,
|
537
|
-
)
|
538
|
-
|
539
|
-
if stream:
|
540
|
-
# Return streaming response
|
541
|
-
return StreamingResponse(
|
542
|
-
stream_chat_response(platform, message_text, model),
|
543
|
-
media_type="text/event-stream",
|
544
|
-
)
|
545
|
-
else:
|
546
|
-
# Get chat response
|
547
|
-
try:
|
548
|
-
response_text = platform.chat_until_success(message_text)
|
549
|
-
|
550
|
-
# Create response in OpenAI format
|
551
|
-
completion_id = f"chatcmpl-{str(uuid.uuid4())}"
|
552
|
-
|
553
|
-
# Update chat history with response
|
554
|
-
if conversation_id in chat_histories:
|
555
|
-
chat_histories[conversation_id]["messages"].append(
|
556
|
-
{"role": "assistant", "content": response_text}
|
557
|
-
)
|
558
|
-
|
559
|
-
# Log the conversation with response
|
560
|
-
log_conversation(
|
561
|
-
conversation_id,
|
562
|
-
chat_histories[conversation_id]["messages"],
|
563
|
-
model,
|
564
|
-
response_text,
|
565
|
-
)
|
566
|
-
|
567
|
-
return {
|
568
|
-
"id": completion_id,
|
569
|
-
"object": "chat.completion",
|
570
|
-
"created": int(time.time()),
|
571
|
-
"model": model,
|
572
|
-
"choices": [
|
573
|
-
{
|
574
|
-
"index": 0,
|
575
|
-
"message": {"role": "assistant", "content": response_text},
|
576
|
-
"finish_reason": "stop",
|
577
|
-
}
|
578
|
-
],
|
579
|
-
"usage": {
|
580
|
-
"prompt_tokens": len(message_text) // 4, # Rough estimate
|
581
|
-
"completion_tokens": len(response_text) // 4, # Rough estimate
|
582
|
-
"total_tokens": (len(message_text) + len(response_text))
|
583
|
-
// 4, # Rough estimate
|
584
|
-
},
|
585
|
-
}
|
586
|
-
except Exception as e:
|
587
|
-
raise HTTPException(status_code=500, detail=str(e))
|
588
|
-
|
589
|
-
async def stream_chat_response(platform, message, model_name):
|
590
|
-
"""Stream chat response in OpenAI-compatible format"""
|
591
|
-
import json
|
592
|
-
import os
|
593
|
-
import time
|
594
|
-
import uuid
|
595
|
-
from datetime import datetime
|
596
|
-
|
597
|
-
completion_id = f"chatcmpl-{str(uuid.uuid4())}"
|
598
|
-
created_time = int(time.time())
|
599
|
-
conversation_id = str(uuid.uuid4())
|
600
|
-
|
601
|
-
# Create logs directory if it doesn't exist
|
602
|
-
logs_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logs")
|
603
|
-
os.makedirs(logs_dir, exist_ok=True)
|
604
|
-
|
605
|
-
# 修改第一个yield语句的格式
|
606
|
-
initial_data = {
|
607
|
-
"id": completion_id,
|
608
|
-
"object": "chat.completion.chunk",
|
609
|
-
"created": created_time,
|
610
|
-
"model": model_name,
|
611
|
-
"choices": [
|
612
|
-
{"index": 0, "delta": {"role": "assistant"}, "finish_reason": None}
|
613
|
-
],
|
614
|
-
}
|
615
|
-
res = json.dumps(initial_data)
|
616
|
-
yield f"data: {res}\n\n"
|
617
|
-
|
618
|
-
try:
|
619
|
-
# 直接获取聊天响应,而不是尝试捕获stdout
|
620
|
-
response = platform.chat_until_success(message)
|
621
|
-
|
622
|
-
# 记录完整响应
|
623
|
-
full_response = ""
|
624
|
-
|
625
|
-
# 如果有响应,将其分块发送
|
626
|
-
if response:
|
627
|
-
# 分成小块以获得更好的流式体验
|
628
|
-
chunk_size = 4 # 每个块的字符数
|
629
|
-
for i in range(0, len(response), chunk_size):
|
630
|
-
chunk = response[i : i + chunk_size]
|
631
|
-
full_response += chunk
|
632
|
-
|
633
|
-
# 创建并发送块
|
634
|
-
chunk_data = {
|
635
|
-
"id": completion_id,
|
636
|
-
"object": "chat.completion.chunk",
|
637
|
-
"created": created_time,
|
638
|
-
"model": model_name,
|
639
|
-
"choices": [
|
640
|
-
{
|
641
|
-
"index": 0,
|
642
|
-
"delta": {"content": chunk},
|
643
|
-
"finish_reason": None,
|
644
|
-
}
|
645
|
-
],
|
646
|
-
}
|
647
|
-
|
648
|
-
yield f"data: {json.dumps(chunk_data)}\n\n"
|
649
|
-
|
650
|
-
# 小延迟以模拟流式传输
|
651
|
-
await asyncio.sleep(0.01)
|
652
|
-
else:
|
653
|
-
# 如果没有输出,发送一个空内容块
|
654
|
-
chunk_data = {
|
655
|
-
"id": completion_id,
|
656
|
-
"object": "chat.completion.chunk",
|
657
|
-
"created": created_time,
|
658
|
-
"model": model_name,
|
659
|
-
"choices": [
|
660
|
-
{
|
661
|
-
"index": 0,
|
662
|
-
"delta": {"content": "No response from model."},
|
663
|
-
"finish_reason": None,
|
664
|
-
}
|
665
|
-
],
|
666
|
-
}
|
667
|
-
yield f"data: {json.dumps(chunk_data)}\n\n"
|
668
|
-
full_response = "No response from model."
|
669
|
-
|
670
|
-
# 修改最终yield语句的格式
|
671
|
-
final_data = {
|
672
|
-
"id": completion_id,
|
673
|
-
"object": "chat.completion.chunk",
|
674
|
-
"created": created_time,
|
675
|
-
"model": model_name,
|
676
|
-
"choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}],
|
677
|
-
}
|
678
|
-
yield f"data: {json.dumps(final_data)}\n\n"
|
679
|
-
|
680
|
-
# 发送[DONE]标记
|
681
|
-
yield "data: [DONE]\n\n"
|
682
|
-
|
683
|
-
# 记录对话到文件
|
684
|
-
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
685
|
-
log_file = os.path.join(
|
686
|
-
logs_dir, f"stream_conversation_{conversation_id}_{timestamp}.json"
|
687
|
-
)
|
688
|
-
|
689
|
-
log_data = {
|
690
|
-
"conversation_id": conversation_id,
|
691
|
-
"timestamp": timestamp,
|
692
|
-
"model": model_name,
|
693
|
-
"message": message,
|
694
|
-
"response": full_response,
|
695
|
-
}
|
696
|
-
|
697
|
-
with open(log_file, "w", encoding="utf-8", errors="ignore") as f:
|
698
|
-
json.dump(log_data, f, ensure_ascii=False, indent=2)
|
699
|
-
|
700
|
-
PrettyOutput.print(
|
701
|
-
f"Stream conversation logged to {log_file}", OutputType.INFO
|
702
|
-
)
|
703
|
-
|
704
|
-
except Exception as e:
|
705
|
-
# 发送错误消息
|
706
|
-
error_msg = f"Error: {str(e)}"
|
707
|
-
print(f"Streaming error: {error_msg}")
|
708
|
-
|
709
|
-
res = json.dumps(
|
710
|
-
{
|
711
|
-
"id": completion_id,
|
712
|
-
"object": "chat.completion.chunk",
|
713
|
-
"created": created_time,
|
714
|
-
"model": model_name,
|
715
|
-
"choices": [
|
716
|
-
{
|
717
|
-
"index": 0,
|
718
|
-
"delta": {"content": error_msg},
|
719
|
-
"finish_reason": "stop",
|
720
|
-
}
|
721
|
-
],
|
722
|
-
}
|
723
|
-
)
|
724
|
-
yield f"data: {res}\n\n"
|
725
|
-
yield f"data: {json.dumps({'error': {'message': error_msg, 'type': 'server_error'}})}\n\n"
|
726
|
-
yield "data: [DONE]\n\n"
|
727
|
-
|
728
|
-
# 记录错误到文件
|
729
|
-
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
730
|
-
log_file = os.path.join(
|
731
|
-
logs_dir, f"stream_error_{conversation_id}_{timestamp}.json"
|
732
|
-
)
|
733
|
-
|
734
|
-
log_data = {
|
735
|
-
"conversation_id": conversation_id,
|
736
|
-
"timestamp": timestamp,
|
737
|
-
"model": model_name,
|
738
|
-
"message": message,
|
739
|
-
"error": error_msg,
|
740
|
-
}
|
741
|
-
|
742
|
-
with open(log_file, "w", encoding="utf-8", errors="ignore") as f:
|
743
|
-
json.dump(log_data, f, ensure_ascii=False, indent=2)
|
744
|
-
|
745
|
-
PrettyOutput.print(f"Stream error logged to {log_file}", OutputType.ERROR)
|
746
|
-
|
747
|
-
# Run the server
|
748
|
-
uvicorn.run(app, host=host, port=port)
|
749
|
-
|
750
|
-
|
751
|
-
def main():
|
752
|
-
"""Main function"""
|
753
|
-
import argparse
|
754
358
|
|
359
|
+
def main() -> None:
|
360
|
+
"""Main entry point for Jarvis platform manager."""
|
755
361
|
init_env("欢迎使用 Jarvis-PlatformManager,您的平台管理助手已准备就绪!")
|
756
362
|
|
757
363
|
parser = argparse.ArgumentParser(description="Jarvis AI 平台")
|
@@ -759,11 +365,13 @@ def main():
|
|
759
365
|
|
760
366
|
# info subcommand
|
761
367
|
info_parser = subparsers.add_parser("info", help="显示支持的平台和模型信息")
|
368
|
+
info_parser.set_defaults(func=info_command)
|
762
369
|
|
763
370
|
# chat subcommand
|
764
371
|
chat_parser = subparsers.add_parser("chat", help="与指定平台和模型聊天")
|
765
372
|
chat_parser.add_argument("--platform", "-p", help="指定要使用的平台")
|
766
373
|
chat_parser.add_argument("--model", "-m", help="指定要使用的模型")
|
374
|
+
chat_parser.set_defaults(func=chat_command)
|
767
375
|
|
768
376
|
# service subcommand
|
769
377
|
service_parser = subparsers.add_parser("service", help="启动OpenAI兼容的API服务")
|
@@ -773,12 +381,9 @@ def main():
|
|
773
381
|
service_parser.add_argument(
|
774
382
|
"--port", type=int, default=8000, help="服务端口 (默认: 8000)"
|
775
383
|
)
|
776
|
-
service_parser.add_argument(
|
777
|
-
|
778
|
-
)
|
779
|
-
service_parser.add_argument(
|
780
|
-
"--model", "-m", help="指定默认模型,当客户端未指定平台时使用"
|
781
|
-
)
|
384
|
+
service_parser.add_argument("--platform", "-p", help="指定默认平台,当客户端未指定平台时使用")
|
385
|
+
service_parser.add_argument("--model", "-m", help="指定默认模型,当客户端未指定平台时使用")
|
386
|
+
service_parser.set_defaults(func=service_command)
|
782
387
|
|
783
388
|
# role subcommand
|
784
389
|
role_parser = subparsers.add_parser("role", help="加载角色配置文件并开始对话")
|
@@ -788,17 +393,12 @@ def main():
|
|
788
393
|
default="~/.jarvis/roles.yaml",
|
789
394
|
help="角色配置文件路径(YAML格式,默认: ~/.jarvis/roles.yaml)",
|
790
395
|
)
|
396
|
+
role_parser.set_defaults(func=role_command)
|
791
397
|
|
792
398
|
args = parser.parse_args()
|
793
399
|
|
794
|
-
if args
|
795
|
-
|
796
|
-
elif args.command == "chat":
|
797
|
-
chat_command(args)
|
798
|
-
elif args.command == "service":
|
799
|
-
service_command(args)
|
800
|
-
elif args.command == "role":
|
801
|
-
role_command(args)
|
400
|
+
if hasattr(args, "func"):
|
401
|
+
args.func(args)
|
802
402
|
else:
|
803
403
|
parser.print_help()
|
804
404
|
|