massgen 0.0.3__py3-none-any.whl → 0.1.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.
Potentially problematic release.
This version of massgen might be problematic. Click here for more details.
- massgen/__init__.py +142 -8
- massgen/adapters/__init__.py +29 -0
- massgen/adapters/ag2_adapter.py +483 -0
- massgen/adapters/base.py +183 -0
- massgen/adapters/tests/__init__.py +0 -0
- massgen/adapters/tests/test_ag2_adapter.py +439 -0
- massgen/adapters/tests/test_agent_adapter.py +128 -0
- massgen/adapters/utils/__init__.py +2 -0
- massgen/adapters/utils/ag2_utils.py +236 -0
- massgen/adapters/utils/tests/__init__.py +0 -0
- massgen/adapters/utils/tests/test_ag2_utils.py +138 -0
- massgen/agent_config.py +329 -55
- massgen/api_params_handler/__init__.py +10 -0
- massgen/api_params_handler/_api_params_handler_base.py +99 -0
- massgen/api_params_handler/_chat_completions_api_params_handler.py +176 -0
- massgen/api_params_handler/_claude_api_params_handler.py +113 -0
- massgen/api_params_handler/_response_api_params_handler.py +130 -0
- massgen/backend/__init__.py +39 -4
- massgen/backend/azure_openai.py +385 -0
- massgen/backend/base.py +341 -69
- massgen/backend/base_with_mcp.py +1102 -0
- massgen/backend/capabilities.py +386 -0
- massgen/backend/chat_completions.py +577 -130
- massgen/backend/claude.py +1033 -537
- massgen/backend/claude_code.py +1203 -0
- massgen/backend/cli_base.py +209 -0
- massgen/backend/docs/BACKEND_ARCHITECTURE.md +126 -0
- massgen/backend/{CLAUDE_API_RESEARCH.md → docs/CLAUDE_API_RESEARCH.md} +18 -18
- massgen/backend/{GEMINI_API_DOCUMENTATION.md → docs/GEMINI_API_DOCUMENTATION.md} +9 -9
- massgen/backend/docs/Gemini MCP Integration Analysis.md +1050 -0
- massgen/backend/docs/MCP_IMPLEMENTATION_CLAUDE_BACKEND.md +177 -0
- massgen/backend/docs/MCP_INTEGRATION_RESPONSE_BACKEND.md +352 -0
- massgen/backend/docs/OPENAI_GPT5_MODELS.md +211 -0
- massgen/backend/{OPENAI_RESPONSES_API_FORMAT.md → docs/OPENAI_RESPONSE_API_TOOL_CALLS.md} +3 -3
- massgen/backend/docs/OPENAI_response_streaming.md +20654 -0
- massgen/backend/docs/inference_backend.md +257 -0
- massgen/backend/docs/permissions_and_context_files.md +1085 -0
- massgen/backend/external.py +126 -0
- massgen/backend/gemini.py +1850 -241
- massgen/backend/grok.py +40 -156
- massgen/backend/inference.py +156 -0
- massgen/backend/lmstudio.py +171 -0
- massgen/backend/response.py +1095 -322
- massgen/chat_agent.py +131 -113
- massgen/cli.py +1560 -275
- massgen/config_builder.py +2396 -0
- massgen/configs/BACKEND_CONFIGURATION.md +458 -0
- massgen/configs/README.md +559 -216
- massgen/configs/ag2/ag2_case_study.yaml +27 -0
- massgen/configs/ag2/ag2_coder.yaml +34 -0
- massgen/configs/ag2/ag2_coder_case_study.yaml +36 -0
- massgen/configs/ag2/ag2_gemini.yaml +27 -0
- massgen/configs/ag2/ag2_groupchat.yaml +108 -0
- massgen/configs/ag2/ag2_groupchat_gpt.yaml +118 -0
- massgen/configs/ag2/ag2_single_agent.yaml +21 -0
- massgen/configs/basic/multi/fast_timeout_example.yaml +37 -0
- massgen/configs/basic/multi/gemini_4o_claude.yaml +31 -0
- massgen/configs/basic/multi/gemini_gpt5nano_claude.yaml +36 -0
- massgen/configs/{gemini_4o_claude.yaml → basic/multi/geminicode_4o_claude.yaml} +3 -3
- massgen/configs/basic/multi/geminicode_gpt5nano_claude.yaml +36 -0
- massgen/configs/basic/multi/glm_gemini_claude.yaml +25 -0
- massgen/configs/basic/multi/gpt4o_audio_generation.yaml +30 -0
- massgen/configs/basic/multi/gpt4o_image_generation.yaml +31 -0
- massgen/configs/basic/multi/gpt5nano_glm_qwen.yaml +26 -0
- massgen/configs/basic/multi/gpt5nano_image_understanding.yaml +26 -0
- massgen/configs/{three_agents_default.yaml → basic/multi/three_agents_default.yaml} +8 -4
- massgen/configs/basic/multi/three_agents_opensource.yaml +27 -0
- massgen/configs/basic/multi/three_agents_vllm.yaml +20 -0
- massgen/configs/basic/multi/two_agents_gemini.yaml +19 -0
- massgen/configs/{two_agents.yaml → basic/multi/two_agents_gpt5.yaml} +14 -6
- massgen/configs/basic/multi/two_agents_opensource_lmstudio.yaml +31 -0
- massgen/configs/basic/multi/two_qwen_vllm_sglang.yaml +28 -0
- massgen/configs/{single_agent.yaml → basic/single/single_agent.yaml} +1 -1
- massgen/configs/{single_flash2.5.yaml → basic/single/single_flash2.5.yaml} +1 -2
- massgen/configs/basic/single/single_gemini2.5pro.yaml +16 -0
- massgen/configs/basic/single/single_gpt4o_audio_generation.yaml +22 -0
- massgen/configs/basic/single/single_gpt4o_image_generation.yaml +22 -0
- massgen/configs/basic/single/single_gpt4o_video_generation.yaml +24 -0
- massgen/configs/basic/single/single_gpt5nano.yaml +20 -0
- massgen/configs/basic/single/single_gpt5nano_file_search.yaml +18 -0
- massgen/configs/basic/single/single_gpt5nano_image_understanding.yaml +17 -0
- massgen/configs/basic/single/single_gptoss120b.yaml +15 -0
- massgen/configs/basic/single/single_openrouter_audio_understanding.yaml +15 -0
- massgen/configs/basic/single/single_qwen_video_understanding.yaml +15 -0
- massgen/configs/debug/code_execution/command_filtering_blacklist.yaml +29 -0
- massgen/configs/debug/code_execution/command_filtering_whitelist.yaml +28 -0
- massgen/configs/debug/code_execution/docker_verification.yaml +29 -0
- massgen/configs/debug/skip_coordination_test.yaml +27 -0
- massgen/configs/debug/test_sdk_migration.yaml +17 -0
- massgen/configs/docs/DISCORD_MCP_SETUP.md +208 -0
- massgen/configs/docs/TWITTER_MCP_ENESCINAR_SETUP.md +82 -0
- massgen/configs/providers/azure/azure_openai_multi.yaml +21 -0
- massgen/configs/providers/azure/azure_openai_single.yaml +19 -0
- massgen/configs/providers/claude/claude.yaml +14 -0
- massgen/configs/providers/gemini/gemini_gpt5nano.yaml +28 -0
- massgen/configs/providers/local/lmstudio.yaml +11 -0
- massgen/configs/providers/openai/gpt5.yaml +46 -0
- massgen/configs/providers/openai/gpt5_nano.yaml +46 -0
- massgen/configs/providers/others/grok_single_agent.yaml +19 -0
- massgen/configs/providers/others/zai_coding_team.yaml +108 -0
- massgen/configs/providers/others/zai_glm45.yaml +12 -0
- massgen/configs/{creative_team.yaml → teams/creative/creative_team.yaml} +16 -6
- massgen/configs/{travel_planning.yaml → teams/creative/travel_planning.yaml} +16 -6
- massgen/configs/{news_analysis.yaml → teams/research/news_analysis.yaml} +16 -6
- massgen/configs/{research_team.yaml → teams/research/research_team.yaml} +15 -7
- massgen/configs/{technical_analysis.yaml → teams/research/technical_analysis.yaml} +16 -6
- massgen/configs/tools/code-execution/basic_command_execution.yaml +25 -0
- massgen/configs/tools/code-execution/code_execution_use_case_simple.yaml +41 -0
- massgen/configs/tools/code-execution/docker_claude_code.yaml +32 -0
- massgen/configs/tools/code-execution/docker_multi_agent.yaml +32 -0
- massgen/configs/tools/code-execution/docker_simple.yaml +29 -0
- massgen/configs/tools/code-execution/docker_with_resource_limits.yaml +32 -0
- massgen/configs/tools/code-execution/multi_agent_playwright_automation.yaml +57 -0
- massgen/configs/tools/filesystem/cc_gpt5_gemini_filesystem.yaml +34 -0
- massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +68 -0
- massgen/configs/tools/filesystem/claude_code_flash2.5.yaml +43 -0
- massgen/configs/tools/filesystem/claude_code_flash2.5_gptoss.yaml +49 -0
- massgen/configs/tools/filesystem/claude_code_gpt5nano.yaml +31 -0
- massgen/configs/tools/filesystem/claude_code_single.yaml +40 -0
- massgen/configs/tools/filesystem/fs_permissions_test.yaml +87 -0
- massgen/configs/tools/filesystem/gemini_gemini_workspace_cleanup.yaml +54 -0
- massgen/configs/tools/filesystem/gemini_gpt5_filesystem_casestudy.yaml +30 -0
- massgen/configs/tools/filesystem/gemini_gpt5nano_file_context_path.yaml +43 -0
- massgen/configs/tools/filesystem/gemini_gpt5nano_protected_paths.yaml +45 -0
- massgen/configs/tools/filesystem/gpt5mini_cc_fs_context_path.yaml +31 -0
- massgen/configs/tools/filesystem/grok4_gpt5_gemini_filesystem.yaml +32 -0
- massgen/configs/tools/filesystem/multiturn/grok4_gpt5_claude_code_filesystem_multiturn.yaml +58 -0
- massgen/configs/tools/filesystem/multiturn/grok4_gpt5_gemini_filesystem_multiturn.yaml +58 -0
- massgen/configs/tools/filesystem/multiturn/two_claude_code_filesystem_multiturn.yaml +47 -0
- massgen/configs/tools/filesystem/multiturn/two_gemini_flash_filesystem_multiturn.yaml +48 -0
- massgen/configs/tools/mcp/claude_code_discord_mcp_example.yaml +27 -0
- massgen/configs/tools/mcp/claude_code_simple_mcp.yaml +35 -0
- massgen/configs/tools/mcp/claude_code_twitter_mcp_example.yaml +32 -0
- massgen/configs/tools/mcp/claude_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/claude_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/five_agents_travel_mcp_test.yaml +157 -0
- massgen/configs/tools/mcp/five_agents_weather_mcp_test.yaml +103 -0
- massgen/configs/tools/mcp/gemini_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test.yaml +23 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_sharing.yaml +23 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_single_agent.yaml +17 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_with_claude_code.yaml +24 -0
- massgen/configs/tools/mcp/gemini_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/gemini_notion_mcp.yaml +52 -0
- massgen/configs/tools/mcp/gpt5_nano_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/gpt5_nano_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/gpt5mini_claude_code_discord_mcp_example.yaml +38 -0
- massgen/configs/tools/mcp/gpt_oss_mcp_example.yaml +25 -0
- massgen/configs/tools/mcp/gpt_oss_mcp_test.yaml +28 -0
- massgen/configs/tools/mcp/grok3_mini_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/grok3_mini_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/multimcp_gemini.yaml +111 -0
- massgen/configs/tools/mcp/qwen_api_mcp_example.yaml +25 -0
- massgen/configs/tools/mcp/qwen_api_mcp_test.yaml +28 -0
- massgen/configs/tools/mcp/qwen_local_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/qwen_local_mcp_test.yaml +27 -0
- massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +140 -0
- massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +151 -0
- massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +151 -0
- massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +155 -0
- massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +73 -0
- massgen/configs/tools/web-search/claude_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gemini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gpt5_mini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gpt_oss_streamable_http_test.yaml +44 -0
- massgen/configs/tools/web-search/grok3_mini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/qwen_api_streamable_http_test.yaml +44 -0
- massgen/configs/tools/web-search/qwen_local_streamable_http_test.yaml +43 -0
- massgen/coordination_tracker.py +708 -0
- massgen/docker/README.md +462 -0
- massgen/filesystem_manager/__init__.py +21 -0
- massgen/filesystem_manager/_base.py +9 -0
- massgen/filesystem_manager/_code_execution_server.py +545 -0
- massgen/filesystem_manager/_docker_manager.py +477 -0
- massgen/filesystem_manager/_file_operation_tracker.py +248 -0
- massgen/filesystem_manager/_filesystem_manager.py +813 -0
- massgen/filesystem_manager/_path_permission_manager.py +1261 -0
- massgen/filesystem_manager/_workspace_tools_server.py +1815 -0
- massgen/formatter/__init__.py +10 -0
- massgen/formatter/_chat_completions_formatter.py +284 -0
- massgen/formatter/_claude_formatter.py +235 -0
- massgen/formatter/_formatter_base.py +156 -0
- massgen/formatter/_response_formatter.py +263 -0
- massgen/frontend/__init__.py +1 -2
- massgen/frontend/coordination_ui.py +471 -286
- massgen/frontend/displays/base_display.py +56 -11
- massgen/frontend/displays/create_coordination_table.py +1956 -0
- massgen/frontend/displays/rich_terminal_display.py +1259 -619
- massgen/frontend/displays/simple_display.py +9 -4
- massgen/frontend/displays/terminal_display.py +27 -68
- massgen/logger_config.py +681 -0
- massgen/mcp_tools/README.md +232 -0
- massgen/mcp_tools/__init__.py +105 -0
- massgen/mcp_tools/backend_utils.py +1035 -0
- massgen/mcp_tools/circuit_breaker.py +195 -0
- massgen/mcp_tools/client.py +894 -0
- massgen/mcp_tools/config_validator.py +138 -0
- massgen/mcp_tools/docs/circuit_breaker.md +646 -0
- massgen/mcp_tools/docs/client.md +950 -0
- massgen/mcp_tools/docs/config_validator.md +478 -0
- massgen/mcp_tools/docs/exceptions.md +1165 -0
- massgen/mcp_tools/docs/security.md +854 -0
- massgen/mcp_tools/exceptions.py +338 -0
- massgen/mcp_tools/hooks.py +212 -0
- massgen/mcp_tools/security.py +780 -0
- massgen/message_templates.py +342 -64
- massgen/orchestrator.py +1515 -241
- massgen/stream_chunk/__init__.py +35 -0
- massgen/stream_chunk/base.py +92 -0
- massgen/stream_chunk/multimodal.py +237 -0
- massgen/stream_chunk/text.py +162 -0
- massgen/tests/mcp_test_server.py +150 -0
- massgen/tests/multi_turn_conversation_design.md +0 -8
- massgen/tests/test_azure_openai_backend.py +156 -0
- massgen/tests/test_backend_capabilities.py +262 -0
- massgen/tests/test_backend_event_loop_all.py +179 -0
- massgen/tests/test_chat_completions_refactor.py +142 -0
- massgen/tests/test_claude_backend.py +15 -28
- massgen/tests/test_claude_code.py +268 -0
- massgen/tests/test_claude_code_context_sharing.py +233 -0
- massgen/tests/test_claude_code_orchestrator.py +175 -0
- massgen/tests/test_cli_backends.py +180 -0
- massgen/tests/test_code_execution.py +679 -0
- massgen/tests/test_external_agent_backend.py +134 -0
- massgen/tests/test_final_presentation_fallback.py +237 -0
- massgen/tests/test_gemini_planning_mode.py +351 -0
- massgen/tests/test_grok_backend.py +7 -10
- massgen/tests/test_http_mcp_server.py +42 -0
- massgen/tests/test_integration_simple.py +198 -0
- massgen/tests/test_mcp_blocking.py +125 -0
- massgen/tests/test_message_context_building.py +29 -47
- massgen/tests/test_orchestrator_final_presentation.py +48 -0
- massgen/tests/test_path_permission_manager.py +2087 -0
- massgen/tests/test_rich_terminal_display.py +14 -13
- massgen/tests/test_timeout.py +133 -0
- massgen/tests/test_v3_3agents.py +11 -12
- massgen/tests/test_v3_simple.py +8 -13
- massgen/tests/test_v3_three_agents.py +11 -18
- massgen/tests/test_v3_two_agents.py +8 -13
- massgen/token_manager/__init__.py +7 -0
- massgen/token_manager/token_manager.py +400 -0
- massgen/utils.py +52 -16
- massgen/v1/agent.py +45 -91
- massgen/v1/agents.py +18 -53
- massgen/v1/backends/gemini.py +50 -153
- massgen/v1/backends/grok.py +21 -54
- massgen/v1/backends/oai.py +39 -111
- massgen/v1/cli.py +36 -93
- massgen/v1/config.py +8 -12
- massgen/v1/logging.py +43 -127
- massgen/v1/main.py +18 -32
- massgen/v1/orchestrator.py +68 -209
- massgen/v1/streaming_display.py +62 -163
- massgen/v1/tools.py +8 -12
- massgen/v1/types.py +9 -23
- massgen/v1/utils.py +5 -23
- massgen-0.1.0.dist-info/METADATA +1245 -0
- massgen-0.1.0.dist-info/RECORD +273 -0
- massgen-0.1.0.dist-info/entry_points.txt +2 -0
- massgen/frontend/logging/__init__.py +0 -9
- massgen/frontend/logging/realtime_logger.py +0 -197
- massgen-0.0.3.dist-info/METADATA +0 -568
- massgen-0.0.3.dist-info/RECORD +0 -76
- massgen-0.0.3.dist-info/entry_points.txt +0 -2
- /massgen/backend/{Function calling openai responses.md → docs/Function calling openai responses.md} +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/WHEEL +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/top_level.txt +0 -0
massgen/backend/grok.py
CHANGED
|
@@ -1,26 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
3
2
|
"""
|
|
4
|
-
Grok/xAI backend
|
|
5
|
-
|
|
3
|
+
Grok/xAI backend is using the chat_completions backend for streaming.
|
|
4
|
+
It overrides methods for Grok-specific features (Grok Live Search).
|
|
6
5
|
|
|
7
6
|
✅ TESTED: Backend works correctly with architecture
|
|
8
|
-
- ✅ Grok API integration working
|
|
9
|
-
- ✅
|
|
10
|
-
- ✅ Streaming functionality working correctly
|
|
7
|
+
- ✅ Grok API integration working (through chat_completions)
|
|
8
|
+
- ✅ Streaming functionality working correctly
|
|
11
9
|
- ✅ SingleAgent integration working
|
|
12
10
|
- ✅ Error handling and pricing calculations implemented
|
|
11
|
+
- ✅ Web search is working through Grok Live Search
|
|
12
|
+
- ✅ MCP is working
|
|
13
13
|
|
|
14
14
|
TODO for future releases:
|
|
15
15
|
- Test multi-agent orchestrator integration
|
|
16
|
-
- Test web search capabilities with tools
|
|
17
16
|
- Validate advanced Grok-specific features
|
|
18
17
|
"""
|
|
18
|
+
# -*- coding: utf-8 -*-
|
|
19
|
+
from __future__ import annotations
|
|
19
20
|
|
|
20
21
|
import os
|
|
21
|
-
from typing import Dict, List,
|
|
22
|
+
from typing import Any, Dict, List, Optional
|
|
23
|
+
|
|
24
|
+
from openai import AsyncOpenAI
|
|
25
|
+
|
|
26
|
+
from ..logger_config import log_stream_chunk
|
|
22
27
|
from .chat_completions import ChatCompletionsBackend
|
|
23
|
-
from .base import StreamChunk
|
|
24
28
|
|
|
25
29
|
|
|
26
30
|
class GrokBackend(ChatCompletionsBackend):
|
|
@@ -31,72 +35,32 @@ class GrokBackend(ChatCompletionsBackend):
|
|
|
31
35
|
self.api_key = api_key or os.getenv("XAI_API_KEY")
|
|
32
36
|
self.base_url = "https://api.x.ai/v1"
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
api_params = {
|
|
61
|
-
"model": model,
|
|
62
|
-
"messages": grok_messages,
|
|
63
|
-
"tools": converted_tools,
|
|
64
|
-
"max_tokens": max_tokens,
|
|
65
|
-
"temperature": temperature,
|
|
66
|
-
"stream": True,
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
# Add Live Search parameters if enabled (Grok-specific)
|
|
70
|
-
if enable_web_search:
|
|
71
|
-
search_params_kwargs = {"mode": "auto", "return_citations": True}
|
|
72
|
-
|
|
73
|
-
# Allow override of search parameters from backend params
|
|
74
|
-
max_results = kwargs.get("max_search_results")
|
|
75
|
-
if max_results is not None:
|
|
76
|
-
search_params_kwargs["max_search_results"] = max_results
|
|
77
|
-
|
|
78
|
-
search_mode = kwargs.get("search_mode")
|
|
79
|
-
if search_mode is not None:
|
|
80
|
-
search_params_kwargs["mode"] = search_mode
|
|
81
|
-
|
|
82
|
-
return_citations = kwargs.get("return_citations")
|
|
83
|
-
if return_citations is not None:
|
|
84
|
-
search_params_kwargs["return_citations"] = return_citations
|
|
85
|
-
|
|
86
|
-
# Use extra_body to pass search_parameters to xAI API
|
|
87
|
-
api_params["extra_body"] = {"search_parameters": search_params_kwargs}
|
|
88
|
-
|
|
89
|
-
# Create stream
|
|
90
|
-
stream = await client.chat.completions.create(**api_params)
|
|
91
|
-
|
|
92
|
-
# Use base class streaming handler
|
|
93
|
-
async for chunk in self.handle_chat_completions_stream(
|
|
94
|
-
stream, enable_web_search
|
|
95
|
-
):
|
|
96
|
-
yield chunk
|
|
97
|
-
|
|
98
|
-
except Exception as e:
|
|
99
|
-
yield StreamChunk(type="error", error=f"Grok API error: {e}")
|
|
38
|
+
def _create_client(self, **kwargs) -> AsyncOpenAI:
|
|
39
|
+
"""Create OpenAI client configured for xAI's Grok API."""
|
|
40
|
+
import openai
|
|
41
|
+
|
|
42
|
+
return openai.AsyncOpenAI(api_key=self.api_key, base_url=self.base_url)
|
|
43
|
+
|
|
44
|
+
def _build_base_api_params(self, messages: List[Dict[str, Any]], all_params: Dict[str, Any]) -> Dict[str, Any]:
|
|
45
|
+
"""Build base API params for xAI's Grok API."""
|
|
46
|
+
api_params = super()._build_base_api_params(messages, all_params)
|
|
47
|
+
|
|
48
|
+
# Add Live Search parameters if enabled (Grok-specific)
|
|
49
|
+
enable_web_search = all_params.get("enable_web_search", False)
|
|
50
|
+
if enable_web_search:
|
|
51
|
+
# Check for conflict with manually specified search_parameters
|
|
52
|
+
existing_extra = api_params.get("extra_body", {})
|
|
53
|
+
if isinstance(existing_extra, dict) and "search_parameters" in existing_extra:
|
|
54
|
+
error_message = "Conflict: Cannot use both 'enable_web_search: true' and manual 'extra_body.search_parameters'. Use one or the other."
|
|
55
|
+
log_stream_chunk("backend.grok", "error", error_message, self.agent_id)
|
|
56
|
+
raise ValueError(error_message)
|
|
57
|
+
# Merge search_parameters into existing extra_body
|
|
58
|
+
search_params = {"mode": "auto", "return_citations": True}
|
|
59
|
+
merged_extra = existing_extra.copy()
|
|
60
|
+
merged_extra["search_parameters"] = search_params
|
|
61
|
+
api_params["extra_body"] = merged_extra
|
|
62
|
+
|
|
63
|
+
return api_params
|
|
100
64
|
|
|
101
65
|
def get_provider_name(self) -> str:
|
|
102
66
|
"""Get the name of this provider."""
|
|
@@ -105,83 +69,3 @@ class GrokBackend(ChatCompletionsBackend):
|
|
|
105
69
|
def get_supported_builtin_tools(self) -> List[str]:
|
|
106
70
|
"""Get list of builtin tools supported by Grok."""
|
|
107
71
|
return ["web_search"]
|
|
108
|
-
|
|
109
|
-
def estimate_tokens(self, text: str) -> int:
|
|
110
|
-
"""Estimate token count for text (rough approximation)."""
|
|
111
|
-
return int(len(text.split()) * 1.3)
|
|
112
|
-
|
|
113
|
-
def calculate_cost(
|
|
114
|
-
self, input_tokens: int, output_tokens: int, model: str
|
|
115
|
-
) -> float:
|
|
116
|
-
"""Calculate cost for token usage."""
|
|
117
|
-
model_lower = model.lower()
|
|
118
|
-
|
|
119
|
-
# Handle -mini models with lower costs
|
|
120
|
-
if "grok-2" in model_lower:
|
|
121
|
-
if "mini" in model_lower:
|
|
122
|
-
input_cost = (input_tokens / 1_000_000) * 1.0 # Lower cost for mini
|
|
123
|
-
output_cost = (output_tokens / 1_000_000) * 5.0
|
|
124
|
-
else:
|
|
125
|
-
input_cost = (input_tokens / 1_000_000) * 2.0
|
|
126
|
-
output_cost = (output_tokens / 1_000_000) * 10.0
|
|
127
|
-
elif "grok-3" in model_lower:
|
|
128
|
-
if "mini" in model_lower:
|
|
129
|
-
input_cost = (input_tokens / 1_000_000) * 2.5 # Lower cost for mini
|
|
130
|
-
output_cost = (output_tokens / 1_000_000) * 7.5
|
|
131
|
-
else:
|
|
132
|
-
input_cost = (input_tokens / 1_000_000) * 5.0
|
|
133
|
-
output_cost = (output_tokens / 1_000_000) * 15.0
|
|
134
|
-
elif "grok-4" in model_lower:
|
|
135
|
-
if "mini" in model_lower:
|
|
136
|
-
input_cost = (input_tokens / 1_000_000) * 4.0 # Lower cost for mini
|
|
137
|
-
output_cost = (output_tokens / 1_000_000) * 10.0
|
|
138
|
-
else:
|
|
139
|
-
input_cost = (input_tokens / 1_000_000) * 8.0
|
|
140
|
-
output_cost = (output_tokens / 1_000_000) * 20.0
|
|
141
|
-
else:
|
|
142
|
-
# Default fallback (assume grok-3 pricing)
|
|
143
|
-
input_cost = (input_tokens / 1_000_000) * 5.0
|
|
144
|
-
output_cost = (output_tokens / 1_000_000) * 15.0
|
|
145
|
-
|
|
146
|
-
return input_cost + output_cost
|
|
147
|
-
|
|
148
|
-
def _convert_messages_for_grok(
|
|
149
|
-
self, messages: List[Dict[str, Any]]
|
|
150
|
-
) -> List[Dict[str, Any]]:
|
|
151
|
-
"""
|
|
152
|
-
Convert messages for Grok API compatibility.
|
|
153
|
-
|
|
154
|
-
Grok expects tool call arguments as JSON strings in conversation history,
|
|
155
|
-
but returns them as objects in responses.
|
|
156
|
-
"""
|
|
157
|
-
import json
|
|
158
|
-
|
|
159
|
-
converted_messages = []
|
|
160
|
-
|
|
161
|
-
for message in messages:
|
|
162
|
-
# Create a copy to avoid modifying the original
|
|
163
|
-
converted_msg = dict(message)
|
|
164
|
-
|
|
165
|
-
# Convert tool_calls arguments from objects to JSON strings
|
|
166
|
-
if message.get("role") == "assistant" and "tool_calls" in message:
|
|
167
|
-
converted_tool_calls = []
|
|
168
|
-
for tool_call in message["tool_calls"]:
|
|
169
|
-
converted_call = dict(tool_call)
|
|
170
|
-
if "function" in converted_call:
|
|
171
|
-
converted_function = dict(converted_call["function"])
|
|
172
|
-
arguments = converted_function.get("arguments")
|
|
173
|
-
|
|
174
|
-
# Convert arguments to JSON string if it's an object
|
|
175
|
-
if isinstance(arguments, dict):
|
|
176
|
-
converted_function["arguments"] = json.dumps(arguments)
|
|
177
|
-
elif arguments is None:
|
|
178
|
-
converted_function["arguments"] = "{}"
|
|
179
|
-
# If it's already a string, keep it as-is
|
|
180
|
-
|
|
181
|
-
converted_call["function"] = converted_function
|
|
182
|
-
converted_tool_calls.append(converted_call)
|
|
183
|
-
converted_msg["tool_calls"] = converted_tool_calls
|
|
184
|
-
|
|
185
|
-
converted_messages.append(converted_msg)
|
|
186
|
-
|
|
187
|
-
return converted_messages
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Inference backend supporting both vLLM and SGLang servers using OpenAI-compatible Chat Completions API.
|
|
4
|
+
|
|
5
|
+
Defaults are tailored for local inference servers:
|
|
6
|
+
- vLLM: base_url: http://localhost:8000/v1, api_key: "EMPTY"
|
|
7
|
+
- SGLang: base_url: http://localhost:30000/v1, api_key: "EMPTY"
|
|
8
|
+
|
|
9
|
+
This backend delegates most behavior to ChatCompletionsBackend, only
|
|
10
|
+
customizing provider naming, API key resolution, and backend-specific extra_body parameters.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
from typing import Any, AsyncGenerator, Dict, List, Optional, Set
|
|
16
|
+
|
|
17
|
+
from ..api_params_handler._chat_completions_api_params_handler import (
|
|
18
|
+
ChatCompletionsAPIParamsHandler,
|
|
19
|
+
)
|
|
20
|
+
from .base import StreamChunk
|
|
21
|
+
from .chat_completions import ChatCompletionsBackend
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class InferenceAPIParamsHandler(ChatCompletionsAPIParamsHandler):
|
|
25
|
+
"""API params handler for InferenceBackend that excludes backend-specific parameters."""
|
|
26
|
+
|
|
27
|
+
def get_excluded_params(self) -> Set[str]:
|
|
28
|
+
"""Get parameters to exclude from Chat Completions API calls, including backend-specific ones."""
|
|
29
|
+
return (
|
|
30
|
+
super()
|
|
31
|
+
.get_excluded_params()
|
|
32
|
+
.union(
|
|
33
|
+
{
|
|
34
|
+
"chat_template_kwargs",
|
|
35
|
+
"top_k",
|
|
36
|
+
"repetition_penalty",
|
|
37
|
+
"separate_reasoning", # SGLang-specific parameter
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class InferenceBackend(ChatCompletionsBackend):
|
|
44
|
+
"""Backend for local inference servers (vLLM and SGLang).
|
|
45
|
+
|
|
46
|
+
This backend connects to inference servers running with OpenAI-compatible API.
|
|
47
|
+
It supports both vLLM and SGLang specific parameters like guided generation,
|
|
48
|
+
thinking mode, and separate reasoning.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, backend_type: str = "vllm", api_key: Optional[str] = None, **kwargs):
|
|
52
|
+
"""Initialize inference backend.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
backend_type: Type of backend ("vllm" or "sglang")
|
|
56
|
+
api_key: API key (usually "EMPTY" for local servers)
|
|
57
|
+
**kwargs: Additional arguments passed to parent
|
|
58
|
+
"""
|
|
59
|
+
self._backend_type = backend_type.lower()
|
|
60
|
+
|
|
61
|
+
# Set default base URLs based on backend type
|
|
62
|
+
if "base_url" not in kwargs:
|
|
63
|
+
if self._backend_type == "sglang":
|
|
64
|
+
kwargs["base_url"] = "http://localhost:30000/v1"
|
|
65
|
+
else: # vllm
|
|
66
|
+
kwargs["base_url"] = "http://localhost:8000/v1"
|
|
67
|
+
|
|
68
|
+
# Determine API key based on backend type before calling parent
|
|
69
|
+
if api_key is None:
|
|
70
|
+
if self._backend_type == "sglang":
|
|
71
|
+
api_key = os.getenv("SGLANG_API_KEY") or "EMPTY"
|
|
72
|
+
else: # vllm
|
|
73
|
+
api_key = os.getenv("VLLM_API_KEY") or "EMPTY"
|
|
74
|
+
|
|
75
|
+
# Initialize parent with the correct API key
|
|
76
|
+
super().__init__(api_key, **kwargs)
|
|
77
|
+
|
|
78
|
+
# Override the API params handler to exclude backend-specific parameters
|
|
79
|
+
self.api_params_handler = InferenceAPIParamsHandler(self)
|
|
80
|
+
|
|
81
|
+
def get_provider_name(self) -> str:
|
|
82
|
+
"""Get the provider name for this backend."""
|
|
83
|
+
if self._backend_type == "sglang":
|
|
84
|
+
return "SGLang"
|
|
85
|
+
return "vLLM"
|
|
86
|
+
|
|
87
|
+
def _build_extra_body(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
|
|
88
|
+
"""Build backend-specific extra_body parameters and strip them from kwargs.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
kwargs: Keyword arguments that may contain backend parameters
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Dictionary of backend-specific parameters for extra_body
|
|
95
|
+
"""
|
|
96
|
+
extra_body: Dict[str, Any] = {}
|
|
97
|
+
|
|
98
|
+
# Add vLLM and sglang specific parameters from kwargs while preventing them from reaching parent payload
|
|
99
|
+
top_k = kwargs.pop("top_k", None)
|
|
100
|
+
if top_k is not None:
|
|
101
|
+
extra_body["top_k"] = top_k
|
|
102
|
+
|
|
103
|
+
repetition_penalty = kwargs.pop("repetition_penalty", None)
|
|
104
|
+
if repetition_penalty is not None:
|
|
105
|
+
extra_body["repetition_penalty"] = repetition_penalty
|
|
106
|
+
|
|
107
|
+
# Unified chat template handling for both vLLM and SGLang , Some models different way to add it
|
|
108
|
+
chat_template_kwargs = kwargs.pop("chat_template_kwargs", None)
|
|
109
|
+
if chat_template_kwargs is not None:
|
|
110
|
+
extra_body["chat_template_kwargs"] = chat_template_kwargs
|
|
111
|
+
|
|
112
|
+
# SGLang-specific parameters handling for separate reasoning
|
|
113
|
+
if self._backend_type == "sglang":
|
|
114
|
+
separate_reasoning = kwargs.pop("separate_reasoning", None)
|
|
115
|
+
if separate_reasoning is not None:
|
|
116
|
+
extra_body["separate_reasoning"] = separate_reasoning
|
|
117
|
+
|
|
118
|
+
return extra_body
|
|
119
|
+
|
|
120
|
+
async def stream_with_tools(
|
|
121
|
+
self,
|
|
122
|
+
messages: List[Dict[str, Any]],
|
|
123
|
+
tools: List[Dict[str, Any]],
|
|
124
|
+
**kwargs,
|
|
125
|
+
) -> AsyncGenerator[StreamChunk, None]:
|
|
126
|
+
"""Stream response using OpenAI-compatible Chat Completions API with backend-specific parameters.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
messages: List of messages
|
|
130
|
+
tools: List of tool definitions
|
|
131
|
+
**kwargs: Additional parameters including backend-specific ones
|
|
132
|
+
|
|
133
|
+
Yields:
|
|
134
|
+
StreamChunk objects
|
|
135
|
+
"""
|
|
136
|
+
# Build backend-specific extra_body parameters
|
|
137
|
+
extra_body = self._build_extra_body(kwargs)
|
|
138
|
+
|
|
139
|
+
# Add extra_body to kwargs if we have backend-specific parameters
|
|
140
|
+
if extra_body:
|
|
141
|
+
# Add to existing extra_body if present
|
|
142
|
+
if "extra_body" in kwargs:
|
|
143
|
+
kwargs["extra_body"].update(extra_body)
|
|
144
|
+
else:
|
|
145
|
+
kwargs["extra_body"] = extra_body
|
|
146
|
+
|
|
147
|
+
# Delegate to parent with backend-specific parameters in extra_body
|
|
148
|
+
async for chunk in super().stream_with_tools(messages, tools, **kwargs):
|
|
149
|
+
yield chunk
|
|
150
|
+
|
|
151
|
+
def get_supported_builtin_tools(self) -> List[str]:
|
|
152
|
+
"""Return list of supported builtin tools.
|
|
153
|
+
|
|
154
|
+
Local inference servers (vLLM/SGLang) do not provide provider-specific builtin tools.
|
|
155
|
+
"""
|
|
156
|
+
return []
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
LM Studio backend using an OpenAI-compatible Chat Completions API.
|
|
4
|
+
|
|
5
|
+
Defaults are tailored for a local LM Studio server:
|
|
6
|
+
- base_url: http://localhost:1234/v1
|
|
7
|
+
- api_key: "lm-studio" (LM Studio accepts any non-empty key)
|
|
8
|
+
|
|
9
|
+
This backend delegates most behavior to ChatCompletionsBackend, only
|
|
10
|
+
customizing provider naming, API key resolution, and cost calculation.
|
|
11
|
+
"""
|
|
12
|
+
# -*- coding: utf-8 -*-
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import platform
|
|
16
|
+
import shutil
|
|
17
|
+
import subprocess
|
|
18
|
+
import time
|
|
19
|
+
from typing import Any, AsyncGenerator, Dict, List, Optional
|
|
20
|
+
|
|
21
|
+
import lmstudio as lms
|
|
22
|
+
|
|
23
|
+
from .base import StreamChunk
|
|
24
|
+
from .chat_completions import ChatCompletionsBackend
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LMStudioBackend(ChatCompletionsBackend):
|
|
28
|
+
"""LM Studio backend (OpenAI-compatible, local server)."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, api_key: Optional[str] = None, **kwargs):
|
|
31
|
+
super().__init__(api_key="lm-studio", **kwargs) # Override to avoid environment-variable enforcement; LM Studio accepts any key
|
|
32
|
+
self._models_attempted = set() # Track models this instance has attempted to load
|
|
33
|
+
self.start_lmstudio_server(**kwargs)
|
|
34
|
+
|
|
35
|
+
async def stream_with_tools(self, messages: List[Dict[str, Any]], tools: List[Dict[str, Any]], **kwargs) -> AsyncGenerator[StreamChunk, None]:
|
|
36
|
+
"""Stream response using OpenAI-compatible Chat Completions API.
|
|
37
|
+
|
|
38
|
+
LM Studio does not require special message conversions; this delegates to
|
|
39
|
+
the generic ChatCompletions implementation while preserving our defaults.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
# Ensure LM Studio defaults
|
|
43
|
+
base_url = kwargs.get("base_url", "http://localhost:1234/v1")
|
|
44
|
+
kwargs["base_url"] = base_url
|
|
45
|
+
|
|
46
|
+
async for chunk in super().stream_with_tools(messages, tools, **kwargs):
|
|
47
|
+
yield chunk
|
|
48
|
+
|
|
49
|
+
# self.end_lmstudio_server()
|
|
50
|
+
|
|
51
|
+
def get_supported_builtin_tools(self) -> List[str]: # type: ignore[override]
|
|
52
|
+
# LM Studio (local OpenAI-compatible) does not provide provider-builtins
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
def start_lmstudio_server(self, **kwargs):
|
|
56
|
+
"""Start LM Studio server after checking CLI and model availability."""
|
|
57
|
+
self._ensure_cli_installed()
|
|
58
|
+
self._start_server()
|
|
59
|
+
model_name = kwargs.get("model", "")
|
|
60
|
+
if model_name:
|
|
61
|
+
self._handle_model(model_name)
|
|
62
|
+
|
|
63
|
+
def _ensure_cli_installed(self):
|
|
64
|
+
"""Ensure LM Studio CLI is installed."""
|
|
65
|
+
if shutil.which("lms"):
|
|
66
|
+
return
|
|
67
|
+
print("LM Studio CLI not found. Installing...")
|
|
68
|
+
try:
|
|
69
|
+
system = platform.system().lower()
|
|
70
|
+
install_commands = {
|
|
71
|
+
"darwin": (["brew", "install", "lmstudio"], False),
|
|
72
|
+
"linux": (["curl", "-sSL", "https://lmstudio.ai/install.sh", "|", "sh"], True),
|
|
73
|
+
"windows": (["powershell", "-Command", "iwr -useb https://lmstudio.ai/install.ps1 | iex"], False),
|
|
74
|
+
}
|
|
75
|
+
if system not in install_commands:
|
|
76
|
+
raise RuntimeError(f"Unsupported platform: {system}")
|
|
77
|
+
cmd, use_shell = install_commands[system]
|
|
78
|
+
subprocess.run(cmd, shell=use_shell, check=True)
|
|
79
|
+
except subprocess.CalledProcessError as e:
|
|
80
|
+
raise RuntimeError(f"Failed to install LM Studio CLI: {e}") from e
|
|
81
|
+
|
|
82
|
+
def _start_server(self):
|
|
83
|
+
"""Start the LM Studio server in background mode."""
|
|
84
|
+
try:
|
|
85
|
+
with subprocess.Popen(
|
|
86
|
+
["lms", "server", "start"],
|
|
87
|
+
stdout=subprocess.PIPE,
|
|
88
|
+
stderr=subprocess.PIPE,
|
|
89
|
+
text=True,
|
|
90
|
+
) as process:
|
|
91
|
+
time.sleep(3)
|
|
92
|
+
if process.poll() is None:
|
|
93
|
+
print("LM Studio server started successfully (running in background).")
|
|
94
|
+
else:
|
|
95
|
+
self._handle_server_output(process)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
raise RuntimeError(f"Failed to start LM Studio server: {e}") from e
|
|
98
|
+
|
|
99
|
+
def _handle_server_output(self, process):
|
|
100
|
+
"""Handle server process output."""
|
|
101
|
+
stdout, stderr = process.communicate(timeout=1)
|
|
102
|
+
if stdout:
|
|
103
|
+
print(f"Server output: {stdout}")
|
|
104
|
+
if stderr:
|
|
105
|
+
self._process_stderr(stderr)
|
|
106
|
+
print("LM Studio server started successfully.")
|
|
107
|
+
|
|
108
|
+
def _process_stderr(self, stderr):
|
|
109
|
+
"""Process server stderr output."""
|
|
110
|
+
stderr_lower = stderr.lower()
|
|
111
|
+
if "success" in stderr_lower or "running on port" in stderr_lower:
|
|
112
|
+
print(f"Server info: {stderr.strip()}")
|
|
113
|
+
elif "warning" in stderr_lower or "warn" in stderr_lower:
|
|
114
|
+
print(f"Server warning: {stderr.strip()}")
|
|
115
|
+
else:
|
|
116
|
+
print(f"Server error: {stderr.strip()}")
|
|
117
|
+
|
|
118
|
+
def _handle_model(self, model_name):
|
|
119
|
+
"""Handle model downloading and loading."""
|
|
120
|
+
self._ensure_model_downloaded(model_name)
|
|
121
|
+
self._load_model_if_needed(model_name)
|
|
122
|
+
|
|
123
|
+
def _ensure_model_downloaded(self, model_name):
|
|
124
|
+
"""Ensure model is downloaded locally."""
|
|
125
|
+
try:
|
|
126
|
+
downloaded = lms.list_downloaded_models()
|
|
127
|
+
model_keys = [m.model_key for m in downloaded]
|
|
128
|
+
if model_name not in model_keys:
|
|
129
|
+
print(f"Model '{model_name}' not found locally. Downloading...")
|
|
130
|
+
subprocess.run(["lms", "get", model_name], check=True)
|
|
131
|
+
print(f"Model '{model_name}' downloaded successfully.")
|
|
132
|
+
except Exception as e:
|
|
133
|
+
print(f"Warning: Could not check/download model: {e}")
|
|
134
|
+
|
|
135
|
+
def _load_model_if_needed(self, model_name):
|
|
136
|
+
"""Load model if not already loaded."""
|
|
137
|
+
try:
|
|
138
|
+
if model_name in self._models_attempted:
|
|
139
|
+
print(f"Model '{model_name}' load already attempted by this instance.")
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
time.sleep(5)
|
|
143
|
+
loaded = lms.list_loaded_models()
|
|
144
|
+
loaded_identifiers = [m.identifier for m in loaded]
|
|
145
|
+
if model_name not in loaded_identifiers:
|
|
146
|
+
print(f"Model '{model_name}' not loaded. Loading...")
|
|
147
|
+
self._models_attempted.add(model_name)
|
|
148
|
+
subprocess.run(["lms", "load", model_name], check=True)
|
|
149
|
+
print(f"Model '{model_name}' loaded successfully.")
|
|
150
|
+
else:
|
|
151
|
+
print(f"Model '{model_name}' is already loaded.")
|
|
152
|
+
self._models_attempted.add(model_name)
|
|
153
|
+
except subprocess.CalledProcessError as e:
|
|
154
|
+
print(f"Warning: Failed to load model '{model_name}': {e}")
|
|
155
|
+
except Exception as e:
|
|
156
|
+
print(f"Warning: Could not check loaded models: {e}")
|
|
157
|
+
|
|
158
|
+
def end_lmstudio_server(self):
|
|
159
|
+
"""Stop the LM Studio server after receiving all chunks."""
|
|
160
|
+
try:
|
|
161
|
+
# Use lms server end command as specified in requirement
|
|
162
|
+
result = subprocess.run(["lms", "server", "end"], capture_output=True, text=True, check=False)
|
|
163
|
+
|
|
164
|
+
if result.returncode == 0:
|
|
165
|
+
print("LM Studio server ended successfully.")
|
|
166
|
+
else:
|
|
167
|
+
# Fallback to stop command if end doesn't work
|
|
168
|
+
subprocess.run(["lms", "server", "stop"], check=True)
|
|
169
|
+
print("LM Studio server stopped successfully.")
|
|
170
|
+
except Exception as e:
|
|
171
|
+
print(f"Warning: Failed to end LM Studio server: {e}")
|