open-swarm 0.1.1743070217__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 (89) hide show
  1. open_swarm-0.1.1743070217.dist-info/METADATA +258 -0
  2. open_swarm-0.1.1743070217.dist-info/RECORD +89 -0
  3. open_swarm-0.1.1743070217.dist-info/WHEEL +5 -0
  4. open_swarm-0.1.1743070217.dist-info/entry_points.txt +3 -0
  5. open_swarm-0.1.1743070217.dist-info/licenses/LICENSE +21 -0
  6. open_swarm-0.1.1743070217.dist-info/top_level.txt +1 -0
  7. swarm/__init__.py +3 -0
  8. swarm/agent/__init__.py +7 -0
  9. swarm/agent/agent.py +49 -0
  10. swarm/apps.py +53 -0
  11. swarm/auth.py +56 -0
  12. swarm/consumers.py +141 -0
  13. swarm/core.py +326 -0
  14. swarm/extensions/__init__.py +1 -0
  15. swarm/extensions/blueprint/__init__.py +36 -0
  16. swarm/extensions/blueprint/agent_utils.py +45 -0
  17. swarm/extensions/blueprint/blueprint_base.py +562 -0
  18. swarm/extensions/blueprint/blueprint_discovery.py +112 -0
  19. swarm/extensions/blueprint/blueprint_utils.py +17 -0
  20. swarm/extensions/blueprint/common_utils.py +12 -0
  21. swarm/extensions/blueprint/django_utils.py +203 -0
  22. swarm/extensions/blueprint/interactive_mode.py +102 -0
  23. swarm/extensions/blueprint/modes/rest_mode.py +37 -0
  24. swarm/extensions/blueprint/output_utils.py +95 -0
  25. swarm/extensions/blueprint/spinner.py +91 -0
  26. swarm/extensions/cli/__init__.py +0 -0
  27. swarm/extensions/cli/blueprint_runner.py +251 -0
  28. swarm/extensions/cli/cli_args.py +88 -0
  29. swarm/extensions/cli/commands/__init__.py +0 -0
  30. swarm/extensions/cli/commands/blueprint_management.py +31 -0
  31. swarm/extensions/cli/commands/config_management.py +15 -0
  32. swarm/extensions/cli/commands/edit_config.py +77 -0
  33. swarm/extensions/cli/commands/list_blueprints.py +22 -0
  34. swarm/extensions/cli/commands/validate_env.py +57 -0
  35. swarm/extensions/cli/commands/validate_envvars.py +39 -0
  36. swarm/extensions/cli/interactive_shell.py +41 -0
  37. swarm/extensions/cli/main.py +36 -0
  38. swarm/extensions/cli/selection.py +43 -0
  39. swarm/extensions/cli/utils/discover_commands.py +32 -0
  40. swarm/extensions/cli/utils/env_setup.py +15 -0
  41. swarm/extensions/cli/utils.py +105 -0
  42. swarm/extensions/config/__init__.py +6 -0
  43. swarm/extensions/config/config_loader.py +208 -0
  44. swarm/extensions/config/config_manager.py +258 -0
  45. swarm/extensions/config/server_config.py +49 -0
  46. swarm/extensions/config/setup_wizard.py +103 -0
  47. swarm/extensions/config/utils/__init__.py +0 -0
  48. swarm/extensions/config/utils/logger.py +36 -0
  49. swarm/extensions/launchers/__init__.py +1 -0
  50. swarm/extensions/launchers/build_launchers.py +14 -0
  51. swarm/extensions/launchers/build_swarm_wrapper.py +12 -0
  52. swarm/extensions/launchers/swarm_api.py +68 -0
  53. swarm/extensions/launchers/swarm_cli.py +304 -0
  54. swarm/extensions/launchers/swarm_wrapper.py +29 -0
  55. swarm/extensions/mcp/__init__.py +1 -0
  56. swarm/extensions/mcp/cache_utils.py +36 -0
  57. swarm/extensions/mcp/mcp_client.py +341 -0
  58. swarm/extensions/mcp/mcp_constants.py +7 -0
  59. swarm/extensions/mcp/mcp_tool_provider.py +110 -0
  60. swarm/llm/chat_completion.py +195 -0
  61. swarm/messages.py +132 -0
  62. swarm/migrations/0010_initial_chat_models.py +51 -0
  63. swarm/migrations/__init__.py +0 -0
  64. swarm/models.py +45 -0
  65. swarm/repl/__init__.py +1 -0
  66. swarm/repl/repl.py +87 -0
  67. swarm/serializers.py +12 -0
  68. swarm/settings.py +189 -0
  69. swarm/tool_executor.py +239 -0
  70. swarm/types.py +126 -0
  71. swarm/urls.py +89 -0
  72. swarm/util.py +124 -0
  73. swarm/utils/color_utils.py +40 -0
  74. swarm/utils/context_utils.py +272 -0
  75. swarm/utils/general_utils.py +162 -0
  76. swarm/utils/logger.py +61 -0
  77. swarm/utils/logger_setup.py +25 -0
  78. swarm/utils/message_sequence.py +173 -0
  79. swarm/utils/message_utils.py +95 -0
  80. swarm/utils/redact.py +68 -0
  81. swarm/views/__init__.py +41 -0
  82. swarm/views/api_views.py +46 -0
  83. swarm/views/chat_views.py +76 -0
  84. swarm/views/core_views.py +118 -0
  85. swarm/views/message_views.py +40 -0
  86. swarm/views/model_views.py +135 -0
  87. swarm/views/utils.py +457 -0
  88. swarm/views/web_views.py +149 -0
  89. swarm/wsgi.py +16 -0
