open-swarm 0.1.1743070217__py3-none-any.whl → 0.1.1743364176__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. open_swarm-0.1.1743364176.dist-info/METADATA +286 -0
  2. open_swarm-0.1.1743364176.dist-info/RECORD +260 -0
  3. {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743364176.dist-info}/WHEEL +1 -2
  4. open_swarm-0.1.1743364176.dist-info/entry_points.txt +2 -0
  5. swarm/__init__.py +0 -2
  6. swarm/auth.py +53 -49
  7. swarm/blueprints/README.md +67 -0
  8. swarm/blueprints/burnt_noodles/blueprint_burnt_noodles.py +412 -0
  9. swarm/blueprints/chatbot/blueprint_chatbot.py +98 -0
  10. swarm/blueprints/chatbot/templates/chatbot/chatbot.html +33 -0
  11. swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +183 -0
  12. swarm/blueprints/dilbot_universe/blueprint_dilbot_universe.py +285 -0
  13. swarm/blueprints/divine_code/__init__.py +0 -0
  14. swarm/blueprints/divine_code/apps.py +11 -0
  15. swarm/blueprints/divine_code/blueprint_divine_code.py +219 -0
  16. swarm/blueprints/django_chat/apps.py +6 -0
  17. swarm/blueprints/django_chat/blueprint_django_chat.py +84 -0
  18. swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +37 -0
  19. swarm/blueprints/django_chat/urls.py +8 -0
  20. swarm/blueprints/django_chat/views.py +32 -0
  21. swarm/blueprints/echocraft/blueprint_echocraft.py +44 -0
  22. swarm/blueprints/family_ties/apps.py +11 -0
  23. swarm/blueprints/family_ties/blueprint_family_ties.py +152 -0
  24. swarm/blueprints/family_ties/models.py +19 -0
  25. swarm/blueprints/family_ties/serializers.py +7 -0
  26. swarm/blueprints/family_ties/settings.py +16 -0
  27. swarm/blueprints/family_ties/urls.py +10 -0
  28. swarm/blueprints/family_ties/views.py +26 -0
  29. swarm/blueprints/flock/__init__.py +0 -0
  30. swarm/blueprints/gaggle/blueprint_gaggle.py +184 -0
  31. swarm/blueprints/gotchaman/blueprint_gotchaman.py +232 -0
  32. swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +133 -0
  33. swarm/blueprints/messenger/templates/messenger/messenger.html +46 -0
  34. swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +234 -0
  35. swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +248 -0
  36. swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +156 -0
  37. swarm/blueprints/omniplex/blueprint_omniplex.py +221 -0
  38. swarm/blueprints/rue_code/__init__.py +0 -0
  39. swarm/blueprints/rue_code/blueprint_rue_code.py +291 -0
  40. swarm/blueprints/suggestion/blueprint_suggestion.py +110 -0
  41. swarm/blueprints/unapologetic_press/blueprint_unapologetic_press.py +298 -0
  42. swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
  43. swarm/blueprints/whiskeytango_foxtrot/apps.py +11 -0
  44. swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +256 -0
  45. swarm/extensions/blueprint/__init__.py +30 -15
  46. swarm/extensions/blueprint/agent_utils.py +16 -40
  47. swarm/extensions/blueprint/blueprint_base.py +141 -543
  48. swarm/extensions/blueprint/blueprint_discovery.py +112 -98
  49. swarm/extensions/blueprint/cli_handler.py +185 -0
  50. swarm/extensions/blueprint/config_loader.py +122 -0
  51. swarm/extensions/blueprint/django_utils.py +181 -79
  52. swarm/extensions/blueprint/interactive_mode.py +1 -1
  53. swarm/extensions/config/config_loader.py +83 -200
  54. swarm/extensions/launchers/build_swarm_wrapper.py +0 -0
  55. swarm/extensions/launchers/swarm_cli.py +199 -287
  56. swarm/llm/chat_completion.py +26 -55
  57. swarm/management/__init__.py +0 -0
  58. swarm/management/commands/__init__.py +0 -0
  59. swarm/management/commands/runserver.py +58 -0
  60. swarm/permissions.py +38 -0
  61. swarm/serializers.py +96 -5
  62. swarm/settings.py +95 -110
  63. swarm/static/contrib/fonts/fontawesome-webfont.ttf +7 -0
  64. swarm/static/contrib/fonts/fontawesome-webfont.woff +7 -0
  65. swarm/static/contrib/fonts/fontawesome-webfont.woff2 +7 -0
  66. swarm/static/contrib/markedjs/marked.min.js +6 -0
  67. swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +27 -0
  68. swarm/static/contrib/tabler-icons/alert-triangle.svg +21 -0
  69. swarm/static/contrib/tabler-icons/archive.svg +21 -0
  70. swarm/static/contrib/tabler-icons/artboard.svg +27 -0
  71. swarm/static/contrib/tabler-icons/automatic-gearbox.svg +23 -0
  72. swarm/static/contrib/tabler-icons/box-multiple.svg +19 -0
  73. swarm/static/contrib/tabler-icons/carambola.svg +19 -0
  74. swarm/static/contrib/tabler-icons/copy.svg +20 -0
  75. swarm/static/contrib/tabler-icons/download.svg +21 -0
  76. swarm/static/contrib/tabler-icons/edit.svg +21 -0
  77. swarm/static/contrib/tabler-icons/filled/carambola.svg +13 -0
  78. swarm/static/contrib/tabler-icons/filled/paint.svg +13 -0
  79. swarm/static/contrib/tabler-icons/headset.svg +22 -0
  80. swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +21 -0
  81. swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +21 -0
  82. swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +21 -0
  83. swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +21 -0
  84. swarm/static/contrib/tabler-icons/message-chatbot.svg +22 -0
  85. swarm/static/contrib/tabler-icons/message-star.svg +22 -0
  86. swarm/static/contrib/tabler-icons/message-x.svg +23 -0
  87. swarm/static/contrib/tabler-icons/message.svg +21 -0
  88. swarm/static/contrib/tabler-icons/paperclip.svg +18 -0
  89. swarm/static/contrib/tabler-icons/playlist-add.svg +22 -0
  90. swarm/static/contrib/tabler-icons/robot.svg +26 -0
  91. swarm/static/contrib/tabler-icons/search.svg +19 -0
  92. swarm/static/contrib/tabler-icons/settings.svg +20 -0
  93. swarm/static/contrib/tabler-icons/thumb-down.svg +19 -0
  94. swarm/static/contrib/tabler-icons/thumb-up.svg +19 -0
  95. swarm/static/css/dropdown.css +22 -0
  96. swarm/static/htmx/htmx.min.js +0 -0
  97. swarm/static/js/dropdown.js +23 -0
  98. swarm/static/rest_mode/css/base.css +470 -0
  99. swarm/static/rest_mode/css/chat-history.css +286 -0
  100. swarm/static/rest_mode/css/chat.css +251 -0
  101. swarm/static/rest_mode/css/chatbot.css +74 -0
  102. swarm/static/rest_mode/css/chatgpt.css +62 -0
  103. swarm/static/rest_mode/css/colors/corporate.css +74 -0
  104. swarm/static/rest_mode/css/colors/pastel.css +81 -0
  105. swarm/static/rest_mode/css/colors/tropical.css +82 -0
  106. swarm/static/rest_mode/css/general.css +142 -0
  107. swarm/static/rest_mode/css/layout.css +167 -0
  108. swarm/static/rest_mode/css/layouts/messenger-layout.css +17 -0
  109. swarm/static/rest_mode/css/layouts/minimalist-layout.css +57 -0
  110. swarm/static/rest_mode/css/layouts/mobile-layout.css +8 -0
  111. swarm/static/rest_mode/css/messages.css +84 -0
  112. swarm/static/rest_mode/css/messenger.css +135 -0
  113. swarm/static/rest_mode/css/settings.css +91 -0
  114. swarm/static/rest_mode/css/simple.css +44 -0
  115. swarm/static/rest_mode/css/slack.css +58 -0
  116. swarm/static/rest_mode/css/style.css +156 -0
  117. swarm/static/rest_mode/css/theme.css +30 -0
  118. swarm/static/rest_mode/css/toast.css +40 -0
  119. swarm/static/rest_mode/js/auth.js +9 -0
  120. swarm/static/rest_mode/js/blueprint.js +41 -0
  121. swarm/static/rest_mode/js/blueprintUtils.js +12 -0
  122. swarm/static/rest_mode/js/chatLogic.js +79 -0
  123. swarm/static/rest_mode/js/debug.js +63 -0
  124. swarm/static/rest_mode/js/events.js +98 -0
  125. swarm/static/rest_mode/js/main.js +19 -0
  126. swarm/static/rest_mode/js/messages.js +264 -0
  127. swarm/static/rest_mode/js/messengerLogic.js +355 -0
  128. swarm/static/rest_mode/js/modules/apiService.js +84 -0
  129. swarm/static/rest_mode/js/modules/blueprintManager.js +162 -0
  130. swarm/static/rest_mode/js/modules/chatHistory.js +110 -0
  131. swarm/static/rest_mode/js/modules/debugLogger.js +14 -0
  132. swarm/static/rest_mode/js/modules/eventHandlers.js +107 -0
  133. swarm/static/rest_mode/js/modules/messageProcessor.js +120 -0
  134. swarm/static/rest_mode/js/modules/state.js +7 -0
  135. swarm/static/rest_mode/js/modules/userInteractions.js +29 -0
  136. swarm/static/rest_mode/js/modules/validation.js +23 -0
  137. swarm/static/rest_mode/js/rendering.js +119 -0
  138. swarm/static/rest_mode/js/settings.js +130 -0
  139. swarm/static/rest_mode/js/sidebar.js +94 -0
  140. swarm/static/rest_mode/js/simpleLogic.js +37 -0
  141. swarm/static/rest_mode/js/slackLogic.js +66 -0
  142. swarm/static/rest_mode/js/splash.js +76 -0
  143. swarm/static/rest_mode/js/theme.js +111 -0
  144. swarm/static/rest_mode/js/toast.js +36 -0
  145. swarm/static/rest_mode/js/ui.js +265 -0
  146. swarm/static/rest_mode/js/validation.js +57 -0
  147. swarm/static/rest_mode/svg/animated_spinner.svg +12 -0
  148. swarm/static/rest_mode/svg/arrow_down.svg +5 -0
  149. swarm/static/rest_mode/svg/arrow_left.svg +5 -0
  150. swarm/static/rest_mode/svg/arrow_right.svg +5 -0
  151. swarm/static/rest_mode/svg/arrow_up.svg +5 -0
  152. swarm/static/rest_mode/svg/attach.svg +8 -0
  153. swarm/static/rest_mode/svg/avatar.svg +7 -0
  154. swarm/static/rest_mode/svg/canvas.svg +6 -0
  155. swarm/static/rest_mode/svg/chat_history.svg +4 -0
  156. swarm/static/rest_mode/svg/close.svg +5 -0
  157. swarm/static/rest_mode/svg/copy.svg +4 -0
  158. swarm/static/rest_mode/svg/dark_mode.svg +3 -0
  159. swarm/static/rest_mode/svg/edit.svg +5 -0
  160. swarm/static/rest_mode/svg/layout.svg +9 -0
  161. swarm/static/rest_mode/svg/logo.svg +29 -0
  162. swarm/static/rest_mode/svg/logout.svg +5 -0
  163. swarm/static/rest_mode/svg/mobile.svg +5 -0
  164. swarm/static/rest_mode/svg/new_chat.svg +4 -0
  165. swarm/static/rest_mode/svg/not_visible.svg +5 -0
  166. swarm/static/rest_mode/svg/plus.svg +7 -0
  167. swarm/static/rest_mode/svg/run_code.svg +6 -0
  168. swarm/static/rest_mode/svg/save.svg +4 -0
  169. swarm/static/rest_mode/svg/search.svg +6 -0
  170. swarm/static/rest_mode/svg/settings.svg +4 -0
  171. swarm/static/rest_mode/svg/speaker.svg +5 -0
  172. swarm/static/rest_mode/svg/stop.svg +6 -0
  173. swarm/static/rest_mode/svg/thumbs_down.svg +3 -0
  174. swarm/static/rest_mode/svg/thumbs_up.svg +3 -0
  175. swarm/static/rest_mode/svg/toggle_off.svg +6 -0
  176. swarm/static/rest_mode/svg/toggle_on.svg +6 -0
  177. swarm/static/rest_mode/svg/trash.svg +10 -0
  178. swarm/static/rest_mode/svg/undo.svg +3 -0
  179. swarm/static/rest_mode/svg/visible.svg +8 -0
  180. swarm/static/rest_mode/svg/voice.svg +10 -0
  181. swarm/templates/account/login.html +22 -0
  182. swarm/templates/account/signup.html +32 -0
  183. swarm/templates/base.html +30 -0
  184. swarm/templates/chat.html +43 -0
  185. swarm/templates/index.html +35 -0
  186. swarm/templates/rest_mode/components/chat_sidebar.html +55 -0
  187. swarm/templates/rest_mode/components/header.html +45 -0
  188. swarm/templates/rest_mode/components/main_chat_pane.html +41 -0
  189. swarm/templates/rest_mode/components/settings_dialog.html +97 -0
  190. swarm/templates/rest_mode/components/splash_screen.html +7 -0
  191. swarm/templates/rest_mode/components/top_bar.html +28 -0
  192. swarm/templates/rest_mode/message_ui.html +50 -0
  193. swarm/templates/rest_mode/slackbot.html +30 -0
  194. swarm/templates/simple_blueprint_page.html +24 -0
  195. swarm/templates/websocket_partials/final_system_message.html +3 -0
  196. swarm/templates/websocket_partials/system_message.html +4 -0
  197. swarm/templates/websocket_partials/user_message.html +5 -0
  198. swarm/urls.py +57 -74
  199. swarm/utils/log_utils.py +63 -0
  200. swarm/views/api_views.py +48 -39
  201. swarm/views/chat_views.py +156 -70
  202. swarm/views/core_views.py +85 -90
  203. swarm/views/model_views.py +64 -121
  204. swarm/views/utils.py +65 -441
  205. open_swarm-0.1.1743070217.dist-info/METADATA +0 -258
  206. open_swarm-0.1.1743070217.dist-info/RECORD +0 -89
  207. open_swarm-0.1.1743070217.dist-info/entry_points.txt +0 -3
  208. open_swarm-0.1.1743070217.dist-info/top_level.txt +0 -1
  209. swarm/agent/agent.py +0 -49
  210. swarm/core.py +0 -326
  211. swarm/extensions/mcp/__init__.py +0 -1
  212. swarm/extensions/mcp/cache_utils.py +0 -36
  213. swarm/extensions/mcp/mcp_client.py +0 -341
  214. swarm/extensions/mcp/mcp_constants.py +0 -7
  215. swarm/extensions/mcp/mcp_tool_provider.py +0 -110
  216. swarm/types.py +0 -126
  217. {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743364176.dist-info}/licenses/LICENSE +0 -0
