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.
Files changed (42) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +63 -103
  3. jarvis/jarvis_agent/edit_file_handler.py +43 -47
  4. jarvis/jarvis_agent/jarvis.py +33 -39
  5. jarvis/jarvis_code_agent/code_agent.py +74 -30
  6. jarvis/jarvis_code_agent/lint.py +6 -6
  7. jarvis/jarvis_code_analysis/code_review.py +164 -175
  8. jarvis/jarvis_data/config_schema.json +0 -25
  9. jarvis/jarvis_git_utils/git_commiter.py +148 -153
  10. jarvis/jarvis_methodology/main.py +70 -81
  11. jarvis/jarvis_platform/base.py +21 -17
  12. jarvis/jarvis_platform/kimi.py +59 -64
  13. jarvis/jarvis_platform/tongyi.py +118 -131
  14. jarvis/jarvis_platform/yuanbao.py +117 -122
  15. jarvis/jarvis_platform_manager/main.py +102 -502
  16. jarvis/jarvis_platform_manager/service.py +432 -0
  17. jarvis/jarvis_smart_shell/main.py +99 -33
  18. jarvis/jarvis_tools/ask_user.py +0 -1
  19. jarvis/jarvis_tools/edit_file.py +64 -55
  20. jarvis/jarvis_tools/file_analyzer.py +17 -28
  21. jarvis/jarvis_tools/read_code.py +80 -81
  22. jarvis/jarvis_utils/builtin_replace_map.py +1 -36
  23. jarvis/jarvis_utils/config.py +13 -48
  24. jarvis/jarvis_utils/embedding.py +6 -51
  25. jarvis/jarvis_utils/git_utils.py +93 -43
  26. jarvis/jarvis_utils/http.py +104 -0
  27. jarvis/jarvis_utils/methodology.py +12 -17
  28. jarvis/jarvis_utils/utils.py +186 -63
  29. {jarvis_ai_assistant-0.1.207.dist-info → jarvis_ai_assistant-0.1.209.dist-info}/METADATA +4 -19
  30. {jarvis_ai_assistant-0.1.207.dist-info → jarvis_ai_assistant-0.1.209.dist-info}/RECORD +34 -40
  31. {jarvis_ai_assistant-0.1.207.dist-info → jarvis_ai_assistant-0.1.209.dist-info}/entry_points.txt +1 -1
  32. jarvis/jarvis_data/huggingface.tar.gz +0 -0
  33. jarvis/jarvis_dev/main.py +0 -1247
  34. jarvis/jarvis_tools/chdir.py +0 -72
  35. jarvis/jarvis_tools/code_plan.py +0 -218
  36. jarvis/jarvis_tools/create_code_agent.py +0 -95
  37. jarvis/jarvis_tools/create_sub_agent.py +0 -82
  38. jarvis/jarvis_tools/file_operation.py +0 -238
  39. jarvis/jarvis_utils/jarvis_history.py +0 -98
  40. {jarvis_ai_assistant-0.1.207.dist-info → jarvis_ai_assistant-0.1.209.dist-info}/WHEEL +0 -0
  41. {jarvis_ai_assistant-0.1.207.dist-info → jarvis_ai_assistant-0.1.209.dist-info}/licenses/LICENSE +0 -0
  42. {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
- import asyncio
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 e:
49
+ except Exception as exc:
51
50
  PrettyOutput.print(
52
- f"获取 {platform_name} 的模型列表失败: {str(e)}", OutputType.WARNING
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 - 上传文件, /shell - 执行shell命令, /save - 保存当前对话, /saveall - 保存所有对话",
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
- continue
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 e:
104
- PrettyOutput.print(f"清除会话失败: {str(e)}", OutputType.ERROR)
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 e:
134
- PrettyOutput.print(f"上传文件失败: {str(e)}", OutputType.ERROR)
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 f:
159
+ with open(file_path, "w", encoding="utf-8") as file_obj:
157
160
  last_entry = conversation_history[-1]
158
- f.write(f"{last_entry['content']}\n")
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 e:
165
- PrettyOutput.print(f"保存消息失败: {str(e)}", OutputType.ERROR)
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 f:
189
+ with open(file_path, "w", encoding="utf-8") as file_obj:
187
190
  for entry in conversation_history:
188
- f.write(f"{entry['role']}: {entry['content']}\n\n")
191
+ file_obj.write(f"{entry['role']}: {entry['content']}\n\n")
189
192
 
190
- PrettyOutput.print(
191
- f"所有对话已保存到 {file_path}", OutputType.SUCCESS
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 ex:
217
- PrettyOutput.print(f"执行命令失败: {str(ex)}", OutputType.ERROR)
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 e:
231
- PrettyOutput.print(f"聊天失败: {str(e)}", OutputType.ERROR)
231
+ except Exception as exc:
232
+ PrettyOutput.print(f"聊天失败: {str(exc)}", OutputType.ERROR)
232
233
 
233
- except Exception as e:
234
- PrettyOutput.print(f"初始化会话失败: {str(e)}", OutputType.ERROR)
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
- # Helper function for platform and model validation
244
- def validate_platform_model(args):
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
- class ChatCompletionChoice(BaseModel):
281
- index: int
282
- message: ChatMessage
283
- finish_reason: str = "stop"
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
- class ChatCompletionResponse(BaseModel):
295
- id: str
296
- object: str = "chat.completion"
297
- created: int
298
- model: str
299
- choices: List[ChatCompletionChoice]
300
- usage: Dict[str, int] = Field(
301
- default_factory=lambda: {
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) -> dict:
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 f:
311
+ with open(config_path, "r", encoding="utf-8", errors="ignore") as file_obj:
325
312
  try:
326
- config = yaml.safe_load(f)
313
+ config = yaml.safe_load(file_obj)
327
314
  return config if config else {}
328
- except yaml.YAMLError as e:
329
- PrettyOutput.print(f"角色配置文件解析失败: {str(e)}", OutputType.ERROR)
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
- for i, role in enumerate(config["roles"], 1):
343
- PrettyOutput.print(
344
- f"{i}. {role['name']} - {role.get('description', '')}", OutputType.INFO
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(get_multiline_input("请选择角色(输入编号): "))
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
- "--platform", "-p", help="指定默认平台,当客户端未指定平台时使用"
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.command == "info":
795
- info_command(args)
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