swarm/messages.py ADDED
@@ -0,0 +1,132 @@
1
+ """
2
+ Message handling utilities for the Swarm framework.
3
+ Defines the ChatMessage structure.
4
+ """
5
+
6
+ import json
7
+ import logging
8
+ from types import SimpleNamespace
9
+ from typing import Optional, List, Dict, Any, Union
10
+
11
+ # Import the specific Pydantic model used for tool calls
12
+ from .types import ChatCompletionMessageToolCall
13
+
14
+ # Configure module-level logging
15
+ logger = logging.getLogger(__name__)
16
+ # logger.setLevel(logging.DEBUG) # Uncomment for detailed message logging
17
+ if not logger.handlers:
18
+ stream_handler = logging.StreamHandler()
19
+ formatter = logging.Formatter("[%(levelname)s] %(asctime)s - %(name)s:%(lineno)d - %(message)s")
20
+ stream_handler.setFormatter(formatter)
21
+ logger.addHandler(stream_handler)
22
+
23
+
24
+ class ChatMessage(SimpleNamespace):
25
+ """
26
+ Represents a chat message within the Swarm framework, aiming for compatibility
27
+ with OpenAI's chat completion message structure while allowing custom fields.
28
+
29
+ Attributes:
30
+ role (str): The role of the message sender (e.g., "system", "user", "assistant", "tool").
31
+ content (Optional[str]): The text content of the message. Can be None for assistant messages
32
+ requesting tool calls without initial text.
33
+ sender (str): Custom field identifying the specific agent or source (not sent to LLM API).
34
+ function_call (Optional[dict]): Deprecated field for legacy function calls.
35
+ tool_calls (Optional[List[ChatCompletionMessageToolCall]]): A list of tool calls requested
36
+ by the assistant.
37
+ tool_call_id (Optional[str]): Identifier for a tool response message, linking it to a specific
38
+ tool_call requested by the assistant.
39
+ name (Optional[str]): The name of the tool that was called (used in 'tool' role messages).
40
+ """
41
+
42
+ def __init__(self, **kwargs):
43
+ """
44
+ Initialize a ChatMessage instance.
45
+
46
+ Args:
47
+ **kwargs: Arbitrary keyword arguments mapping to message attributes.
48
+ Defaults are provided for common fields.
49
+ """
50
+ # Define default values for standard message attributes
51
+ defaults = {
52
+ "role": "assistant", # Default role
53
+ "content": None, # Default content to None (as per OpenAI spec for tool calls)
54
+ "sender": "assistant", # Default custom sender
55
+ "function_call": None, # Deprecated field
56
+ "tool_calls": None, # For assistant requests
57
+ "tool_call_id": None, # For tool responses
58
+ "name": None # For tool responses (function name)
59
+ }
60
+ # Merge provided kwargs with defaults
61
+ merged_attrs = defaults | kwargs
62
+ super().__init__(**merged_attrs)
63
+
64
+ # --- Validation and Type Conversion for tool_calls ---
65
+ # Ensure tool_calls, if present, is a list
66
+ if self.tool_calls is not None and not isinstance(self.tool_calls, list):
67
+ logger.warning(f"ChatMessage 'tool_calls' received non-list type ({type(self.tool_calls)}) for sender '{self.sender}'. Resetting to None.")
68
+ self.tool_calls = None
69
+ elif isinstance(self.tool_calls, list):
70
+ # Convert dictionary items in the list to ChatCompletionMessageToolCall objects
71
+ validated_tool_calls = []
72
+ for i, tc in enumerate(self.tool_calls):
73
+ if isinstance(tc, ChatCompletionMessageToolCall):
74
+ validated_tool_calls.append(tc)
75
+ elif isinstance(tc, dict):
76
+ try:
77
+ # Attempt to instantiate the Pydantic model
78
+ validated_tool_calls.append(ChatCompletionMessageToolCall(**tc))
79
+ except Exception as e: # Catch potential validation errors from Pydantic
80
+ logger.warning(f"Failed to convert dict to ChatCompletionMessageToolCall at index {i} for sender '{self.sender}': {e}. Skipping this tool call.")
81
+ logger.debug(f"Invalid tool call dict: {tc}")
82
+ else:
83
+ logger.warning(f"Invalid item type in 'tool_calls' list at index {i} ({type(tc)}) for sender '{self.sender}'. Skipping.")
84
+ self.tool_calls = validated_tool_calls if validated_tool_calls else None # Set back validated list or None if empty/all invalid
85
+ if self.tool_calls:
86
+ logger.debug(f"Validated {len(self.tool_calls)} tool calls for ChatMessage (sender: '{self.sender}')")
87
+
88
+
89
+ def model_dump(self) -> Dict[str, Any]:
90
+ """
91
+ Serialize the message attributes relevant for the OpenAI API into a dictionary.
92
+ Excludes custom fields like 'sender'. Handles optional fields correctly.
93
+ """
94
+ # Start with required 'role'
95
+ d: Dict[str, Any] = {"role": self.role}
96
+
97
+ # Include 'content' only if it's not None
98
+ # OpenAI API allows null content for assistant messages with tool_calls
99
+ if self.content is not None:
100
+ d["content"] = self.content
101
+
102
+ # Include 'tool_calls' if present and not empty
103
+ if self.tool_calls: # Checks for non-None and non-empty list after validation
104
+ # Ensure each item is dumped correctly using its own model_dump if available
105
+ d["tool_calls"] = [tc.model_dump() if hasattr(tc, 'model_dump') else tc for tc in self.tool_calls]
106
+
107
+ # Include 'tool_call_id' if present (for 'tool' role messages)
108
+ if self.tool_call_id is not None:
109
+ d["tool_call_id"] = self.tool_call_id
110
+
111
+ # Include 'name' if present (for 'tool' role messages, matches function name)
112
+ if self.name is not None:
113
+ d["name"] = self.name
114
+
115
+ # Include deprecated 'function_call' if present
116
+ if self.function_call is not None:
117
+ d["function_call"] = self.function_call
118
+
119
+ return d
120
+
121
+
122
+ def model_dump_json(self) -> str:
123
+ """Serialize the message to a JSON string suitable for the OpenAI API."""
124
+ # Use the model_dump method to get the correct dictionary structure first
125
+ api_dict = self.model_dump()
126
+ # Serialize the dictionary to JSON
127
+ try:
128
+ return json.dumps(api_dict)
129
+ except TypeError as e:
130
+ logger.error(f"Failed to serialize ChatMessage to JSON for sender '{self.sender}': {e}")
131
+ # Fallback: return a basic JSON representation on error
132
+ return json.dumps({"role": self.role, "content": f"[Serialization Error: {e}]"})
@@ -0,0 +1,51 @@
1
+ # Generated manually on 2025-02-24 to establish core Swarm chat models
2
+
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+ from django.conf import settings
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ initial = True
10
+
11
+ dependencies = []
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name='ChatConversation',
16
+ fields=[
17
+ ('conversation_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
18
+ ('created_at', models.DateTimeField(auto_now_add=True)),
19
+ ('student', models.ForeignKey(
20
+ blank=True,
21
+ null=True,
22
+ on_delete=django.db.models.deletion.CASCADE,
23
+ to=settings.AUTH_USER_MODEL,
24
+ )),
25
+ ],
26
+ options={
27
+ 'verbose_name': 'Chat Conversation',
28
+ 'verbose_name_plural': 'Chat Conversations',
29
+ },
30
+ ),
31
+ migrations.CreateModel(
32
+ name='ChatMessage',
33
+ fields=[
34
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
35
+ ('sender', models.CharField(max_length=50)),
36
+ ('content', models.TextField()),
37
+ ('timestamp', models.DateTimeField(auto_now_add=True)),
38
+ ('tool_call_id', models.CharField(blank=True, max_length=255, null=True)),
39
+ ('conversation', models.ForeignKey(
40
+ on_delete=django.db.models.deletion.CASCADE,
41
+ related_name='chat_messages',
42
+ to='swarm.chatconversation',
43
+ )),
44
+ ],
45
+ options={
46
+ 'ordering': ['timestamp'],
47
+ 'verbose_name': 'Chat Message',
48
+ 'verbose_name_plural': 'Chat Messages',
49
+ },
50
+ ),
51
+ ]
File without changes
swarm/models.py ADDED
@@ -0,0 +1,45 @@
1
+ from django.db import models
2
+
3
+ class ChatConversation(models.Model):
4
+ """Represents a single chat session."""
5
+ conversation_id = models.CharField(max_length=255, primary_key=True)
6
+ created_at = models.DateTimeField(auto_now_add=True)
7
+ student = models.ForeignKey("auth.User", on_delete=models.CASCADE, blank=True, null=True)
8
+
9
+ class Meta:
10
+ app_label = "swarm"
11
+ verbose_name = "Chat Conversation"
12
+ verbose_name_plural = "Chat Conversations"
13
+
14
+ def __str__(self):
15
+ return f"ChatConversation({self.conversation_id})"
16
+
17
+ @property
18
+ def messages(self):
19
+ return self.chat_messages.all()
20
+
21
+ class ChatMessage(models.Model):
22
+ """Stores individual chat messages within a conversation."""
23
+ conversation = models.ForeignKey(ChatConversation, related_name="chat_messages", on_delete=models.CASCADE)
24
+ sender = models.CharField(max_length=50)
25
+ content = models.TextField()
26
+ timestamp = models.DateTimeField(auto_now_add=True)
27
+ tool_call_id = models.CharField(max_length=255, blank=True, null=True)
28
+
29
+ class Meta:
30
+ ordering = ["timestamp"]
31
+ verbose_name = "Chat Message"
32
+ verbose_name_plural = "Chat Messages"
33
+
34
+ def __str__(self):
35
+ return self.content[:50]
36
+
37
+ __all__ = [
38
+ "ChatConversation",
39
+ "ChatMessage",
40
+ ]
41
+
42
+ # Alias the module to prevent conflicting model registrations.
43
+ import sys
44
+ sys.modules["swarm.models"] = sys.modules[__name__]
45
+ sys.modules["src.swarm.models"] = sys.modules["swarm.models"]
swarm/repl/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from .repl import run_demo_loop
swarm/repl/repl.py ADDED
@@ -0,0 +1,87 @@
1
+ import json
2
+
3
+ from swarm import Swarm
4
+
5
+
6
+ def process_and_print_streaming_response(response):
7
+ content = ""
8
+ last_sender = ""
9
+
10
+ for chunk in response:
11
+ if "sender" in chunk:
12
+ last_sender = chunk["sender"]
13
+
14
+ if "content" in chunk and chunk["content"] is not None:
15
+ if not content and last_sender:
16
+ print(f"\033[94m{last_sender}:\033[0m", end=" ", flush=True)
17
+ last_sender = ""
18
+ print(chunk["content"], end="", flush=True)
19
+ content += chunk["content"]
20
+
21
+ if "tool_calls" in chunk and chunk["tool_calls"] is not None:
22
+ for tool_call in chunk["tool_calls"]:
23
+ f = tool_call["function"]
24
+ name = f["name"]
25
+ if not name:
26
+ continue
27
+ print(f"\033[94m{last_sender}: \033[95m{name}\033[0m()")
28
+
29
+ if "delim" in chunk and chunk["delim"] == "end" and content:
30
+ print() # End of response message
31
+ content = ""
32
+
33
+ if "response" in chunk:
34
+ return chunk["response"]
35
+
36
+
37
+ def pretty_print_messages(messages) -> None:
38
+ for message in messages:
39
+ if message["role"] != "assistant":
40
+ continue
41
+
42
+ # print agent name in blue
43
+ print(f"\033[94m{message['sender']}\033[0m:", end=" ")
44
+
45
+ # print response, if any
46
+ if message["content"]:
47
+ print(message["content"])
48
+
49
+ # print tool calls in purple, if any
50
+ tool_calls = message.get("tool_calls") or []
51
+ if len(tool_calls) > 1:
52
+ print()
53
+ for tool_call in tool_calls:
54
+ f = tool_call["function"]
55
+ name, args = f["name"], f["arguments"]
56
+ arg_str = json.dumps(json.loads(args)).replace(":", "=")
57
+ print(f"\033[95m{name}\033[0m({arg_str[1:-1]})")
58
+
59
+
60
+ def run_demo_loop(
61
+ starting_agent, context_variables=None, stream=False, debug=False
62
+ ) -> None:
63
+ client = Swarm()
64
+ print("Starting Swarm CLI 🐝")
65
+
66
+ messages = []
67
+ agent = starting_agent
68
+
69
+ while True:
70
+ user_input = input("\033[90mUser\033[0m: ")
71
+ messages.append({"role": "user", "content": user_input})
72
+
73
+ response = client.run(
74
+ agent=agent,
75
+ messages=messages,
76
+ context_variables=context_variables or {},
77
+ stream=stream,
78
+ debug=debug,
79
+ )
80
+
81
+ if stream:
82
+ response = process_and_print_streaming_response(response)
83
+ else:
84
+ pretty_print_messages(response.messages)
85
+
86
+ messages.extend(response.messages)
87
+ agent = response.agent
swarm/serializers.py ADDED
@@ -0,0 +1,12 @@
1
+ from rest_framework import serializers
2
+ from swarm.models import ChatMessage, ChatConversation
3
+
4
+ class ChatMessageSerializer(serializers.ModelSerializer):
5
+ class Meta:
6
+ model = ChatMessage
7
+ fields = '__all__'
8
+
9
+ class ChatConversationSerializer(serializers.ModelSerializer):
10
+ class Meta:
11
+ model = ChatConversation
12
+ fields = '__all__'
swarm/settings.py ADDED
@@ -0,0 +1,189 @@
1
+ """
2
+ Django settings for swarm project.
3
+ Includes Pydantic base settings for Swarm Core.
4
+ """
5
+
6
+ import logging
7
+ import os
8
+ import sys
9
+ from enum import Enum
10
+ from pathlib import Path
11
+ from pydantic import Field # Import Field from Pydantic v2+
12
+ from pydantic_settings import BaseSettings, SettingsConfigDict
13
+
14
+ # --- Pydantic Settings for Swarm Core ---
15
+ class LogFormat(str, Enum):
16
+ standard = "[%(levelname)s] %(asctime)s - %(name)s:%(lineno)d - %(message)s"
17
+ simple = "[%(levelname)s] %(name)s - %(message)s"
18
+
19
+ class Settings(BaseSettings):
20
+ model_config = SettingsConfigDict(env_prefix='SWARM_', case_sensitive=False)
21
+
22
+ log_level: str = Field(default='INFO', description="Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)")
23
+ log_format: LogFormat = Field(default=LogFormat.standard, description="Logging format")
24
+ debug: bool = Field(default=False, description="Global debug flag")
25
+
26
+ # --- Standard Django Settings ---
27
+
28
+ # Build paths inside the project like this: BASE_DIR / 'subdir'.
29
+ BASE_DIR = Path(__file__).resolve().parent.parent
30
+ PROJECT_ROOT = BASE_DIR.parent
31
+ if str(PROJECT_ROOT) not in sys.path:
32
+ sys.path.insert(0, str(PROJECT_ROOT))
33
+
34
+ BLUEPRINTS_DIR = PROJECT_ROOT / 'blueprints'
35
+
36
+ # --- Determine if running under pytest ---
37
+ TESTING = 'pytest' in sys.modules
38
+
39
+ # Quick-start development settings - unsuitable for production
40
+ SECRET_KEY = os.getenv('DJANGO_SECRET_KEY', 'django-insecure-YOUR_FALLBACK_KEY_HERE_CHANGE_ME')
41
+ # Use the Pydantic setting value for Django's DEBUG
42
+ DEBUG = Settings().debug # Read from instantiated Pydantic settings
43
+ ALLOWED_HOSTS = os.getenv('DJANGO_ALLOWED_HOSTS', '*').split(',')
44
+
45
+ # --- Application definition ---
46
+ INSTALLED_APPS = [
47
+ 'django.contrib.admin',
48
+ 'django.contrib.auth',
49
+ 'django.contrib.contenttypes',
50
+ 'django.contrib.sessions',
51
+ 'django.contrib.messages',
52
+ 'django.contrib.staticfiles',
53
+ # Third-party apps
54
+ 'rest_framework',
55
+ 'rest_framework.authtoken',
56
+ 'drf_spectacular',
57
+ # Local apps
58
+ 'swarm.apps.SwarmConfig',
59
+ ]
60
+
61
+ # --- Conditionally add blueprint apps for TESTING ---
62
+ if TESTING:
63
+ _test_apps_to_add = ['blueprints.university']
64
+ for app in _test_apps_to_add:
65
+ if app not in INSTALLED_APPS:
66
+ INSTALLED_APPS.insert(0, app)
67
+ logging.info(f"Settings [TESTING]: Added '{app}' to INSTALLED_APPS.")
68
+ if 'SWARM_BLUEPRINTS' not in os.environ:
69
+ os.environ['SWARM_BLUEPRINTS'] = 'university'
70
+ logging.info(f"Settings [TESTING]: Set SWARM_BLUEPRINTS='university'")
71
+ else:
72
+ # --- Dynamic App Loading for Production/Development ---
73
+ _INITIAL_BLUEPRINT_APPS = []
74
+ _swarm_blueprints_env = os.getenv('SWARM_BLUEPRINTS')
75
+ _log_source = "Not Set"
76
+ if _swarm_blueprints_env:
77
+ _blueprint_names = [name.strip() for name in _swarm_blueprints_env.split(',') if name.strip()]
78
+ _INITIAL_BLUEPRINT_APPS = [f'blueprints.{name}' for name in _blueprint_names if name.replace('_', '').isidentifier()]
79
+ _log_source = "SWARM_BLUEPRINTS env var"
80
+ logging.info(f"Settings: Found blueprints from env var: {_INITIAL_BLUEPRINT_APPS}")
81
+ else:
82
+ _log_source = "directory scan"
83
+ try:
84
+ if BLUEPRINTS_DIR.is_dir():
85
+ for item in BLUEPRINTS_DIR.iterdir():
86
+ if item.is_dir() and (item / '__init__.py').exists():
87
+ if item.name.replace('_', '').isidentifier():
88
+ _INITIAL_BLUEPRINT_APPS.append(f'blueprints.{item.name}')
89
+ logging.info(f"Settings: Found blueprints from directory scan: {_INITIAL_BLUEPRINT_APPS}")
90
+ except Exception as e:
91
+ logging.error(f"Settings: Error discovering blueprint apps during initial load: {e}")
92
+
93
+ for app in _INITIAL_BLUEPRINT_APPS:
94
+ if app not in INSTALLED_APPS:
95
+ INSTALLED_APPS.append(app)
96
+ logging.info(f"Settings [{_log_source}]: Added '{app}' to INSTALLED_APPS.")
97
+ # --- End App Loading Logic ---
98
+
99
+ if isinstance(INSTALLED_APPS, tuple): INSTALLED_APPS = list(INSTALLED_APPS)
100
+ logging.info(f"Settings: Final INSTALLED_APPS = {INSTALLED_APPS}")
101
+
102
+ MIDDLEWARE = [
103
+ 'django.middleware.security.SecurityMiddleware',
104
+ 'django.contrib.sessions.middleware.SessionMiddleware',
105
+ 'django.middleware.common.CommonMiddleware',
106
+ 'django.middleware.csrf.CsrfViewMiddleware',
107
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
108
+ 'django.contrib.messages.middleware.MessageMiddleware',
109
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
110
+ ]
111
+
112
+ ROOT_URLCONF = 'swarm.urls'
113
+
114
+ TEMPLATES = [
115
+ {
116
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
117
+ 'DIRS': [BASE_DIR / 'templates'],
118
+ 'APP_DIRS': True,
119
+ 'OPTIONS': {
120
+ 'context_processors': [
121
+ 'django.template.context_processors.debug',
122
+ 'django.template.context_processors.request',
123
+ 'django.contrib.auth.context_processors.auth',
124
+ 'django.contrib.messages.context_processors.messages',
125
+ ],
126
+ },
127
+ },
128
+ ]
129
+
130
+ WSGI_APPLICATION = 'swarm.wsgi.application'
131
+
132
+ SQLITE_DB_PATH = os.getenv('SQLITE_DB_PATH', BASE_DIR / 'db.sqlite3')
133
+ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': SQLITE_DB_PATH } }
134
+ DJANGO_DATABASE = DATABASES['default']
135
+
136
+ AUTH_PASSWORD_VALIDATORS = [
137
+ {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
138
+ {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
139
+ {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
140
+ {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
141
+ ]
142
+
143
+ LANGUAGE_CODE = 'en-us'; TIME_ZONE = 'UTC'; USE_I18N = True; USE_TZ = True
144
+
145
+ STATIC_URL = '/static/'; STATIC_ROOT = BASE_DIR / 'staticfiles'
146
+ STATICFILES_DIRS = [ BASE_DIR / 'static', BASE_DIR / 'assets' ]
147
+
148
+ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
149
+
150
+ REST_FRAMEWORK = {
151
+ 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
152
+ 'DEFAULT_AUTHENTICATION_CLASSES': (
153
+ 'swarm.auth.EnvOrTokenAuthentication',
154
+ 'rest_framework.authentication.TokenAuthentication',
155
+ 'rest_framework.authentication.SessionAuthentication',
156
+ ),
157
+ 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAuthenticated',)
158
+ }
159
+
160
+ SPECTACULAR_SETTINGS = {
161
+ 'TITLE': 'Open Swarm API',
162
+ 'DESCRIPTION': 'API for the Open Swarm multi-agent collaboration framework.',
163
+ 'VERSION': '1.0.0',
164
+ 'SERVE_INCLUDE_SCHEMA': False,
165
+ 'SERVE_PERMISSIONS': ['rest_framework.permissions.AllowAny'],
166
+ }
167
+
168
+ LOGGING = {
169
+ 'version': 1, 'disable_existing_loggers': False,
170
+ 'formatters': { 'standard': { 'format': '[%(levelname)s] %(asctime)s - %(name)s:%(lineno)d - %(message)s' }, },
171
+ 'handlers': { 'console': { 'level': 'DEBUG' if DEBUG else 'INFO', 'class': 'logging.StreamHandler', 'formatter': 'standard', }, },
172
+ 'loggers': {
173
+ 'django': { 'handlers': ['console'], 'level': 'INFO', 'propagate': False, },
174
+ 'django.request': { 'handlers': ['console'], 'level': 'WARNING', 'propagate': False, },
175
+ 'swarm': { 'handlers': ['console'], 'level': 'DEBUG' if DEBUG else 'INFO', 'propagate': False, },
176
+ 'swarm.extensions': { 'handlers': ['console'], 'level': 'DEBUG' if DEBUG else 'INFO', 'propagate': False, },
177
+ 'blueprints': { 'handlers': ['console'], 'level': 'DEBUG' if DEBUG else 'INFO', 'propagate': False, },
178
+ },
179
+ }
180
+
181
+ AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend']
182
+ LOGIN_URL = '/accounts/login/'; LOGIN_REDIRECT_URL = '/chatbot/'
183
+
184
+ REDIS_HOST = os.getenv('REDIS_HOST', 'localhost')
185
+ REDIS_PORT = int(os.getenv('REDIS_PORT', 6379))
186
+
187
+ if TESTING:
188
+ print("Pytest detected: Adjusting settings for testing.")
189
+ DATABASES['default']['NAME'] = ':memory:'