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.

Files changed (268) hide show
  1. massgen/__init__.py +142 -8
  2. massgen/adapters/__init__.py +29 -0
  3. massgen/adapters/ag2_adapter.py +483 -0
  4. massgen/adapters/base.py +183 -0
  5. massgen/adapters/tests/__init__.py +0 -0
  6. massgen/adapters/tests/test_ag2_adapter.py +439 -0
  7. massgen/adapters/tests/test_agent_adapter.py +128 -0
  8. massgen/adapters/utils/__init__.py +2 -0
  9. massgen/adapters/utils/ag2_utils.py +236 -0
  10. massgen/adapters/utils/tests/__init__.py +0 -0
  11. massgen/adapters/utils/tests/test_ag2_utils.py +138 -0
  12. massgen/agent_config.py +329 -55
  13. massgen/api_params_handler/__init__.py +10 -0
  14. massgen/api_params_handler/_api_params_handler_base.py +99 -0
  15. massgen/api_params_handler/_chat_completions_api_params_handler.py +176 -0
  16. massgen/api_params_handler/_claude_api_params_handler.py +113 -0
  17. massgen/api_params_handler/_response_api_params_handler.py +130 -0
  18. massgen/backend/__init__.py +39 -4
  19. massgen/backend/azure_openai.py +385 -0
  20. massgen/backend/base.py +341 -69
  21. massgen/backend/base_with_mcp.py +1102 -0
  22. massgen/backend/capabilities.py +386 -0
  23. massgen/backend/chat_completions.py +577 -130
  24. massgen/backend/claude.py +1033 -537
  25. massgen/backend/claude_code.py +1203 -0
  26. massgen/backend/cli_base.py +209 -0
  27. massgen/backend/docs/BACKEND_ARCHITECTURE.md +126 -0
  28. massgen/backend/{CLAUDE_API_RESEARCH.md → docs/CLAUDE_API_RESEARCH.md} +18 -18
  29. massgen/backend/{GEMINI_API_DOCUMENTATION.md → docs/GEMINI_API_DOCUMENTATION.md} +9 -9
  30. massgen/backend/docs/Gemini MCP Integration Analysis.md +1050 -0
  31. massgen/backend/docs/MCP_IMPLEMENTATION_CLAUDE_BACKEND.md +177 -0
  32. massgen/backend/docs/MCP_INTEGRATION_RESPONSE_BACKEND.md +352 -0
  33. massgen/backend/docs/OPENAI_GPT5_MODELS.md +211 -0
  34. massgen/backend/{OPENAI_RESPONSES_API_FORMAT.md → docs/OPENAI_RESPONSE_API_TOOL_CALLS.md} +3 -3
  35. massgen/backend/docs/OPENAI_response_streaming.md +20654 -0
  36. massgen/backend/docs/inference_backend.md +257 -0
  37. massgen/backend/docs/permissions_and_context_files.md +1085 -0
  38. massgen/backend/external.py +126 -0
  39. massgen/backend/gemini.py +1850 -241
  40. massgen/backend/grok.py +40 -156
  41. massgen/backend/inference.py +156 -0
  42. massgen/backend/lmstudio.py +171 -0
  43. massgen/backend/response.py +1095 -322
  44. massgen/chat_agent.py +131 -113
  45. massgen/cli.py +1560 -275
  46. massgen/config_builder.py +2396 -0
  47. massgen/configs/BACKEND_CONFIGURATION.md +458 -0
  48. massgen/configs/README.md +559 -216
  49. massgen/configs/ag2/ag2_case_study.yaml +27 -0
  50. massgen/configs/ag2/ag2_coder.yaml +34 -0
  51. massgen/configs/ag2/ag2_coder_case_study.yaml +36 -0
  52. massgen/configs/ag2/ag2_gemini.yaml +27 -0
  53. massgen/configs/ag2/ag2_groupchat.yaml +108 -0
  54. massgen/configs/ag2/ag2_groupchat_gpt.yaml +118 -0
  55. massgen/configs/ag2/ag2_single_agent.yaml +21 -0
  56. massgen/configs/basic/multi/fast_timeout_example.yaml +37 -0
  57. massgen/configs/basic/multi/gemini_4o_claude.yaml +31 -0
  58. massgen/configs/basic/multi/gemini_gpt5nano_claude.yaml +36 -0
  59. massgen/configs/{gemini_4o_claude.yaml → basic/multi/geminicode_4o_claude.yaml} +3 -3
  60. massgen/configs/basic/multi/geminicode_gpt5nano_claude.yaml +36 -0
  61. massgen/configs/basic/multi/glm_gemini_claude.yaml +25 -0
  62. massgen/configs/basic/multi/gpt4o_audio_generation.yaml +30 -0
  63. massgen/configs/basic/multi/gpt4o_image_generation.yaml +31 -0
  64. massgen/configs/basic/multi/gpt5nano_glm_qwen.yaml +26 -0
  65. massgen/configs/basic/multi/gpt5nano_image_understanding.yaml +26 -0
  66. massgen/configs/{three_agents_default.yaml → basic/multi/three_agents_default.yaml} +8 -4
  67. massgen/configs/basic/multi/three_agents_opensource.yaml +27 -0
  68. massgen/configs/basic/multi/three_agents_vllm.yaml +20 -0
  69. massgen/configs/basic/multi/two_agents_gemini.yaml +19 -0
  70. massgen/configs/{two_agents.yaml → basic/multi/two_agents_gpt5.yaml} +14 -6
  71. massgen/configs/basic/multi/two_agents_opensource_lmstudio.yaml +31 -0
  72. massgen/configs/basic/multi/two_qwen_vllm_sglang.yaml +28 -0
  73. massgen/configs/{single_agent.yaml → basic/single/single_agent.yaml} +1 -1
  74. massgen/configs/{single_flash2.5.yaml → basic/single/single_flash2.5.yaml} +1 -2
  75. massgen/configs/basic/single/single_gemini2.5pro.yaml +16 -0
  76. massgen/configs/basic/single/single_gpt4o_audio_generation.yaml +22 -0
  77. massgen/configs/basic/single/single_gpt4o_image_generation.yaml +22 -0
  78. massgen/configs/basic/single/single_gpt4o_video_generation.yaml +24 -0
  79. massgen/configs/basic/single/single_gpt5nano.yaml +20 -0
  80. massgen/configs/basic/single/single_gpt5nano_file_search.yaml +18 -0
  81. massgen/configs/basic/single/single_gpt5nano_image_understanding.yaml +17 -0
  82. massgen/configs/basic/single/single_gptoss120b.yaml +15 -0
  83. massgen/configs/basic/single/single_openrouter_audio_understanding.yaml +15 -0
  84. massgen/configs/basic/single/single_qwen_video_understanding.yaml +15 -0
  85. massgen/configs/debug/code_execution/command_filtering_blacklist.yaml +29 -0
  86. massgen/configs/debug/code_execution/command_filtering_whitelist.yaml +28 -0
  87. massgen/configs/debug/code_execution/docker_verification.yaml +29 -0
  88. massgen/configs/debug/skip_coordination_test.yaml +27 -0
  89. massgen/configs/debug/test_sdk_migration.yaml +17 -0
  90. massgen/configs/docs/DISCORD_MCP_SETUP.md +208 -0
  91. massgen/configs/docs/TWITTER_MCP_ENESCINAR_SETUP.md +82 -0
  92. massgen/configs/providers/azure/azure_openai_multi.yaml +21 -0
  93. massgen/configs/providers/azure/azure_openai_single.yaml +19 -0
  94. massgen/configs/providers/claude/claude.yaml +14 -0
  95. massgen/configs/providers/gemini/gemini_gpt5nano.yaml +28 -0
  96. massgen/configs/providers/local/lmstudio.yaml +11 -0
  97. massgen/configs/providers/openai/gpt5.yaml +46 -0
  98. massgen/configs/providers/openai/gpt5_nano.yaml +46 -0
  99. massgen/configs/providers/others/grok_single_agent.yaml +19 -0
  100. massgen/configs/providers/others/zai_coding_team.yaml +108 -0
  101. massgen/configs/providers/others/zai_glm45.yaml +12 -0
  102. massgen/configs/{creative_team.yaml → teams/creative/creative_team.yaml} +16 -6
  103. massgen/configs/{travel_planning.yaml → teams/creative/travel_planning.yaml} +16 -6
  104. massgen/configs/{news_analysis.yaml → teams/research/news_analysis.yaml} +16 -6
  105. massgen/configs/{research_team.yaml → teams/research/research_team.yaml} +15 -7
  106. massgen/configs/{technical_analysis.yaml → teams/research/technical_analysis.yaml} +16 -6
  107. massgen/configs/tools/code-execution/basic_command_execution.yaml +25 -0
  108. massgen/configs/tools/code-execution/code_execution_use_case_simple.yaml +41 -0
  109. massgen/configs/tools/code-execution/docker_claude_code.yaml +32 -0
  110. massgen/configs/tools/code-execution/docker_multi_agent.yaml +32 -0
  111. massgen/configs/tools/code-execution/docker_simple.yaml +29 -0
  112. massgen/configs/tools/code-execution/docker_with_resource_limits.yaml +32 -0
  113. massgen/configs/tools/code-execution/multi_agent_playwright_automation.yaml +57 -0
  114. massgen/configs/tools/filesystem/cc_gpt5_gemini_filesystem.yaml +34 -0
  115. massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +68 -0
  116. massgen/configs/tools/filesystem/claude_code_flash2.5.yaml +43 -0
  117. massgen/configs/tools/filesystem/claude_code_flash2.5_gptoss.yaml +49 -0
  118. massgen/configs/tools/filesystem/claude_code_gpt5nano.yaml +31 -0
  119. massgen/configs/tools/filesystem/claude_code_single.yaml +40 -0
  120. massgen/configs/tools/filesystem/fs_permissions_test.yaml +87 -0
  121. massgen/configs/tools/filesystem/gemini_gemini_workspace_cleanup.yaml +54 -0
  122. massgen/configs/tools/filesystem/gemini_gpt5_filesystem_casestudy.yaml +30 -0
  123. massgen/configs/tools/filesystem/gemini_gpt5nano_file_context_path.yaml +43 -0
  124. massgen/configs/tools/filesystem/gemini_gpt5nano_protected_paths.yaml +45 -0
  125. massgen/configs/tools/filesystem/gpt5mini_cc_fs_context_path.yaml +31 -0
  126. massgen/configs/tools/filesystem/grok4_gpt5_gemini_filesystem.yaml +32 -0
  127. massgen/configs/tools/filesystem/multiturn/grok4_gpt5_claude_code_filesystem_multiturn.yaml +58 -0
  128. massgen/configs/tools/filesystem/multiturn/grok4_gpt5_gemini_filesystem_multiturn.yaml +58 -0
  129. massgen/configs/tools/filesystem/multiturn/two_claude_code_filesystem_multiturn.yaml +47 -0
  130. massgen/configs/tools/filesystem/multiturn/two_gemini_flash_filesystem_multiturn.yaml +48 -0
  131. massgen/configs/tools/mcp/claude_code_discord_mcp_example.yaml +27 -0
  132. massgen/configs/tools/mcp/claude_code_simple_mcp.yaml +35 -0
  133. massgen/configs/tools/mcp/claude_code_twitter_mcp_example.yaml +32 -0
  134. massgen/configs/tools/mcp/claude_mcp_example.yaml +24 -0
  135. massgen/configs/tools/mcp/claude_mcp_test.yaml +27 -0
  136. massgen/configs/tools/mcp/five_agents_travel_mcp_test.yaml +157 -0
  137. massgen/configs/tools/mcp/five_agents_weather_mcp_test.yaml +103 -0
  138. massgen/configs/tools/mcp/gemini_mcp_example.yaml +24 -0
  139. massgen/configs/tools/mcp/gemini_mcp_filesystem_test.yaml +23 -0
  140. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_sharing.yaml +23 -0
  141. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_single_agent.yaml +17 -0
  142. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_with_claude_code.yaml +24 -0
  143. massgen/configs/tools/mcp/gemini_mcp_test.yaml +27 -0
  144. massgen/configs/tools/mcp/gemini_notion_mcp.yaml +52 -0
  145. massgen/configs/tools/mcp/gpt5_nano_mcp_example.yaml +24 -0
  146. massgen/configs/tools/mcp/gpt5_nano_mcp_test.yaml +27 -0
  147. massgen/configs/tools/mcp/gpt5mini_claude_code_discord_mcp_example.yaml +38 -0
  148. massgen/configs/tools/mcp/gpt_oss_mcp_example.yaml +25 -0
  149. massgen/configs/tools/mcp/gpt_oss_mcp_test.yaml +28 -0
  150. massgen/configs/tools/mcp/grok3_mini_mcp_example.yaml +24 -0
  151. massgen/configs/tools/mcp/grok3_mini_mcp_test.yaml +27 -0
  152. massgen/configs/tools/mcp/multimcp_gemini.yaml +111 -0
  153. massgen/configs/tools/mcp/qwen_api_mcp_example.yaml +25 -0
  154. massgen/configs/tools/mcp/qwen_api_mcp_test.yaml +28 -0
  155. massgen/configs/tools/mcp/qwen_local_mcp_example.yaml +24 -0
  156. massgen/configs/tools/mcp/qwen_local_mcp_test.yaml +27 -0
  157. massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +140 -0
  158. massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +151 -0
  159. massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +151 -0
  160. massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +155 -0
  161. massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +73 -0
  162. massgen/configs/tools/web-search/claude_streamable_http_test.yaml +43 -0
  163. massgen/configs/tools/web-search/gemini_streamable_http_test.yaml +43 -0
  164. massgen/configs/tools/web-search/gpt5_mini_streamable_http_test.yaml +43 -0
  165. massgen/configs/tools/web-search/gpt_oss_streamable_http_test.yaml +44 -0
  166. massgen/configs/tools/web-search/grok3_mini_streamable_http_test.yaml +43 -0
  167. massgen/configs/tools/web-search/qwen_api_streamable_http_test.yaml +44 -0
  168. massgen/configs/tools/web-search/qwen_local_streamable_http_test.yaml +43 -0
  169. massgen/coordination_tracker.py +708 -0
  170. massgen/docker/README.md +462 -0
  171. massgen/filesystem_manager/__init__.py +21 -0
  172. massgen/filesystem_manager/_base.py +9 -0
  173. massgen/filesystem_manager/_code_execution_server.py +545 -0
  174. massgen/filesystem_manager/_docker_manager.py +477 -0
  175. massgen/filesystem_manager/_file_operation_tracker.py +248 -0
  176. massgen/filesystem_manager/_filesystem_manager.py +813 -0
  177. massgen/filesystem_manager/_path_permission_manager.py +1261 -0
  178. massgen/filesystem_manager/_workspace_tools_server.py +1815 -0
  179. massgen/formatter/__init__.py +10 -0
  180. massgen/formatter/_chat_completions_formatter.py +284 -0
  181. massgen/formatter/_claude_formatter.py +235 -0
  182. massgen/formatter/_formatter_base.py +156 -0
  183. massgen/formatter/_response_formatter.py +263 -0
  184. massgen/frontend/__init__.py +1 -2
  185. massgen/frontend/coordination_ui.py +471 -286
  186. massgen/frontend/displays/base_display.py +56 -11
  187. massgen/frontend/displays/create_coordination_table.py +1956 -0
  188. massgen/frontend/displays/rich_terminal_display.py +1259 -619
  189. massgen/frontend/displays/simple_display.py +9 -4
  190. massgen/frontend/displays/terminal_display.py +27 -68
  191. massgen/logger_config.py +681 -0
  192. massgen/mcp_tools/README.md +232 -0
  193. massgen/mcp_tools/__init__.py +105 -0
  194. massgen/mcp_tools/backend_utils.py +1035 -0
  195. massgen/mcp_tools/circuit_breaker.py +195 -0
  196. massgen/mcp_tools/client.py +894 -0
  197. massgen/mcp_tools/config_validator.py +138 -0
  198. massgen/mcp_tools/docs/circuit_breaker.md +646 -0
  199. massgen/mcp_tools/docs/client.md +950 -0
  200. massgen/mcp_tools/docs/config_validator.md +478 -0
  201. massgen/mcp_tools/docs/exceptions.md +1165 -0
  202. massgen/mcp_tools/docs/security.md +854 -0
  203. massgen/mcp_tools/exceptions.py +338 -0
  204. massgen/mcp_tools/hooks.py +212 -0
  205. massgen/mcp_tools/security.py +780 -0
  206. massgen/message_templates.py +342 -64
  207. massgen/orchestrator.py +1515 -241
  208. massgen/stream_chunk/__init__.py +35 -0
  209. massgen/stream_chunk/base.py +92 -0
  210. massgen/stream_chunk/multimodal.py +237 -0
  211. massgen/stream_chunk/text.py +162 -0
  212. massgen/tests/mcp_test_server.py +150 -0
  213. massgen/tests/multi_turn_conversation_design.md +0 -8
  214. massgen/tests/test_azure_openai_backend.py +156 -0
  215. massgen/tests/test_backend_capabilities.py +262 -0
  216. massgen/tests/test_backend_event_loop_all.py +179 -0
  217. massgen/tests/test_chat_completions_refactor.py +142 -0
  218. massgen/tests/test_claude_backend.py +15 -28
  219. massgen/tests/test_claude_code.py +268 -0
  220. massgen/tests/test_claude_code_context_sharing.py +233 -0
  221. massgen/tests/test_claude_code_orchestrator.py +175 -0
  222. massgen/tests/test_cli_backends.py +180 -0
  223. massgen/tests/test_code_execution.py +679 -0
  224. massgen/tests/test_external_agent_backend.py +134 -0
  225. massgen/tests/test_final_presentation_fallback.py +237 -0
  226. massgen/tests/test_gemini_planning_mode.py +351 -0
  227. massgen/tests/test_grok_backend.py +7 -10
  228. massgen/tests/test_http_mcp_server.py +42 -0
  229. massgen/tests/test_integration_simple.py +198 -0
  230. massgen/tests/test_mcp_blocking.py +125 -0
  231. massgen/tests/test_message_context_building.py +29 -47
  232. massgen/tests/test_orchestrator_final_presentation.py +48 -0
  233. massgen/tests/test_path_permission_manager.py +2087 -0
  234. massgen/tests/test_rich_terminal_display.py +14 -13
  235. massgen/tests/test_timeout.py +133 -0
  236. massgen/tests/test_v3_3agents.py +11 -12
  237. massgen/tests/test_v3_simple.py +8 -13
  238. massgen/tests/test_v3_three_agents.py +11 -18
  239. massgen/tests/test_v3_two_agents.py +8 -13
  240. massgen/token_manager/__init__.py +7 -0
  241. massgen/token_manager/token_manager.py +400 -0
  242. massgen/utils.py +52 -16
  243. massgen/v1/agent.py +45 -91
  244. massgen/v1/agents.py +18 -53
  245. massgen/v1/backends/gemini.py +50 -153
  246. massgen/v1/backends/grok.py +21 -54
  247. massgen/v1/backends/oai.py +39 -111
  248. massgen/v1/cli.py +36 -93
  249. massgen/v1/config.py +8 -12
  250. massgen/v1/logging.py +43 -127
  251. massgen/v1/main.py +18 -32
  252. massgen/v1/orchestrator.py +68 -209
  253. massgen/v1/streaming_display.py +62 -163
  254. massgen/v1/tools.py +8 -12
  255. massgen/v1/types.py +9 -23
  256. massgen/v1/utils.py +5 -23
  257. massgen-0.1.0.dist-info/METADATA +1245 -0
  258. massgen-0.1.0.dist-info/RECORD +273 -0
  259. massgen-0.1.0.dist-info/entry_points.txt +2 -0
  260. massgen/frontend/logging/__init__.py +0 -9
  261. massgen/frontend/logging/realtime_logger.py +0 -197
  262. massgen-0.0.3.dist-info/METADATA +0 -568
  263. massgen-0.0.3.dist-info/RECORD +0 -76
  264. massgen-0.0.3.dist-info/entry_points.txt +0 -2
  265. /massgen/backend/{Function calling openai responses.md → docs/Function calling openai responses.md} +0 -0
  266. {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/WHEEL +0 -0
  267. {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/licenses/LICENSE +0 -0
  268. {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
- from __future__ import annotations
2
-
1
+ # -*- coding: utf-8 -*-
3
2
  """
4
- Grok/xAI backend implementation using OpenAI-compatible API.
5
- Clean implementation with only Grok-specific features.
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
- - ✅ Tool message conversion compatible with Chat Completions format
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, Any, AsyncGenerator, Optional
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
- async def stream_with_tools(
35
- self, messages: List[Dict[str, Any]], tools: List[Dict[str, Any]], **kwargs
36
- ) -> AsyncGenerator[StreamChunk, None]:
37
- """Stream response using xAI's OpenAI-compatible API."""
38
-
39
- # Convert messages for Grok API compatibility
40
- grok_messages = self._convert_messages_for_grok(messages)
41
-
42
- try:
43
- import openai
44
-
45
- # Use OpenAI client with xAI base URL
46
- client = openai.AsyncOpenAI(api_key=self.api_key, base_url=self.base_url)
47
-
48
- # Extract parameters
49
- model = kwargs.get("model", "grok-3-mini")
50
- max_tokens = kwargs.get("max_tokens", None)
51
- temperature = kwargs.get("temperature", None)
52
- enable_web_search = kwargs.get("enable_web_search", False)
53
-
54
- # Convert tools to Chat Completions format
55
- converted_tools = (
56
- self.convert_tools_to_chat_completions_format(tools) if tools else None
57
- )
58
-
59
- # Chat Completions API parameters
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}")