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
@@ -1,562 +1,160 @@
1
- """
2
- Swarm Blueprint Base Module (Sync Interactive Mode)
3
- """
4
-
5
- import asyncio
6
- import json
7
1
  import logging
8
- from src.swarm.utils.message_sequence import repair_message_payload, validate_message_sequence
9
- from src.swarm.utils.context_utils import truncate_message_history, get_token_count
10
2
  import os
11
- import uuid
12
- import sys
3
+ import inspect
4
+ from typing import Dict, List, Any, AsyncGenerator, Optional, Type
13
5
  from abc import ABC, abstractmethod
14
- from typing import Optional, Dict, Any, List
15
-
6
+ from jinja2 import Environment, FileSystemLoader, select_autoescape
16
7
  from pathlib import Path
17
- from swarm.core import Swarm
18
- from swarm.extensions.config.config_loader import load_server_config
19
- from swarm.settings import DEBUG
20
- from swarm.utils.redact import redact_sensitive_data
21
- from swarm.utils.context_utils import get_token_count, truncate_message_history
22
- from swarm.extensions.blueprint.agent_utils import (
23
- get_agent_name,
24
- discover_tools_for_agent,
25
- discover_resources_for_agent,
26
- initialize_agents
27
- )
28
- from swarm.extensions.blueprint.django_utils import register_django_components
29
- from swarm.extensions.blueprint.spinner import Spinner
30
- from swarm.extensions.blueprint.output_utils import pretty_print_response
31
- from dotenv import load_dotenv
32
- import argparse
33
- from swarm.types import Agent, Response
34
-
35
- logger = logging.getLogger(__name__)
36
-
37
- class BlueprintBase(ABC):
38
- """Base class for Swarm blueprints with sync interactive mode and Django integration."""
39
-
40
- def __init__(
41
- self,
42
- config: dict,
43
- auto_complete_task: bool = False,
44
- update_user_goal: bool = False,
45
- update_user_goal_frequency: int = 5,
46
- skip_django_registration: bool = False,
47
- record_chat: bool = False,
48
- log_file_path: Optional[str] = None,
49
- debug: bool = False,
50
- use_markdown: bool = False,
51
- **kwargs
52
- ):
53
- self.auto_complete_task = auto_complete_task
54
- self.update_user_goal = update_user_goal
55
- self.update_user_goal_frequency = max(1, update_user_goal_frequency)
56
- self.last_goal_update_count = 0
57
- self.record_chat = record_chat
58
- self.conversation_id = str(uuid.uuid4()) if record_chat else None
59
- self.log_file_path = log_file_path
60
- self.debug = debug or DEBUG
61
- self.use_markdown = use_markdown
62
- self._urls_registered = False
63
-
64
- if self.use_markdown:
65
- logger.debug("Markdown rendering enabled (if rich is available).")
66
- logger.debug(f"Initializing {self.__class__.__name__} with config: {redact_sensitive_data(config)}")
67
- if not hasattr(self, 'metadata') or not isinstance(self.metadata, dict):
68
- raise AssertionError(f"{self.__class__.__name__} must define a 'metadata' property returning a dictionary.")
8
+ import copy
9
+ # *** Import Django settings ***
10
+ from django.conf import settings
69
11
 
70
- self.truncation_mode = os.getenv("SWARM_TRUNCATION_MODE", "pairs").lower()
71
- self.max_context_tokens = max(1, self.metadata.get("max_context_tokens", 8000))
72
- self.max_context_messages = max(1, self.metadata.get("max_context_messages", 50))
73
- logger.debug(f"Truncation settings: mode={self.truncation_mode}, max_tokens={self.max_context_tokens}, max_messages={self.max_context_messages}")
12
+ # *** REMOVE SwarmConfig import from apps.py ***
13
+ # from swarm.apps import SwarmConfig
74
14
 
75
- load_dotenv()
76
- logger.debug("Loaded environment variables from .env.")
15
+ # Import helpers from config_loader if needed directly
16
+ # from swarm.extensions.config.config_loader import find_config_file, DEFAULT_CONFIG_FILENAME
77
17
 
78
- self.config = config
79
- self.skip_django_registration = skip_django_registration or not os.environ.get("DJANGO_SETTINGS_MODULE")
80
- self.swarm = kwargs.get('swarm_instance') or Swarm(config=self.config, debug=self.debug)
81
- logger.debug("Swarm instance initialized.")
18
+ # *** LLMRegistry doesn't exist yet - Comment out import ***
19
+ # from swarm.llm.llm_registry import LLMRegistry # Example path - CHANGE ME!
82
20
 