swarm/core.py DELETED
@@ -1,326 +0,0 @@
1
- import os
2
- import json
3
- import logging
4
- import asyncio
5
- import re # Import re for tool name validation in provider
6
- from typing import List, Dict, Optional, Union, AsyncGenerator, Any, Callable
7
- from openai import AsyncOpenAI, OpenAIError
8
- import uuid
9
-
10
- from .types import Agent, LLMConfig, Response, ToolCall, ToolResult, ChatMessage, Tool
11
- from .settings import Settings
12
- from .extensions.config.config_loader import load_server_config, load_llm_config, get_server_params # Import load_server_config
13
- from .utils.redact import redact_sensitive_data
14
- from .llm.chat_completion import get_chat_completion_message
15
- from .extensions.mcp.mcp_tool_provider import MCPToolProvider
16
- from .utils.context_utils import get_token_count
17
-
18
- settings = Settings()
19
- logger = logging.getLogger(__name__)
20
- logger.setLevel(settings.log_level.upper())
21
- if not logger.handlers and not logging.getLogger().handlers:
22
- log_handler = logging.StreamHandler()
23
- formatter = logging.Formatter(settings.log_format.value)
24
- log_handler.setFormatter(formatter)
25
- logger.addHandler(log_handler)
26
-
27
- logger.debug(f"Swarm Core initialized with log level: {settings.log_level.upper()}")
28
-
29
- # --- FIX: Define correct separator ---
30
- MCP_TOOL_SEPARATOR = "__"
31
-
32
- # --- Helper Function: Discover and Merge Agent Tools ---
33
- async def discover_and_merge_agent_tools(agent: Agent, config: Dict[str, Any], timeout: int, debug: bool) -> List[Tool]:
34
- """
35
- Discovers tools from MCP servers listed in agent.mcp_servers and merges
36
- them with the agent's static functions. Returns a list of Tool objects.
37
- """
38
- merged_tools: Dict[str, Tool] = {}
39
-
40
- # 1. Process static functions
41
- if hasattr(agent, 'functions') and agent.functions:
42
- for func in agent.functions:
43
- if isinstance(func, Tool):
44
- if func.name in merged_tools: logger.warning(f"Duplicate tool name '{func.name}'. Overwriting.")
45
- merged_tools[func.name] = func
46
- elif callable(func):
47
- tool_name = getattr(func, '__name__', f'callable_{uuid.uuid4().hex[:6]}')
48
- if not re.match(r"^[a-zA-Z0-9_-]{1,64}$", tool_name): # Validate static tool name
49
- logger.warning(f"Static function name '{tool_name}' violates OpenAI pattern. Skipping.")
50
- continue
51
- if tool_name in merged_tools: logger.warning(f"Duplicate static tool name '{tool_name}'. Overwriting.")
52
-
53
- docstring = getattr(func, '__doc__', None)
54
- description = docstring.strip() if docstring else f"Executes the {tool_name} function."
55
-
56
- input_schema = {"type": "object", "properties": {}}
57
- merged_tools[tool_name] = Tool(name=tool_name, func=func, description=description, input_schema=input_schema)
58
- else: logger.warning(f"Ignoring non-callable item in agent functions list: {func}")
59
- logger.debug(f"Agent '{agent.name}': Processed {len(merged_tools)} static tools.")
60
-
61
- # 2. Discover tools from MCP servers
62
- if agent.mcp_servers:
63
- mcp_server_configs = config.get("mcpServers", {})
64
- discovery_tasks = []
65
- for server_name in agent.mcp_servers:
66
- if server_name not in mcp_server_configs:
67
- logger.warning(f"Config for MCP server '{server_name}' for agent '{agent.name}' not found. Skipping.")
68
- continue
69
- server_config = mcp_server_configs[server_name]
70
- if not get_server_params(server_config, server_name):
71
- logger.error(f"Invalid config for MCP server '{server_name}'. Cannot discover.")
72
- continue
73
- try:
74
- provider = MCPToolProvider.get_instance(server_name=server_name, server_config=server_config, timeout=timeout, debug=debug)
75
- if provider.client: discovery_tasks.append(provider.discover_tools(agent))
76
- else: logger.error(f"MCPClient failed init for '{server_name}'.")
77
- except Exception as e: logger.error(f"Error getting MCP instance for '{server_name}': {e}", exc_info=True)
78
-
79
- if discovery_tasks:
80
- logger.debug(f"Awaiting discovery from {len(discovery_tasks)} MCP providers.")
81
- results = await asyncio.gather(*discovery_tasks, return_exceptions=True)
82
- for result in results:
83
- if isinstance(result, Exception): logger.error(f"MCP discovery error: {result}")
84
- elif isinstance(result, list):
85
- for mcp_tool in result:
86
- if mcp_tool.name in merged_tools: logger.warning(f"Duplicate tool name '{mcp_tool.name}' (MCP vs Static/Other MCP). Overwriting.")
87
- merged_tools[mcp_tool.name] = mcp_tool # Name already prefixed by provider
88
- else: logger.warning(f"Unexpected result type during MCP discovery: {type(result)}")
89
-
90
- final_tool_list = list(merged_tools.values())
91
- logger.info(f"Agent '{agent.name}': Final merged tool count: {len(final_tool_list)}")
92
- if debug: logger.debug(f"Agent '{agent.name}': Final tools: {[t.name for t in final_tool_list]}")
93
- return final_tool_list
94
-
95
- # --- Helper Function: Format Tools for LLM ---
96
- def format_tools_for_llm(tools: List[Tool]) -> List[Dict[str, Any]]:
97
- """Formats the Tool list into the structure expected by OpenAI API."""
98
- if not tools: return []
99
- formatted = []
100
- for tool in tools:
101
- parameters = tool.input_schema or {"type": "object", "properties": {}}
102
- if not isinstance(parameters, dict) or "type" not in parameters:
103
- logger.warning(f"Invalid schema for tool '{tool.name}'. Using default. Schema: {parameters}")
104
- parameters = {"type": "object", "properties": {}}
105
- elif parameters.get("type") == "object" and "properties" not in parameters:
106
- parameters["properties"] = {}
107
-
108
- # Validate tool name again before formatting
109
- if not re.match(r"^[a-zA-Z0-9_-]{1,64}$", tool.name):
110
- logger.error(f"Tool name '{tool.name}' is invalid for OpenAI API. Skipping.")
111
- continue
112
-
113
- formatted.append({
114
- "type": "function",
115
- "function": {
116
- "name": tool.name,
117
- "description": tool.description or f"Executes the {tool.name} tool.",
118
- "parameters": parameters,
119
- },
120
- })
121
- return formatted
122
-
123
- # --- Swarm Class ---
124
- class Swarm:
125
- def __init__(
126
- self,
127
- llm_profile: str = "default",
128
- config: Optional[dict] = None,
129
- api_key: Optional[str] = None,
130
- base_url: Optional[str] = None,
131
- model: Optional[str] = None,
132
- agents: Optional[Dict[str, Agent]] = None,
133
- max_context_tokens: int = 8000,
134
- max_context_messages: int = 50,
135
- max_tool_response_tokens: int = 4096,
136
- max_total_tool_response_tokens: int = 16384,
137
- max_tool_calls_per_turn: int = 10,
138
- tool_execution_timeout: int = 120,
139
- tool_discovery_timeout: int = 15,
140
- debug: bool = False,
141
- ):
142
- self.debug = debug or settings.debug
143
- if self.debug: logger.setLevel(logging.DEBUG); [h.setLevel(logging.DEBUG) for h in logging.getLogger().handlers if hasattr(h, 'setLevel')] ; logger.debug("Debug mode enabled.")
144
- self.tool_execution_timeout = tool_execution_timeout
145
- self.tool_discovery_timeout = tool_discovery_timeout
146
- self.agents = agents or {}; logger.debug(f"Initial agents: {list(self.agents.keys())}")
147
- # Load config if not provided
148
- self.config = config if config is not None else load_server_config()
149
- logger.debug(f"INIT START: Received api_key arg: {'****' if api_key else 'None'}")
150
-
151
- llm_profile_name = os.getenv("DEFAULT_LLM", llm_profile)
152
- logger.debug(f"INIT: Using LLM profile name: '{llm_profile_name}'")
153
- try:
154
- loaded_config_dict = load_llm_config(self.config, llm_profile_name)
155
- except Exception as e: logger.critical(f"INIT: Failed to load config for profile '{llm_profile_name}': {e}", exc_info=True); raise
156
-
157
- final_config = loaded_config_dict.copy(); log_key_source = final_config.get("_log_key_source", "load_llm_config")
158
- if api_key is not None: final_config['api_key'] = api_key; log_key_source = "__init__ arg"
159
- if base_url is not None: final_config['base_url'] = base_url
160
- if model is not None: final_config['model'] = model
161
- self.current_llm_config = final_config; self.model = self.current_llm_config.get("model"); self.provider = self.current_llm_config.get("provider")
162
-
163
- self.max_context_tokens=max_context_tokens; self.max_context_messages=max_context_messages
164
- self.max_tool_response_tokens=max_tool_response_tokens; self.max_total_tool_response_tokens=max_total_tool_response_tokens
165
- self.max_tool_calls_per_turn=max_tool_calls_per_turn
166
-
167
- client_kwargs = {"api_key": self.current_llm_config.get("api_key"), "base_url": self.current_llm_config.get("base_url")}
168
- client_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
169
- try:
170
- self.client = AsyncOpenAI(**client_kwargs)
171
- final_api_key_used = self.current_llm_config.get("api_key")
172
- logger.info(f"Swarm initialized. LLM Profile: '{llm_profile_name}', Model: '{self.model}', Key Source: {log_key_source}, Key Used: {'****' if final_api_key_used else 'None'}")
173
- if self.debug: logger.debug(f"AsyncOpenAI client kwargs: {redact_sensitive_data(client_kwargs)}")
174
- except Exception as e: logger.critical(f"Failed to initialize OpenAI client: {e}", exc_info=True); raise
175
- self._agent_tools: Dict[str, List[Tool]] = {}
176
-
177
- def register_agent(self, agent: Agent):
178
- if agent.name in self.agents: logger.warning(f"Agent '{agent.name}' already registered. Overwriting.")
179
- self.agents[agent.name] = agent; logger.info(f"Agent '{agent.name}' registered.")
180
- if agent.name in self._agent_tools: del self._agent_tools[agent.name]
181
- if self.debug: logger.debug(f"Agent details: {agent}")
182
-
183
- async def _get_agent_tools(self, agent: Agent) -> List[Tool]:
184
- if agent.name not in self._agent_tools:
185
- logger.debug(f"Tools cache miss for agent '{agent.name}'. Discovering...")
186
- self._agent_tools[agent.name] = await discover_and_merge_agent_tools(agent, self.config, self.tool_discovery_timeout, self.debug)
187
- return self._agent_tools[agent.name]
188
-
189
- async def _execute_tool_call(self, agent: Agent, tool_call: ToolCall, context_variables: Dict[str, Any]) -> ToolResult:
190
- """Executes a single tool call, handling static and MCP tools."""
191
- function_name = tool_call.function.name # This is the name LLM used (could be prefixed)
192
- tool_call_id = tool_call.id
193
- logger.info(f"Executing tool call '{function_name}' (ID: {tool_call_id}) for agent '{agent.name}'.")
194
- arguments = {}
195
- content = f"Error: Tool '{function_name}' execution failed."
196
-
197
- try:
198
- args_raw = tool_call.function.arguments
199
- arguments = json.loads(args_raw) if isinstance(args_raw, str) else args_raw
200
- if not isinstance(arguments, dict):
201
- logger.error(f"Parsed tool args for {function_name} not dict: {type(arguments)}. Args: {args_raw}")
202
- raise ValueError("Tool arguments must be a JSON object.")
203
- except json.JSONDecodeError as e:
204
- logger.error(f"JSONDecodeError parsing args for {function_name}: {e}. Args: {args_raw}")
205
- content = f"Error: Invalid JSON args for '{function_name}': {e}"
206
- except ValueError as e: # Catch the explicit error from above
207
- content = str(e)
208
- except Exception as e:
209
- logger.error(f"Error processing args for {function_name}: {e}", exc_info=True)
210
- content = f"Error processing args for '{function_name}'."
211
-
212
- tool_executed = False
213
- if isinstance(arguments, dict): # Proceed only if args are valid
214
- agent_tools = await self._get_agent_tools(agent)
215
- target_tool: Optional[Tool] = next((t for t in agent_tools if t.name == function_name), None)
216
-
217
- if target_tool and callable(target_tool.func):
218
- tool_executed = True
219
- logger.debug(f"Found tool '{function_name}'. Executing...")
220
- try:
221
- if asyncio.iscoroutinefunction(target_tool.func):
222
- result = await asyncio.wait_for(target_tool.func(**arguments), timeout=self.tool_execution_timeout)
223
- else:
224
- # Consider running sync functions in threadpool executor
225
- result = target_tool.func(**arguments)
226
-
227
- # Process result
228
- if isinstance(result, Agent):
229
- logger.info(f"Handoff signal: Result is Agent '{result.name}'.")
230
- # --- FIX: Use correct separator ---
231
- content = f"HANDOFF{MCP_TOOL_SEPARATOR}{result.name}"
232
- elif isinstance(result, (dict, list, tuple)): content = json.dumps(result, default=str)
233
- elif result is None: content = "Tool executed successfully with no return value."
234
- else: content = str(result)
235
- logger.debug(f"Tool '{function_name}' executed. Raw result type: {type(result)}")
236
-
237
- except asyncio.TimeoutError:
238
- logger.error(f"Timeout executing tool '{function_name}'.")
239
- content = f"Error: Tool '{function_name}' timed out ({self.tool_execution_timeout}s)."
240
- except Exception as e:
241
- logger.error(f"Error executing tool {function_name}: {e}", exc_info=True)
242
- content = f"Error: Tool '{function_name}' failed: {e}"
243
- # else: Tool not found error handled below
244
-
245
- if not tool_executed and isinstance(arguments, dict):
246
- logger.error(f"Tool '{function_name}' not found for agent '{agent.name}'. Available: {[t.name for t in await self._get_agent_tools(agent)]}")
247
- content = f"Error: Tool '{function_name}' not available for agent '{agent.name}'."
248
-
249
- # Truncation
250
- # --- FIX: Use correct separator ---
251
- if isinstance(content, str) and not content.startswith(f"HANDOFF{MCP_TOOL_SEPARATOR}"):
252
- token_count = get_token_count(content, self.current_llm_config.get("model"))
253
- if token_count > self.max_tool_response_tokens:
254
- logger.warning(f"Truncating tool response '{function_name}'. Size: {token_count} > Limit: {self.max_tool_response_tokens}")
255
- content = content[:self.max_tool_response_tokens * 4] + "... (truncated)"
256
-
257
- return ToolResult(tool_call_id=tool_call_id, name=function_name, content=content)
258
-
259
- async def _run_non_streaming(self, agent: Agent, messages: List[Dict[str, Any]], context_variables: Optional[Dict[str, Any]] = None, max_turns: int = 10, debug: bool = False) -> Response:
260
- current_agent = agent; history = list(messages); context_vars = context_variables.copy() if context_variables else {}; turn = 0
261
- while turn < max_turns:
262
- turn += 1; logger.debug(f"Turn {turn} starting with agent '{current_agent.name}'.")
263
- agent_tools = await self._get_agent_tools(current_agent); formatted_tools = format_tools_for_llm(agent_tools)
264
- if debug and formatted_tools: logger.debug(f"Tools for '{current_agent.name}': {[t['function']['name'] for t in formatted_tools]}")
265
- try:
266
- ai_message_dict = await get_chat_completion_message(client=self.client, agent=current_agent, history=history, context_variables=context_vars, current_llm_config=self.current_llm_config, max_context_tokens=self.max_context_tokens, max_context_messages=self.max_context_messages, tools=formatted_tools or None, tool_choice="auto" if formatted_tools else None, stream=False, debug=debug)
267
- ai_message_dict["sender"] = current_agent.name; history.append(ai_message_dict)
268
- tool_calls_raw = ai_message_dict.get("tool_calls")
269
- if tool_calls_raw:
270
- if not isinstance(tool_calls_raw, list): tool_calls_raw = []
271
- logger.info(f"Agent '{current_agent.name}' requested {len(tool_calls_raw)} tool calls.")
272
- tool_calls_to_execute = []
273
- for tc_raw in tool_calls_raw[:self.max_tool_calls_per_turn]:
274
- try:
275
- if isinstance(tc_raw, dict) and 'function' in tc_raw and isinstance(tc_raw['function'], dict) and 'name' in tc_raw['function'] and 'arguments' in tc_raw['function']: tool_calls_to_execute.append(ToolCall(**tc_raw))
276
- else: logger.warning(f"Skipping malformed tool call: {tc_raw}")
277
- except Exception as p_err: logger.warning(f"Skipping tool call validation error: {p_err}. Raw: {tc_raw}")
278
- if len(tool_calls_raw) > self.max_tool_calls_per_turn: logger.warning(f"Clamping tool calls to {self.max_tool_calls_per_turn}.")
279
-
280
- tool_tasks = [self._execute_tool_call(current_agent, tc, context_vars) for tc in tool_calls_to_execute]
281
- tool_results: List[ToolResult] = await asyncio.gather(*tool_tasks)
282
- next_agent_name_from_handoff = None; total_tool_response_tokens = 0
283
- for result in tool_results:
284
- history.append(result.model_dump(exclude_none=True)); content = result.content
285
- if isinstance(content, str):
286
- # --- FIX: Use correct separator ---
287
- if content.startswith(f"HANDOFF{MCP_TOOL_SEPARATOR}"):
288
- parts = content.split(MCP_TOOL_SEPARATOR, 1); potential_next_agent = parts[1] if len(parts) > 1 else None
289
- if potential_next_agent and potential_next_agent in self.agents:
290
- if not next_agent_name_from_handoff: next_agent_name_from_handoff = potential_next_agent; logger.info(f"Handoff to '{next_agent_name_from_handoff}' confirmed.")
291
- elif next_agent_name_from_handoff != potential_next_agent: logger.warning(f"Multiple handoffs requested. Using first '{next_agent_name_from_handoff}'.")
292
- else: logger.warning(f"Handoff to unknown agent '{potential_next_agent}'. Ignoring.")
293
- else: total_tool_response_tokens += get_token_count(content, self.current_llm_config.get("model"))
294
- if total_tool_response_tokens > self.max_total_tool_response_tokens: logger.error(f"Total tool tokens ({total_tool_response_tokens}) exceeded limit. Ending run."); history.append({"role": "assistant", "sender": "System", "content": "[System Error: Tool responses token limit exceeded.]"}); break
295
- if next_agent_name_from_handoff: current_agent = self.agents[next_agent_name_from_handoff]; context_vars["active_agent_name"] = current_agent.name; logger.debug(f"Activating agent '{current_agent.name}'."); continue
296
- else: continue
297
- else: break # No tool calls, end interaction
298
- except OpenAIError as e: logger.error(f"API error turn {turn} for '{current_agent.name}': {e}", exc_info=True); history.append({"role": "assistant", "sender": "System", "content": f"[System Error: API call failed]"}); break
299
- except Exception as e: logger.error(f"Unexpected error turn {turn} for '{current_agent.name}': {e}", exc_info=True); history.append({"role": "assistant", "sender": "System", "content": f"[System Error: Unexpected error]"}); break
300
- if turn >= max_turns: logger.warning(f"Reached max turns ({max_turns}).")
301
- logger.debug(f"Non-streaming run completed. Turns={turn}, History Messages={len(history)}.")
302
- final_messages_raw = history[len(messages):]; final_messages_typed = [ChatMessage(**msg) for msg in final_messages_raw if isinstance(msg, dict)]
303
- response_id = f"response-{uuid.uuid4()}"
304
- return Response(id=response_id, messages=final_messages_typed, agent=current_agent, context_variables=context_vars)
305
-
306
- async def _run_streaming(self, agent: Agent, messages: List[Dict[str, Any]], context_variables: Optional[Dict[str, Any]] = None, max_turns: int = 10, debug: bool = False) -> AsyncGenerator[Dict[str, Any], None]:
307
- current_agent = agent; history = list(messages); context_vars = context_variables.copy() if context_variables else {}; logger.debug(f"Streaming run starting for '{current_agent.name}'. (Tool exec/handoff N/A)")
308
- agent_tools = await self._get_agent_tools(current_agent); formatted_tools = format_tools_for_llm(agent_tools)
309
- if debug and formatted_tools: logger.debug(f"Tools for '{current_agent.name}' (streaming): {[t['function']['name'] for t in formatted_tools]}")
310
- try:
311
- stream_generator = get_chat_completion_message(client=self.client, agent=current_agent, history=history, context_variables=context_vars, current_llm_config=self.current_llm_config, max_context_tokens=self.max_context_tokens, max_context_messages=self.max_context_messages, tools=formatted_tools or None, tool_choice="auto" if formatted_tools else None, stream=True, debug=debug)
312
- async for chunk in stream_generator: yield chunk
313
- logger.warning("Tool calls/handoffs not processed in streaming.")
314
- except OpenAIError as e: logger.error(f"API error stream for '{current_agent.name}': {e}", exc_info=True); yield {"error": f"API call failed: {str(e)}"}
315
- except Exception as e: logger.error(f"Error stream for '{current_agent.name}': {e}", exc_info=True); yield {"error": f"Unexpected error: {str(e)}"}
316
- logger.debug(f"Streaming run finished for '{current_agent.name}'.")
317
-
318
- async def run(self, agent: Agent, messages: List[Dict[str, Any]], context_variables: Optional[Dict[str, Any]] = None, max_turns: int = 10, stream: bool = False, debug: bool = False) -> Union[Response, AsyncGenerator[Dict[str, Any], None]]:
319
- effective_debug = debug or self.debug
320
- if effective_debug != logger.isEnabledFor(logging.DEBUG):
321
- new_level = logging.DEBUG if effective_debug else settings.log_level.upper(); logger.setLevel(new_level); [h.setLevel(new_level) for h in logger.handlers]; logger.debug(f"Log level set to {new_level}.")
322
- if not agent: raise ValueError("Agent cannot be None")
323
- if not isinstance(messages, list): raise TypeError("Messages must be a list")
324
- logger.info(f"Starting {'STREAMING' if stream else 'NON-STREAMING'} run with agent '{agent.name}'")
325
- if stream: return self._run_streaming(agent, messages, context_variables, max_turns, effective_debug)
326
- else: return await self._run_non_streaming(agent, messages, context_variables, max_turns, effective_debug)
@@ -1 +0,0 @@
1
- # This is the __init__.py for the 'mcp' package.
@@ -1,36 +0,0 @@
1
- # cache_utils.py
2
-
3
- from typing import Any
4
-
5
- class DummyCache:
6
- """A dummy cache that performs no operations."""
7
- def get(self, key: str, default: Any = None) -> Any:
8
- return default
9
-
10
- def set(self, key: str, value: Any, timeout: int = None) -> None:
11
- pass
12
-
13
- def get_cache():
14
- """
15
- Attempts to retrieve Django's cache. If Django isn't available or configured,
16
- returns a DummyCache instance.
17
- """
18
- try:
19
- # Intentionally commented out Django imports to avoid mandatory dependency for now
20
- # import django
21
- # from django.conf import settings
22
- # from django.core.cache import cache as django_cache
23
- # from django.core.exceptions import ImproperlyConfigured
24
-
25
- # if not settings.configured:
26
- # # Django settings are not configured; return DummyCache
27
- # return DummyCache()
28
-
29
- # return django_cache
30
- # For now, always return DummyCache until Django integration is confirmed/required
31
- return DummyCache()
32
-
33
-
34
- except (ImportError): # Removed ImproperlyConfigured as Django is not imported
35
- # Django is not installed or not properly configured; use DummyCache
36
- return DummyCache()