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
@@ -0,0 +1,385 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Azure OpenAI backend implementation.
4
+ Uses the official Azure OpenAI client for proper Azure integration.
5
+ """
6
+ # -*- coding: utf-8 -*-
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from typing import Any, AsyncGenerator, Dict, List, Optional
11
+
12
+ from ..logger_config import (
13
+ log_backend_activity,
14
+ log_backend_agent_message,
15
+ log_stream_chunk,
16
+ )
17
+ from .base import LLMBackend, StreamChunk
18
+
19
+
20
+ class AzureOpenAIBackend(LLMBackend):
21
+ """Azure OpenAI backend using the official Azure OpenAI client.
22
+
23
+ Supports Azure OpenAI deployments with proper Azure authentication and configuration.
24
+
25
+ Environment Variables:
26
+ AZURE_OPENAI_API_KEY: Azure OpenAI API key
27
+ AZURE_OPENAI_ENDPOINT: Azure OpenAI endpoint URL
28
+ AZURE_OPENAI_API_VERSION: Azure OpenAI API version (optional, defaults to 2024-12-01-preview)
29
+ """
30
+
31
+ def __init__(self, api_key: Optional[str] = None, **kwargs):
32
+ super().__init__(api_key, **kwargs)
33
+
34
+ # Get Azure configuration from parameters or environment variables
35
+ self.api_key = api_key or os.getenv("AZURE_OPENAI_API_KEY")
36
+
37
+ if not self.api_key:
38
+ raise ValueError("Azure OpenAI API key is required. Set AZURE_OPENAI_API_KEY environment variable or pass api_key parameter.")
39
+
40
+ def get_provider_name(self) -> str:
41
+ """Get the name of this provider."""
42
+ return "Azure OpenAI"
43
+
44
+ async def stream_with_tools(self, messages: List[Dict[str, Any]], tools: List[Dict[str, Any]], **kwargs) -> AsyncGenerator[StreamChunk, None]:
45
+ """
46
+ Stream a response with tool calling support using Azure OpenAI.
47
+
48
+ Args:
49
+ messages: Conversation messages
50
+ tools: Available tools schema
51
+ **kwargs: Additional parameters including model (deployment name)
52
+ """
53
+ # Extract agent_id for logging
54
+ agent_id = kwargs.get("agent_id", None)
55
+
56
+ log_backend_activity(
57
+ self.get_provider_name(),
58
+ "Starting stream_with_tools",
59
+ {"num_messages": len(messages), "num_tools": len(tools) if tools else 0},
60
+ agent_id=agent_id,
61
+ )
62
+
63
+ try:
64
+ # Merge constructor config with stream kwargs (stream kwargs take priority)
65
+ all_params = {**self.config, **kwargs}
66
+
67
+ # Import Azure OpenAI client
68
+ from openai import AsyncAzureOpenAI
69
+
70
+ azure_endpoint = all_params.get("azure_endpoint") or all_params.get("base_url") or os.getenv("AZURE_OPENAI_ENDPOINT")
71
+ api_version = all_params.get("api_version") or os.getenv("AZURE_OPENAI_API_VERSION", "2024-12-01-preview")
72
+
73
+ # Validate required configuration
74
+ if not azure_endpoint:
75
+ raise ValueError("Azure OpenAI endpoint URL is required. Set AZURE_OPENAI_ENDPOINT environment variable or pass azure_endpoint/base_url parameter.")
76
+
77
+ if not api_version:
78
+ raise ValueError("Azure OpenAI API version is required. Set AZURE_OPENAI_API_VERSION environment variable or pass api_version parameter.")
79
+
80
+ # Clean up endpoint URL
81
+ if azure_endpoint.endswith("/"):
82
+ azure_endpoint = azure_endpoint[:-1]
83
+
84
+ # Initialize Azure OpenAI client
85
+ self.client = AsyncAzureOpenAI(
86
+ api_version=api_version,
87
+ azure_endpoint=azure_endpoint,
88
+ api_key=self.api_key,
89
+ )
90
+
91
+ # Get deployment name from model parameter
92
+ deployment_name = all_params.get("model")
93
+ if not deployment_name:
94
+ raise ValueError("Azure OpenAI requires a deployment name. Pass it as the 'model' parameter.")
95
+
96
+ # Check if workflow tools are present
97
+ workflow_tools = [t for t in tools if t.get("function", {}).get("name") in ["new_answer", "vote"]] if tools else []
98
+ has_workflow_tools = len(workflow_tools) > 0
99
+
100
+ # Modify messages to include workflow tool instructions if needed
101
+ modified_messages = self._prepare_messages_with_workflow_tools(messages, workflow_tools) if has_workflow_tools else messages
102
+
103
+ # Log messages being sent
104
+ log_backend_agent_message(
105
+ agent_id or "default",
106
+ "SEND",
107
+ {"messages": modified_messages, "tools": len(tools) if tools else 0},
108
+ backend_name=self.get_provider_name(),
109
+ )
110
+
111
+ # Prepare API parameters
112
+ api_params = {
113
+ "messages": modified_messages,
114
+ "model": deployment_name, # Use deployment name directly
115
+ "stream": True,
116
+ }
117
+
118
+ # Only add tools if explicitly provided and not empty
119
+ if tools and len(tools) > 0:
120
+ # Convert tools to Azure OpenAI format if needed
121
+ converted_tools = self._convert_tools_format(tools)
122
+ api_params["tools"] = converted_tools
123
+ else:
124
+ # Disable tool calling for simple queries
125
+ api_params["tool_choice"] = "none"
126
+
127
+ # Add other parameters (excluding model since we already set it)
128
+ # Filter out unsupported Azure OpenAI parameters
129
+ excluded_params = self.get_base_excluded_config_params() | {
130
+ # Azure OpenAI specific exclusions
131
+ "model",
132
+ "messages",
133
+ "stream",
134
+ "tools",
135
+ }
136
+ for key, value in kwargs.items():
137
+ if key not in excluded_params and value is not None:
138
+ api_params[key] = value
139
+
140
+ # Create streaming response (now properly async)
141
+ stream = await self.client.chat.completions.create(**api_params)
142
+
143
+ # Process streaming response with content accumulation
144
+ accumulated_content = ""
145
+ complete_response = "" # Keep track of the complete response
146
+ last_yield_type = None
147
+
148
+ async for chunk in stream:
149
+ converted = self._convert_chunk_to_stream_chunk(chunk)
150
+
151
+ # Accumulate content chunks
152
+ if converted.type == "content" and converted.content:
153
+ accumulated_content += converted.content
154
+ complete_response += converted.content # Add to complete response
155
+ # Only yield content when we have meaningful chunks (words, not single characters)
156
+ if len(accumulated_content) >= 10 or " " in accumulated_content:
157
+ log_backend_agent_message(
158
+ agent_id or "default",
159
+ "RECV",
160
+ {"content": accumulated_content},
161
+ backend_name=self.get_provider_name(),
162
+ )
163
+ log_stream_chunk(
164
+ "backend.azure_openai",
165
+ "content",
166
+ accumulated_content,
167
+ agent_id,
168
+ )
169
+ yield StreamChunk(type="content", content=accumulated_content)
170
+ accumulated_content = ""
171
+ elif converted.type != "content":
172
+ # Log non-content chunks
173
+ if converted.type == "error":
174
+ log_stream_chunk("backend.azure_openai", "error", converted.error, agent_id)
175
+ elif converted.type == "done":
176
+ log_stream_chunk("backend.azure_openai", "done", None, agent_id)
177
+ # Yield non-content chunks immediately
178
+ last_yield_type = converted.type
179
+ yield converted
180
+
181
+ # Yield any remaining accumulated content
182
+ if accumulated_content:
183
+ log_backend_agent_message(
184
+ agent_id or "default",
185
+ "RECV",
186
+ {"content": accumulated_content},
187
+ backend_name=self.get_provider_name(),
188
+ )
189
+ log_stream_chunk("backend.azure_openai", "content", accumulated_content, agent_id)
190
+ yield StreamChunk(type="content", content=accumulated_content)
191
+
192
+ # After streaming is complete, check if we have workflow tool calls
193
+ if has_workflow_tools:
194
+ workflow_tool_calls = self._extract_workflow_tool_calls(complete_response)
195
+ if workflow_tool_calls:
196
+ log_stream_chunk(
197
+ "backend.azure_openai",
198
+ "tool_calls",
199
+ workflow_tool_calls,
200
+ agent_id,
201
+ )
202
+ yield StreamChunk(type="tool_calls", tool_calls=workflow_tool_calls)
203
+ last_yield_type = "tool_calls"
204
+
205
+ # Ensure stream termination is signaled
206
+ if last_yield_type != "done":
207
+ log_stream_chunk("backend.azure_openai", "done", None, agent_id)
208
+ yield StreamChunk(type="done")
209
+
210
+ except Exception as e:
211
+ error_msg = f"Azure OpenAI API error: {str(e)}"
212
+ log_stream_chunk("backend.azure_openai", "error", error_msg, agent_id)
213
+ yield StreamChunk(type="error", error=error_msg)
214
+
215
+ def _prepare_messages_with_workflow_tools(self, messages: List[Dict[str, Any]], workflow_tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
216
+ """Prepare messages with workflow tool instructions."""
217
+ if not workflow_tools:
218
+ return messages
219
+
220
+ # Find the system message
221
+ system_message = None
222
+ for msg in messages:
223
+ if msg.get("role") == "system":
224
+ system_message = msg
225
+ break
226
+
227
+ # Create enhanced system message with workflow tool instructions
228
+ enhanced_system = self._build_workflow_tools_system_prompt(system_message.get("content", "") if system_message else "", workflow_tools)
229
+
230
+ # Create new messages list with enhanced system message
231
+ new_messages = []
232
+ for msg in messages:
233
+ if msg.get("role") == "system":
234
+ new_messages.append({"role": "system", "content": enhanced_system})
235
+ else:
236
+ new_messages.append(msg)
237
+
238
+ return new_messages
239
+
240
+ def _build_workflow_tools_system_prompt(self, base_system: str, workflow_tools: List[Dict[str, Any]]) -> str:
241
+ """Build system prompt with workflow tool instructions."""
242
+ system_parts = []
243
+
244
+ if base_system:
245
+ system_parts.append(base_system)
246
+
247
+ # Add workflow tools information
248
+ if workflow_tools:
249
+ system_parts.append("\n--- Available Tools ---")
250
+ for tool in workflow_tools:
251
+ name = tool.get("function", {}).get("name", "unknown")
252
+ description = tool.get("function", {}).get("description", "No description")
253
+ system_parts.append(f"- {name}: {description}")
254
+
255
+ # Add usage examples for workflow tools
256
+ if name == "new_answer":
257
+ system_parts.append(' Usage: {"tool_name": "new_answer", ' '"arguments": {"content": "your answer"}}')
258
+ elif name == "vote":
259
+ # Extract valid agent IDs from enum if available
260
+ agent_id_enum = None
261
+ for t in workflow_tools:
262
+ if t.get("function", {}).get("name") == "vote":
263
+ agent_id_param = t.get("function", {}).get("parameters", {}).get("properties", {}).get("agent_id", {})
264
+ if "enum" in agent_id_param:
265
+ agent_id_enum = agent_id_param["enum"]
266
+ break
267
+
268
+ if agent_id_enum:
269
+ agent_list = ", ".join(agent_id_enum)
270
+ system_parts.append(f' Usage: {{"tool_name": "vote", ' f'"arguments": {{"agent_id": "agent1", ' f'"reason": "explanation"}}}} // Choose agent_id from: {agent_list}')
271
+ else:
272
+ system_parts.append(' Usage: {"tool_name": "vote", ' '"arguments": {"agent_id": "agent1", ' '"reason": "explanation"}}')
273
+
274
+ system_parts.append("\n--- MassGen Workflow Instructions ---")
275
+ system_parts.append("IMPORTANT: You must respond with a structured JSON decision at the end of your response.")
276
+ system_parts.append("You must use the coordination tools (new_answer, vote) " "to participate in multi-agent workflows.")
277
+ system_parts.append("The JSON MUST be formatted as a strict JSON code block:")
278
+ system_parts.append("1. Start with ```json on one line")
279
+ system_parts.append("2. Include your JSON content (properly formatted)")
280
+ system_parts.append("3. End with ``` on one line")
281
+ system_parts.append('Example format:\n```json\n{"tool_name": "vote", "arguments": {"agent_id": "agent1", "reason": "explanation"}}\n```')
282
+ system_parts.append("The JSON block should be placed at the very end of your response, after your analysis.")
283
+
284
+ return "\n".join(system_parts)
285
+
286
+ def _extract_workflow_tool_calls(self, content: str) -> List[Dict[str, Any]]:
287
+ """Extract workflow tool calls from content."""
288
+ try:
289
+ import json
290
+ import re
291
+
292
+ # Look for JSON inside markdown code blocks first
293
+ markdown_json_pattern = r"```json\s*(\{.*?\})\s*```"
294
+ markdown_matches = re.findall(markdown_json_pattern, content, re.DOTALL)
295
+
296
+ for match in reversed(markdown_matches):
297
+ try:
298
+ parsed = json.loads(match.strip())
299
+ if isinstance(parsed, dict) and "tool_name" in parsed:
300
+ # Convert to MassGen tool call format
301
+ tool_call = {
302
+ "id": f"call_{hash(match) % 10000}", # Generate a unique ID
303
+ "type": "function",
304
+ "function": {
305
+ "name": parsed["tool_name"],
306
+ "arguments": json.dumps(parsed["arguments"]),
307
+ },
308
+ }
309
+ return [tool_call]
310
+ except json.JSONDecodeError:
311
+ continue
312
+
313
+ # Also look for JSON without markdown blocks
314
+ json_pattern = r'\{[^{}]*"tool_name"[^{}]*\}'
315
+ json_matches = re.findall(json_pattern, content, re.DOTALL)
316
+
317
+ for match in json_matches:
318
+ try:
319
+ parsed = json.loads(match.strip())
320
+ if isinstance(parsed, dict) and "tool_name" in parsed:
321
+ # Convert to MassGen tool call format
322
+ tool_call = {
323
+ "id": f"call_{hash(match) % 10000}", # Generate a unique ID
324
+ "type": "function",
325
+ "function": {
326
+ "name": parsed["tool_name"],
327
+ "arguments": json.dumps(parsed["arguments"]),
328
+ },
329
+ }
330
+ return [tool_call]
331
+ except json.JSONDecodeError:
332
+ continue
333
+
334
+ return []
335
+
336
+ except Exception:
337
+ return []
338
+
339
+ def _convert_tools_format(self, tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
340
+ """Convert tools to Azure OpenAI format if needed."""
341
+ # Azure OpenAI uses the same tool format as OpenAI
342
+ return tools
343
+
344
+ def _convert_chunk_to_stream_chunk(self, chunk) -> StreamChunk:
345
+ """Convert Azure OpenAI chunk to MassGen StreamChunk format."""
346
+ try:
347
+ if hasattr(chunk, "choices") and chunk.choices:
348
+ choice = chunk.choices[0]
349
+
350
+ if hasattr(choice, "delta") and choice.delta:
351
+ delta = choice.delta
352
+
353
+ # Handle content - this should be the main response
354
+ if hasattr(delta, "content") and delta.content:
355
+ return StreamChunk(type="content", content=delta.content)
356
+
357
+ # Handle tool calls - but only if we actually want them
358
+ if hasattr(delta, "tool_calls") and delta.tool_calls:
359
+ # For now, let's ignore tool calls and treat them as content
360
+ # This prevents the empty response issue
361
+ tool_call_text = ""
362
+ for tool_call in delta.tool_calls:
363
+ if hasattr(tool_call, "function") and tool_call.function:
364
+ if hasattr(tool_call.function, "arguments") and tool_call.function.arguments:
365
+ tool_call_text += tool_call.function.arguments
366
+
367
+ if tool_call_text:
368
+ return StreamChunk(type="content", content=tool_call_text)
369
+
370
+ # Handle finish reason
371
+ if hasattr(choice, "finish_reason") and choice.finish_reason:
372
+ if choice.finish_reason == "stop":
373
+ return StreamChunk(type="done")
374
+ elif choice.finish_reason == "tool_calls":
375
+ return StreamChunk(type="done") # Treat as done
376
+
377
+ # Default chunk - this should not happen for valid responses
378
+ return StreamChunk(type="content", content="")
379
+
380
+ except Exception as e:
381
+ return StreamChunk(type="error", error=f"Error processing chunk: {str(e)}")
382
+
383
+ def extract_tool_call_id(self, tool_call: Dict[str, Any]) -> str:
384
+ """Extract tool call id from Chat Completions-style tool call."""
385
+ return tool_call.get("id", "")