83
- self.context_variables: Dict[str, Any] = {"user_goal": ""}
84
- self.starting_agent = None
85
- self._discovered_tools: Dict[str, List[Any]] = {}
86
- self._discovered_resources: Dict[str, List[Any]] = {}
87
- self.spinner = Spinner(interactive=not kwargs.get('non_interactive', False))
88
-
89
- required_env_vars = set(self.metadata.get('env_vars', []))
90
- missing_vars = [var for var in required_env_vars if not os.getenv(var)]
91
- if missing_vars:
92
- logger.warning(f"Missing environment variables for {self.metadata.get('title', self.__class__.__name__)}: {', '.join(missing_vars)}")
93
-
94
- self.required_mcp_servers = self.metadata.get('required_mcp_servers', [])
95
- logger.debug(f"Required MCP servers: {self.required_mcp_servers}")
96
-
97
- if self._is_create_agents_overridden():
98
- initialize_agents(self)
99
- register_django_components(self)
100
-
101
- def _is_create_agents_overridden(self) -> bool:
102
- """Check if the 'create_agents' method is overridden in the subclass."""
103
- return self.__class__.create_agents is not BlueprintBase.create_agents
21
+ logger = logging.getLogger(__name__)
104
22
 
105
- def truncate_message_history(self, messages: List[Dict[str, Any]], model: str) -> List[Dict[str, Any]]:
106
- """Truncate message history using the centralized utility."""
107
- return truncate_message_history(messages, model, self.max_context_tokens, self.max_context_messages)
23
+ class BlueprintBase(ABC):
24
+ """
25
+ Abstract base class for all blueprints.
26
+ Ensures common interface and configuration handling.
27
+ """
28
+ metadata: Dict[str, Any] = {}
108
29
 
109
- @property
110
30
  @abstractmethod
111
- def metadata(self) -> Dict[str, Any]:
112
- """Abstract property for blueprint metadata."""
113
- raise NotImplementedError
114
-
115
- def create_agents(self) -> Dict[str, Agent]:
116
- """Default agent creation method."""
117
- return {}
118
-
119
- def set_starting_agent(self, agent: Agent) -> None:
120
- """Set the starting agent and trigger initial asset discovery."""
121
- agent_name = get_agent_name(agent)
122
- logger.debug(f"Setting starting agent to: {agent_name}")
123
- self.starting_agent = agent
124
- self.context_variables["active_agent_name"] = agent_name
125
-
126
- try:
127
- loop = asyncio.get_event_loop()
128
- is_running = loop.is_running()
129
- except RuntimeError:
130
- loop = None
131
- is_running = False
132
-
133
- # Corrected calls: Pass only agent and config
134
- if loop and is_running:
135
- logger.debug(f"Scheduling async asset discovery for starting agent {agent_name}.")
136
- asyncio.ensure_future(discover_tools_for_agent(agent, self.swarm.config))
137
- asyncio.ensure_future(discover_resources_for_agent(agent, self.swarm.config))
138
- else:
139
- logger.debug(f"Running sync asset discovery for starting agent {agent_name}.")
140
- try:
141
- asyncio.run(discover_tools_for_agent(agent, self.swarm.config))
142
- asyncio.run(discover_resources_for_agent(agent, self.swarm.config))
143
- except RuntimeError as e:
144
- if "cannot be called from a running event loop" in str(e): logger.error("Nested asyncio.run detected during sync discovery.")
145
- else: raise e
146
-
147
- async def determine_active_agent(self) -> Optional[Agent]:
148
- """Determine the currently active agent."""
149
- active_agent_name = self.context_variables.get("active_agent_name")
150
- agent_to_use = None
151
-
152
- if active_agent_name and active_agent_name in self.swarm.agents:
153
- agent_to_use = self.swarm.agents[active_agent_name]
154
- logger.debug(f"Determined active agent from context: {active_agent_name}")
155
- elif self.starting_agent:
156
- agent_to_use = self.starting_agent
157
- active_agent_name = get_agent_name(agent_to_use)
158
- if self.context_variables.get("active_agent_name") != active_agent_name:
159
- self.context_variables["active_agent_name"] = active_agent_name
160
- logger.debug(f"Falling back to starting agent: {active_agent_name} and updating context.")
161
- else:
162
- logger.debug(f"Using starting agent: {active_agent_name}")
163
- else:
164
- logger.error("Cannot determine active agent: No agent name in context and no starting agent set.")
165
- return None
166
-
167
- agent_name_cache_key = get_agent_name(agent_to_use)
168
- # Corrected calls: Pass only agent and config
169
- if agent_name_cache_key not in self._discovered_tools:
170
- logger.debug(f"Cache miss for tools of agent {agent_name_cache_key}. Discovering...")
171
- discovered_tools = await discover_tools_for_agent(agent_to_use, self.swarm.config)
172
- self._discovered_tools[agent_name_cache_key] = discovered_tools
173
-
174
- if agent_name_cache_key not in self._discovered_resources:
175
- logger.debug(f"Cache miss for resources of agent {agent_name_cache_key}. Discovering...")
176
- discovered_resources = await discover_resources_for_agent(agent_to_use, self.swarm.config)
177
- self._discovered_resources[agent_name_cache_key] = discovered_resources
178
-
179
- return agent_to_use
180
-
181
- # --- Core Execution Logic ---
182
- def run_with_context(self, messages: List[Dict[str, str]], context_variables: dict) -> dict:
183
- """Synchronous wrapper for the async execution logic."""
184
- return asyncio.run(self.run_with_context_async(messages, context_variables))
185
-
186
- async def run_with_context_async(self, messages: List[Dict[str, str]], context_variables: dict) -> dict:
187
- """Asynchronously run the blueprint's logic."""
188
- self.context_variables.update(context_variables)
189
- logger.debug(f"Context variables updated: {self.context_variables}")
190
-
191
- active_agent = await self.determine_active_agent()
192
- if not active_agent:
193
- logger.error("No active agent could be determined. Cannot proceed.")
194
- return {"response": Response(messages=[{"role": "assistant", "content": "Error: No active agent available."}], agent=None, context_variables=self.context_variables), "context_variables": self.context_variables}
195
-
196
- model = getattr(active_agent, 'model', None) or self.swarm.current_llm_config.get("model", "default")
197
- logger.debug(f"Using model: {model} for agent {get_agent_name(active_agent)}")
198
-
199
- truncated_messages = self.truncate_message_history(messages, model)
200
- validated_messages = validate_message_sequence(truncated_messages)
201
- repaired_messages = repair_message_payload(validated_messages, debug=self.debug)
202
-
203
- if not self.swarm.agents:
204
- logger.warning("No agents registered; returning default response.")
205
- return {"response": Response(messages=[{"role": "assistant", "content": "No agents available in Swarm."}], agent=None, context_variables=self.context_variables), "context_variables": self.context_variables}
206
-
207
- logger.debug(f"Running Swarm core with agent: {get_agent_name(active_agent)}")
208
- self.spinner.start(f"Generating response from {get_agent_name(active_agent)}")
209
- response_obj = None
210
- try:
211
- response_obj = await self.swarm.run(
212
- agent=active_agent, messages=repaired_messages, context_variables=self.context_variables,
213
- stream=False, debug=self.debug,
214
- )
215
- except Exception as e:
216
- logger.error(f"Swarm run failed: {e}", exc_info=True)
217
- response_obj = Response(messages=[{"role": "assistant", "content": f"An error occurred: {str(e)}"}], agent=active_agent, context_variables=self.context_variables)
218
- finally:
219
- self.spinner.stop()
220
-
221
- final_agent = active_agent
222
- updated_context = self.context_variables.copy()
223
-
224
- if response_obj:
225
- if hasattr(response_obj, 'agent') and response_obj.agent and get_agent_name(response_obj.agent) != get_agent_name(active_agent):
226
- final_agent = response_obj.agent
227
- new_agent_name = get_agent_name(final_agent)
228
- updated_context["active_agent_name"] = new_agent_name
229
- logger.debug(f"Agent handoff occurred. New active agent: {new_agent_name}")
230
- # Corrected calls: Pass only agent and config
231
- asyncio.ensure_future(discover_tools_for_agent(final_agent, self.swarm.config))
232
- asyncio.ensure_future(discover_resources_for_agent(final_agent, self.swarm.config))
233
- if hasattr(response_obj, 'context_variables'):
234
- updated_context.update(response_obj.context_variables)
235
- else:
236
- logger.error("Swarm run returned None or invalid response structure.")
237
- response_obj = Response(messages=[{"role": "assistant", "content": "Error processing the request."}], agent=active_agent, context_variables=updated_context)
238
-
239
- return {"response": response_obj, "context_variables": updated_context}
240
-
241
- def set_active_agent(self, agent_name: str) -> None:
242
- """Explicitly set the active agent by name and trigger asset discovery."""
243
- if agent_name in self.swarm.agents:
244
- self.context_variables["active_agent_name"] = agent_name
245
- agent = self.swarm.agents[agent_name]
246
- logger.debug(f"Explicitly setting active agent to: {agent_name}")
247
- # Corrected calls: Pass only agent and config
248
- if agent_name not in self._discovered_tools:
249
- logger.debug(f"Discovering tools for explicitly set agent {agent_name}.")
250
- try: asyncio.run(discover_tools_for_agent(agent, self.swarm.config))
251
- except RuntimeError as e:
252
- if "cannot be called from a running event loop" in str(e): logger.error("Cannot run sync discovery from within an async context (set_active_agent).")
253
- else: raise e
254
- if agent_name not in self._discovered_resources:
255
- logger.debug(f"Discovering resources for explicitly set agent {agent_name}.")
256
- try: asyncio.run(discover_resources_for_agent(agent, self.swarm.config))
257
- except RuntimeError as e:
258
- if "cannot be called from a running event loop" in str(e): logger.error("Cannot run sync discovery from within an async context (set_active_agent).")
259
- else: raise e
260
- else:
261
- logger.error(f"Attempted to set active agent to '{agent_name}', but agent not found.")
262
-
263
- # --- Task Completion & Goal Update Logic ---
264
- async def _is_task_done_async(self, user_goal: str, conversation_summary: str, last_assistant_message: str) -> bool:
265
- """Check if the task defined by user_goal is complete using an LLM call."""
266
- if not user_goal:
267
- logger.warning("Cannot check task completion: user_goal is empty.")
268
- return False
269
-
270
- system_prompt = os.getenv("TASK_DONE_PROMPT", "You are a completion checker. Respond with ONLY 'YES' or 'NO'.")
271
- user_prompt = os.getenv(
272
- "TASK_DONE_USER_PROMPT",
273
- "User's goal: {user_goal}\nConversation summary: {conversation_summary}\nLast assistant message: {last_assistant_message}\nIs the task fully complete? Answer only YES or NO."
274
- ).format(user_goal=user_goal, conversation_summary=conversation_summary, last_assistant_message=last_assistant_message)
275
-
276
- check_prompt = [{"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}]
277
- client = self.swarm.client
278
- model_to_use = self.swarm.current_llm_config.get("model", self.swarm.model)
279
-
280
- try:
281
- response = await client.chat.completions.create(
282
- model=model_to_use, messages=check_prompt, max_tokens=5, temperature=0
283
- )
284
- if response.choices:
285
- result_content = response.choices[0].message.content.strip().upper()
286
- is_done = result_content.startswith("YES")
287
- logger.debug(f"Task completion check (Goal: '{user_goal}', LLM Raw: '{result_content}'): {is_done}")
288
- return is_done
289
- else:
290
- logger.warning("LLM response for task completion check had no choices.")
291
- return False
292
- except Exception as e:
293
- logger.error(f"Task completion check LLM call failed: {e}", exc_info=True)
294
- return False
295
-
296
- async def _update_user_goal_async(self, messages: List[Dict[str, str]]) -> None:
297
- """Update the 'user_goal' in context_variables based on conversation history using an LLM call."""
298
- if not messages:
299
- logger.debug("Cannot update goal: No messages provided.")
300
- return
301
-
302
- system_prompt = os.getenv(
303
- "UPDATE_GOAL_PROMPT",
304
- "You are an assistant that summarizes the user's primary objective from the conversation. Provide a concise, one-sentence summary."
305
- )
306
- conversation_text = "\n".join(f"{m['role']}: {m.get('content', '')}" for m in messages if m.get('content') or m.get('tool_calls'))
307
- if not conversation_text:
308
- logger.debug("Cannot update goal: No content in messages.")
309
- return
310
-
311
- user_prompt = os.getenv(
312
- "UPDATE_GOAL_USER_PROMPT",
313
- "Summarize the user's main goal based on this conversation:\n{conversation}"
314
- ).format(conversation=conversation_text)
315
-
316
- prompt = [{"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}]
317
- client = self.swarm.client
318
- model_to_use = self.swarm.current_llm_config.get("model", self.swarm.model)
31
+ async def run(self, messages: List[Dict[str, str]]) -> AsyncGenerator[Dict[str, Any], None]:
32
+ """
33
+ Asynchronously runs the blueprint logic with the given messages.
34
+ Must be implemented by subclasses.
35
+ """
36
+ if False: yield {}
37
+ pass
319
38
 
39
+ def __init__(
40
+ self,
41
+ # config_override: Optional[Dict] = None, # Override logic needs rethink
42
+ params: Optional[Dict] = None,
43
+ ):
44
+ logger.debug(f"BlueprintBase.__init__ starting for {self.__class__.__name__}")
45
+ self.params = params if params else {}
46
+ # self._config_override = config_override # Store override for use in _configure
47
+ # self._resolved_config_path = None # Initialize path tracker
48
+
49
+ # Configuration loading needs rethink - access via settings for now
50
+ self._configure() # This sets self.config attributes from settings
51
+
52
+ # Now setup others that might depend on config
53
+ self._setup_llm() # Uses self.config attributes
54
+ self._setup_jinja() # Uses class path
55
+
56
+ # Logging Adjustment
57
+ # Check config *after* it has been loaded by _configure
58
+ # Use self.is_debug which should be set in _configure
59
+ if hasattr(self, 'is_debug') and self.is_debug:
60
+ log_level_int = logging.DEBUG
61
+ if logging.root.level > log_level_int:
62
+ logger.info(f"Root logger level is {logging.getLevelName(logging.root.level)}. Lowering to DEBUG due to config.")
63
+ logging.root.setLevel(log_level_int)
64
+
65
+ # Ensure llm_profile_name is set by _setup_llm before logging it
66
+ # Add check if llm_profile_name exists before logging
67
+ profile_name_to_log = getattr(self, 'llm_profile_name', 'N/A')
68
+ logger.info(f"Initialized blueprint '{self.metadata.get('name', self.__class__.__name__)}' with profile '{profile_name_to_log}'")
69
+
70
+ def _configure(self):
71
+ """Placeholder: Sets config attributes based on django.conf.settings."""
72
+ logger.debug("BlueprintBase._configure accessing django.conf.settings")
73
+ # *** Access settings directly (temporary) ***
74
+ # This assumes swarm_config.json content is NOT yet loaded into settings
75
+ # We'll rely on defaults or potentially fail in _setup_llm
76
+ self.is_debug = getattr(settings, 'DEBUG', False)
77
+ # Store blueprint-specific parts for convenience - these won't exist in settings yet!
78
+ self.blueprint_config = {} # Placeholder
79
+ self.blueprint_defaults = {} # Placeholder
80
+ logger.warning("_configure is using placeholders. Swarm config file content is not loaded here.")
81
+
82
+
83
+ def _setup_llm(self):
84
+ """Sets up the LLM provider based on configuration stored in self.config."""
85
+ # *** Access settings directly (temporary) ***
86
+ # Get default profile name from settings if defined, else 'default'
87
+ default_profile = getattr(settings, 'DEFAULT_LLM_PROFILE', 'default')
88
+ self.llm_profile_name = self.blueprint_config.get('llm_profile', default_profile) # Use placeholder blueprint_config
89
+ logger.debug(f"Getting LLM profile details for '{self.llm_profile_name}'.")
90
+
91
+ # *** This part will likely fail or use defaults as swarm_config isn't in settings ***
92
+ # Attempt to get profiles from settings if they were somehow loaded there
93
+ all_llm_profiles = getattr(settings, 'LLM_PROFILES', {})
94
+ profile_data = all_llm_profiles.get(self.llm_profile_name)
95
+
96
+ if profile_data is None:
97
+ logger.warning(f"LLM profile '{self.llm_profile_name}' not found in django settings. LLM will not be available.")
98
+ self.llm_profile = {} # Set empty profile
99
+ self.llm = None # Set LLM to None
100
+ return # Exit setup early
101
+
102
+ logger.debug(f"Using LLM profile '{self.llm_profile_name}' from settings.")
103
+ self.llm_profile = profile_data
104
+ # *** LLMRegistry doesn't exist yet - Comment out usage and set placeholder ***
105
+ # self.llm = LLMRegistry.get_llm(self.llm_profile)
106
+ self.llm = None # Placeholder until registry is implemented
107
+ logger.warning("LLMRegistry not implemented. self.llm set to None.")
108
+
109
+
110
+ @staticmethod
111
+ def _substitute_env_vars(data: Any) -> Any:
112
+ """Recursively substitutes environment variables in strings."""
113
+ if isinstance(data, dict):
114
+ return {k: BlueprintBase._substitute_env_vars(v) for k, v in data.items()}
115
+ if isinstance(data, list):
116
+ return [BlueprintBase._substitute_env_vars(item) for item in data]
117
+ if isinstance(data, str):
118
+ return os.path.expandvars(data)
119
+ return data
120
+
121
+ def _setup_jinja(self):
122
+ """Sets up Jinja2 environment."""
320
123
  try:
321
- response = await client.chat.completions.create(
322
- model=model_to_use, messages=prompt, max_tokens=60, temperature=0.3
323
- )
324
- if response.choices:
325
- new_goal = response.choices[0].message.content.strip()
326
- if new_goal:
327
- self.context_variables["user_goal"] = new_goal
328
- logger.debug(f"Updated user goal via LLM: {new_goal}")
329
- else:
330
- logger.warning("LLM goal update returned empty response.")
124
+ blueprint_file_path = inspect.getfile(self.__class__)
125
+ blueprint_dir = os.path.dirname(blueprint_file_path)
126
+ template_dir = os.path.join(blueprint_dir, 'templates')
127
+ if os.path.isdir(template_dir):
128
+ logger.debug(f"Setting up Jinja env with loader at: {template_dir}")
129
+ self.jinja_env = Environment(
130
+ loader=FileSystemLoader(template_dir),
131
+ autoescape=select_autoescape(['html', 'xml'])
132
+ )
331
133
  else:
332
- logger.warning("LLM response for goal update had no choices.")
134
+ logger.debug(f"No 'templates' directory found for blueprint at {template_dir}. Jinja env not created.")
135
+ self.jinja_env = None
333
136
  except Exception as e:
334
- logger.error(f"User goal update LLM call failed: {e}", exc_info=True)
335
-
336
- def task_completed(self, outcome: str) -> None:
337
- """Placeholder method potentially used by agents to signal task completion."""
338
- print(f"Task Outcome: {outcome}")
339
- print("continue")
340
-
341
- @property
342
- def prompt(self) -> str:
343
- """Return the custom prompt string, potentially from the active agent."""
344
- active_agent = self.swarm.agents.get(self.context_variables.get("active_agent_name"))
345
- return getattr(active_agent, 'prompt', getattr(self, "custom_user_prompt", "User: "))
346
-
347
- # --- Interactive & Non-Interactive Modes ---
348
- def interactive_mode(self, stream: bool = False) -> None:
349
- """Run the blueprint in interactive command-line mode."""
350
- from .interactive_mode import run_interactive_mode
351
- run_interactive_mode(self, stream)
352
-
353
- def non_interactive_mode(self, instruction: str, stream: bool = False) -> None:
354
- """Run the blueprint non-interactively with a single instruction."""
355
- logger.debug(f"Starting non-interactive mode with instruction: {instruction}, stream={stream}")
356
- try:
357
- asyncio.run(self.non_interactive_mode_async(instruction, stream=stream))
358
- except RuntimeError as e:
359
- if "cannot be called from a running event loop" in str(e):
360
- logger.error("Cannot start non_interactive_mode with asyncio.run from an existing event loop.")
361
- else: raise e
362
-
363
- async def non_interactive_mode_async(self, instruction: str, stream: bool = False) -> None:
364
- """Asynchronously run the blueprint non-interactively."""
365
- logger.debug(f"Starting async non-interactive mode with instruction: {instruction}, stream={stream}")
366
- if not self.swarm:
367
- logger.error("Swarm instance not initialized.")
368
- print("Error: Swarm framework not ready.")
369
- return
370
-
371
- print(f"--- {self.metadata.get('title', 'Blueprint')} Non-Interactive Mode ---")
372
- instructions = [line.strip() for line in instruction.splitlines() if line.strip()]
373
- if not instructions:
374
- print("No valid instruction provided.")
375
- return
376
- messages = [{"role": "user", "content": line} for line in instructions]
377
-
378
- if not self.starting_agent:
379
- if self.swarm.agents:
380
- first_agent_name = next(iter(self.swarm.agents.keys()))
381
- logger.warning(f"No starting agent explicitly set. Defaulting to first agent: {first_agent_name}")
382
- self.set_starting_agent(self.swarm.agents[first_agent_name])
383
- else:
384
- logger.error("No starting agent set and no agents defined.")
385
- print("Error: No agent available to handle the instruction.")
386
- return
387
-
388
- self.context_variables["user_goal"] = instruction
389
- self.context_variables["active_agent_name"] = get_agent_name(self.starting_agent)
390
-
391
- if stream:
392
- logger.debug("Running non-interactive in streaming mode.")
393
- response_generator = self.swarm.run(
394
- agent=self.starting_agent, messages=messages, context_variables=self.context_variables,
395
- stream=True, debug=self.debug,
396
- )
397
- final_response_data = await self._process_and_print_streaming_response_async(response_generator)
398
- if self.auto_complete_task:
399
- logger.warning("Auto-completion is not fully supported with streaming in non-interactive mode.")
400
- else:
401
- logger.debug("Running non-interactive in non-streaming mode.")
402
- result = await self.run_with_context_async(messages, self.context_variables)
403
- swarm_response = result.get("response")
404
- self.context_variables = result.get("context_variables", self.context_variables)
405
-
406
- response_messages = []
407
- if hasattr(swarm_response, 'messages'): response_messages = swarm_response.messages
408
- elif isinstance(swarm_response, dict) and 'messages' in swarm_response: response_messages = swarm_response.get('messages', [])
409
-
410
- self._pretty_print_response(response_messages)
411
- if self.auto_complete_task and self.swarm.agents:
412
- logger.debug("Starting auto-completion task.")
413
- current_history = messages + response_messages
414
- await self._auto_complete_task_async(current_history, stream=False)
415
-
416
- print("--- Execution Completed ---")
417
-
418
- async def _process_and_print_streaming_response_async(self, response_generator):
419
- """Async helper to process and print streaming response chunks."""
420
- content = ""
421
- last_sender = self.context_variables.get("active_agent_name", "Assistant")
422
- final_response_chunk_data = None
137
+ logger.warning(f"Could not determine template path for {self.__class__.__name__}: {e}. Jinja env not created.")
138
+ self.jinja_env = None
139
+
140
+ def render_prompt(self, template_name: str, context: Dict = None) -> str:
141
+ """Renders a Jinja2 template."""
142
+ if not self.jinja_env:
143
+ raise RuntimeError(f"Jinja environment not set up for blueprint {self.__class__.__name__}.")
144
+ if context is None:
145
+ context = {}
423
146
  try:
424
- async for chunk in response_generator:
425
- if isinstance(chunk, dict) and "delim" in chunk:
426
- if chunk["delim"] == "start" and not content:
427
- print(f"\033[94m{last_sender}\033[0m: ", end="", flush=True)
428
- elif chunk["delim"] == "end" and content:
429
- print()
430
- content = ""
431
- elif hasattr(chunk, 'choices') and chunk.choices:
432
- delta = chunk.choices[0].delta
433
- if delta and delta.content:
434
- print(delta.content, end="", flush=True)
435
- content += delta.content
436
- elif isinstance(chunk, dict) and "response" in chunk:
437
- final_response_chunk_data = chunk["response"]
438
- if hasattr(final_response_chunk_data, 'agent'):
439
- last_sender = get_agent_name(final_response_chunk_data.agent)
440
- if hasattr(final_response_chunk_data, 'context_variables'):
441
- self.context_variables.update(final_response_chunk_data.context_variables)
442
- logger.debug("Received final aggregated response chunk in stream.")
443
- elif isinstance(chunk, dict) and "error" in chunk:
444
- logger.error(f"Error received during stream: {chunk['error']}")
445
- print(f"\n[Stream Error: {chunk['error']}]")
147
+ template = self.jinja_env.get_template(template_name)
148
+ full_context = {**self.params, **context}
149
+ return template.render(full_context)
446
150
  except Exception as e:
447
- logger.error(f"Error processing stream: {e}", exc_info=True)
448
- print("\n[Error during streaming output]")
449
- finally:
450
- if content: print()
451
- return final_response_chunk_data
452
-
453
- async def _auto_complete_task_async(self, current_history: List[Dict[str, str]], stream: bool) -> None:
454
- """Async helper for task auto-completion loop (non-streaming)."""
455
- max_auto_turns = 10
456
- auto_turn = 0
457
- while auto_turn < max_auto_turns:
458
- auto_turn += 1
459
- logger.debug(f"Auto-completion Turn: {auto_turn}/{max_auto_turns}")
460
- conversation_summary = " ".join(m.get("content", "") for m in current_history[-4:] if m.get("content"))
461
- last_assistant_msg = next((m.get("content", "") for m in reversed(current_history) if m.get("role") == "assistant" and m.get("content")), "")
462
- user_goal = self.context_variables.get("user_goal", "")
463
-
464
- # Call the renamed async method
465
- if await self._is_task_done_async(user_goal, conversation_summary, last_assistant_msg):
466
- print("\033[93m[System]\033[0m: Task detected as complete.")
467
- break
468
-
469
- logger.debug("Task not complete, running next auto-completion turn.")
470
- result = await self.run_with_context_async(current_history, self.context_variables)
471
- swarm_response = result.get("response")
472
- self.context_variables = result.get("context_variables", self.context_variables)
473
-
474
- new_messages = []
475
- if hasattr(swarm_response, 'messages'): new_messages = swarm_response.messages
476
- elif isinstance(swarm_response, dict) and 'messages' in swarm_response: new_messages = swarm_response.get('messages', [])
477
-
478
- if not new_messages:
479
- logger.warning("Auto-completion turn yielded no new messages. Stopping.")
480
- break
481
-
482
- self._pretty_print_response(new_messages)
483
- current_history.extend(new_messages)
484
-
485
- if auto_turn >= max_auto_turns:
486
- logger.warning("Auto-completion reached maximum turns limit.")
487
- print("\033[93m[System]\033[0m: Reached max auto-completion turns.")
151
+ logger.error(f"Error rendering template '{template_name}': {e}", exc_info=True)
152
+ raise
488
153
 
489
- def _auto_complete_task(self, messages: List[Dict[str, str]], stream: bool) -> None:
490
- """Synchronous wrapper for task auto-completion."""
491
- if stream:
492
- logger.warning("Auto-completion skipped because streaming is enabled.")
493
- return
494
- logger.debug("Starting synchronous auto-completion task.")
495
- try:
496
- asyncio.run(self._auto_complete_task_async(messages, stream=False))
497
- except RuntimeError as e:
498
- if "cannot be called from a running event loop" in str(e):
499
- logger.error("Cannot start _auto_complete_task with asyncio.run from an existing event loop.")
500
- else: raise e
501
-
502
- # --- Class Method for Entry Point ---
503
- @classmethod
504
- def main(cls):
505
- parser = argparse.ArgumentParser(description=f"Run the {cls.__name__} blueprint.")
506
- parser.add_argument("--config", default="./swarm_config.json", help="Path to the swarm_config.json file.")
507
- parser.add_argument("--instruction", help="Single instruction for non-interactive mode.")
508
- parser.add_argument("--stream", action="store_true", help="Enable streaming output in non-interactive mode.")
509
- parser.add_argument("--auto-complete-task", action="store_true", help="Enable task auto-completion in non-interactive mode.")
510
- parser.add_argument("--update-user-goal", action="store_true", help="Enable dynamic goal updates using LLM.")
511
- parser.add_argument("--update-user-goal-frequency", type=int, default=5, help="Frequency (in messages) for updating user goal.")
512
- parser.add_argument("--log-file-path", help="Path for logging output (default: ~/.swarm/logs/<blueprint_name>.log).")
513
- parser.add_argument("--debug", action="store_true", help="Enable debug logging to console instead of file.")
514
- parser.add_argument("--use-markdown", action="store_true", help="Enable markdown rendering for assistant responses.")
515
- args = parser.parse_args()
516
-
517
- root_logger = logging.getLogger()
518
- log_level = logging.DEBUG if args.debug or DEBUG else logging.INFO
519
- root_logger.setLevel(log_level)
520
-
521
- if root_logger.hasHandlers(): root_logger.handlers.clear()
522
-
523
- log_formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(name)s:%(lineno)d - %(message)s")
524
- log_handler = logging.StreamHandler(sys.stdout) if args.debug else logging.FileHandler(
525
- Path(args.log_file_path or Path.home() / ".swarm" / "logs" / f"{cls.__name__.lower()}.log").resolve(), mode='a'
526
- )
527
- log_handler.setFormatter(log_formatter)
528
- root_logger.addHandler(log_handler)
529
- logger.info(f"Logging initialized. Level: {logging.getLevelName(log_level)}. Destination: {getattr(log_handler, 'baseFilename', 'console')}")
530
-
531
- original_stderr = sys.stderr
532
- dev_null = None
533
- if not args.debug:
534
- try:
535
- dev_null = open(os.devnull, "w")
536
- sys.stderr = dev_null
537
- logger.info(f"Redirected stderr to {os.devnull}")
538
- except OSError as e: logger.warning(f"Could not redirect stderr: {e}")
539
-
540
- try:
541
- config_data = load_server_config(args.config)
542
- blueprint_instance = cls(
543
- config=config_data, auto_complete_task=args.auto_complete_task, update_user_goal=args.update_user_goal,
544
- update_user_goal_frequency=args.update_user_goal_frequency, log_file_path=str(getattr(log_handler, 'baseFilename', None)),
545
- debug=args.debug, use_markdown=args.use_markdown, non_interactive=bool(args.instruction)
546
- )
547
- if args.instruction:
548
- asyncio.run(blueprint_instance.non_interactive_mode_async(args.instruction, stream=args.stream))
549
- else:
550
- blueprint_instance.interactive_mode(stream=args.stream)
551
- except Exception as e:
552
- logger.critical(f"Blueprint execution failed: {e}", exc_info=True)
553
- print(f"Critical Error: {e}", file=original_stderr)
554
- finally:
555
- if not args.debug and dev_null is not None:
556
- sys.stderr = original_stderr
557
- dev_null.close()
558
- logger.debug("Restored stderr.")
559
- logger.info("Blueprint execution finished.")
154
+ def get_llm_profile(self) -> Dict:
155
+ """Returns the configuration for the LLM profile being used."""
156
+ if not hasattr(self, 'llm_profile'):
157
+ logger.warning("LLM profile was not set during initialization, returning empty dict.")
158
+ return {}
159
+ return self.llm_profile
560
160
 
561
- if __name__ == "__main__":
562
- BlueprintBase.main()