open-swarm 0.1.1743070217__py3-none-any.whl → 0.1.1743362777__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.1743362777.dist-info/METADATA +217 -0
  2. open_swarm-0.1.1743362777.dist-info/RECORD +260 -0
  3. {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743362777.dist-info}/WHEEL +1 -2
  4. open_swarm-0.1.1743362777.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.1743362777.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,67 @@
1
+ # Blueprints Overview
2
+
3
+ This directory contains example blueprints for the Open Swarm framework, showcasing agent coordination, external data handling, database operations, and more via parody-themed agent teams. Each blueprint achieves a practical outcome while demonstrating specific framework capabilities. Blueprints are generally ordered by complexity.
4
+
5
+ ## Refactored Blueprints (Using `BlueprintBase`)
6
+
7
+ These blueprints have been updated to use the `BlueprintBase` class, `openai-agents` library conventions (like `Agent`, `@function_tool`, agent-as-tool delegation), and standardized configuration loading.
8
+
9
+ | Blueprint Name | CLI (`uv run ...`) Example Instruction | What it Demonstrates | Key Features | MCP Servers Used (Examples) |
10
+ |---------------------------------|-------------------------------------------------------|--------------------------------------------------------------------------------|---------------------------------------------------------------------------|-----------------------------|
11
+ | **EchoCraft** | `--instruction "Repeat this message"` | Simplest blueprint, direct input echo | Basic `BlueprintBase` structure, Agent `process` override | None |
12
+ | **Suggestion** | `--instruction "Topic: AI Ethics"` | Generating structured JSON output | Agent `output_type=TypedDict`, JSON mode | None |
13
+ | **Chatbot** | `--instruction "Tell me a joke"` | Basic single-agent conversation | Standard `Agent` interaction with LLM | None |
14
+ | **BurntNoodles** | `--instruction "Check git status"` | Coordinating Git & testing tasks via function tools & agent delegation | `@function_tool` for CLI commands, Agent-as-tool delegation | None |
15
+ | **RueCode** | `--instruction "Refactor this python code..."` | Multi-agent code generation/refactoring workflow | Agent-as-tool delegation, specialized agent roles (Coordinator, Code, etc.) | memory |
16
+ | **NebulaShellzzar** | `--instruction "List files in /tmp"` | Matrix-themed sysadmin/coding tasks with delegation | Agent-as-tool delegation, `@function_tool` for shell/code analysis | memory |
17
+ | **DigitalButlers** | `--instruction "Search for nearby restaurants"` | Delegating tasks requiring specific MCPs (search, home automation) | Agent-as-tool delegation, MCP usage by specialist agents | duckduckgo-search, home-assistant |
18
+ | **DilbotUniverse (SQLite)** | `--instruction "Start the SDLC"` | Comedic SDLC simulation, instructions loaded from SQLite | Agent-as-tool delegation, SQLite integration for dynamic prompts | sqlite |
19
+ | **FamilyTies** | `--instruction "Create WP post titled 'Hello'..."` | Coordinating WordPress operations via MCP | Agent-as-tool delegation, specialized agent using specific MCP (WP) | server-wp-mcp |
20
+ | **MissionImprobable (SQLite)** | `--instruction "Use RollinFumble to run 'pwd'"` | Spy-themed ops, instructions from SQLite, multi-level delegation | Agent-as-tool delegation, SQLite integration, MCP usage (fs, shell, mem) | memory, filesystem, mcp-shell |
21
+ | **WhiskeyTangoFoxtrot** | `--instruction "Find free vector DBs"` | Hierarchical agents tracking services using DB & web search | Multi-level agent delegation, SQLite, various search/scrape/doc MCPs | sqlite, brave-search, mcp-npx-fetch, mcp-doc-forge, filesystem |
22
+ | **DivineOps** | `--instruction "Design user auth API"` | Large-scale SW dev coordination (Design, Implement, DB, DevOps, Docs) | Complex delegation, wide range of MCP usage (search, shell, db, fs...) | memory, filesystem, mcp-shell, sqlite, sequential-thinking, brave-search |
23
+ | **Gaggle** | `--instruction "Write story: cat library"` | Collaborative story writing (Planner, Writer, Editor) | Agent-as-tool delegation, function tools for writing steps | None |
24
+ | **MonkaiMagic** | `--instruction "List AWS S3 buckets"` | Cloud operations (AWS, Fly, Vercel) via direct CLI function tools | `@function_tool` for external CLIs, agent-as-tool delegation | mcp-shell (for Sandy) |
25
+ | **UnapologeticPress (SQLite)** | `--instruction "Write poem: city rain"` | Collaborative poetry writing by distinct "poet" agents, SQLite instructions | Agent-as-tool (all-to-all), SQLite, broad MCP usage | Various (see blueprint) |
26
+ | **Omniplex** | `--instruction "Use filesystem to read README.md"` | Dynamically routes tasks based on MCP server type (npx, uvx, other) | Dynamic agent/tool creation based on available MCPs | Dynamic (all available) |
27
+
28
+ ## WIP / Needs Refactoring
29
+
30
+ These blueprints still use older patterns or have known issues (e.g., UVX/NeMo dependencies) and need refactoring to the `BlueprintBase` standard.
31
+
32
+ | Blueprint Name | CLI | Description | Status |
33
+ |-------------------------|----------|--------------------------------------------------------------|-----------------|
34
+ | chucks_angels | chuck | Manages transcripts, compute, Flowise (UVX/NeMo WIP) | Needs Refactor |
35
+ | django_chat | djchat | Django-integrated chatbot example | Needs Review |
36
+ | flock | flock | (Details TBC) | Needs Refactor |
37
+ | messenger | msg | (Details TBC) | Needs Refactor |
38
+
39
+ ## Configuration (`swarm_config.json`)
40
+
41
+ The framework uses a central `swarm_config.json` file (usually in the project root) to define:
42
+
43
+ * **`llm`**: Profiles for different language models (provider, model name, API keys via `${ENV_VAR}`, base URL, etc.).
44
+ * **`mcpServers`**: Definitions for starting external MCP servers. Each entry includes:
45
+ * `command`: The command to run (e.g., `npx`, `uvx`, `python`, `docker`). Can be a string or list.
46
+ * `args`: A list of arguments for the command.
47
+ * `env`: A dictionary of environment variables to set for the server process.
48
+ * `cwd`: (Optional) Working directory for the server process.
49
+ * `description`: (Optional) A human-readable description of the server's function.
50
+ * `startup_timeout`: (Optional) Seconds to wait for the server to start and connect (default: 30).
51
+ * **`blueprints`**: Optional section for blueprint-specific overrides (e.g., default profile, max calls).
52
+ * **`defaults`**: Global default settings (e.g., `default_markdown_cli`).
53
+
54
+ ## Environment Variables
55
+
56
+ Many blueprints or their required MCP servers depend on environment variables (e.g., API keys). These should ideally be set in a `.env` file in the project root. `BlueprintBase` will automatically load this file. See individual blueprint metadata (`env_vars`) or `swarm_config.json` for potentially required variables. The `BlueprintBase` will warn if variables listed in a blueprint's `metadata["env_vars"]` are not set.
57
+
58
+ ## Running Blueprints (Development)
59
+
60
+ Use `uv run python <path_to_blueprint.py> --instruction "Your instruction"`
61
+
62
+ Common flags:
63
+ * `--debug`: Enable detailed DEBUG logging.
64
+ * `--quiet`: Suppress most logs, print only final output.
65
+ * `--config-path`: Specify a different config file location.
66
+ * `--profile`: Use a specific LLM profile from the config.
67
+ * `--markdown` / `--no-markdown`: Force markdown rendering on/off.
@@ -0,0 +1,412 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+ import asyncio
5
+ import subprocess
6
+ import shlex # Added for safe command splitting
7
+ import re
8
+ import inspect
9
+ from pathlib import Path # Use pathlib for better path handling
10
+ from typing import Dict, Any, List, Optional, ClassVar
11
+
12
+ try:
13
+ # Core imports from openai-agents
14
+ from agents import Agent, Tool, function_tool, Runner
15
+ from agents.mcp import MCPServer
16
+ from agents.models.interface import Model
17
+ from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
18
+ from openai import AsyncOpenAI
19
+
20
+ # Import our custom base class
21
+ from swarm.extensions.blueprint.blueprint_base import BlueprintBase
22
+ except ImportError as e:
23
+ # Provide more helpful error message
24
+ print(f"ERROR: Import failed in BurntNoodlesBlueprint: {e}. Check 'openai-agents' install and project structure.")
25
+ print(f"Attempted import from directory: {os.path.dirname(__file__)}")
26
+ print(f"sys.path: {sys.path}")
27
+ sys.exit(1)
28
+
29
+ # Configure logging for this blueprint module
30
+ logger = logging.getLogger(__name__)
31
+ # Logging level is controlled by BlueprintBase based on --debug flag
32
+
33
+ # --- Tool Definitions ---
34
+ # Standalone functions decorated as tools for git and testing operations.
35
+ # Enhanced error handling and logging added.
36
+
37
+ @function_tool
38
+ def git_status() -> str:
39
+ """Executes 'git status --porcelain' and returns the current repository status."""
40
+ logger.info("Executing git status --porcelain") # Keep INFO for tool execution start
41
+ try:
42
+ # Using --porcelain for machine-readable output
43
+ result = subprocess.run(["git", "status", "--porcelain"], capture_output=True, text=True, check=True, timeout=30)
44
+ output = result.stdout.strip()
45
+ logger.debug(f"Git status raw output:\n{output}")
46
+ return f"OK: Git Status:\n{output}" if output else "OK: No changes detected in the working directory."
47
+ except FileNotFoundError:
48
+ logger.error("Git command not found. Is git installed and in PATH?")
49
+ return "Error: git command not found."
50
+ except subprocess.CalledProcessError as e:
51
+ logger.error(f"Error executing git status: {e.stderr}")
52
+ return f"Error executing git status: {e.stderr}"
53
+ except subprocess.TimeoutExpired:
54
+ logger.error("Git status command timed out.")
55
+ return "Error: Git status command timed out."
56
+ except Exception as e:
57
+ logger.error(f"Unexpected error during git status: {e}", exc_info=logger.level <= logging.DEBUG)
58
+ return f"Error during git status: {e}"
59
+
60
+ @function_tool
61
+ def git_diff() -> str:
62
+ """Executes 'git diff' and returns the differences in the working directory."""
63
+ logger.info("Executing git diff") # Keep INFO for tool execution start
64
+ try:
65
+ result = subprocess.run(["git", "diff"], capture_output=True, text=True, check=False, timeout=30) # Use check=False, handle exit code
66
+ output = result.stdout
67
+ stderr = result.stderr.strip()
68
+ if result.returncode != 0 and stderr: # Error occurred
69
+ logger.error(f"Error executing git diff (Exit Code {result.returncode}): {stderr}")
70
+ return f"Error executing git diff: {stderr}"
71
+ logger.debug(f"Git diff raw output (Exit Code {result.returncode}):\n{output[:1000]}...") # Log snippet
72
+ return f"OK: Git Diff Output:\n{output}" if output else "OK: No differences found."
73
+ except FileNotFoundError:
74
+ logger.error("Git command not found.")
75
+ return "Error: git command not found."
76
+ except subprocess.TimeoutExpired:
77
+ logger.error("Git diff command timed out.")
78
+ return "Error: Git diff command timed out."
79
+ except Exception as e:
80
+ logger.error(f"Unexpected error during git diff: {e}", exc_info=logger.level <= logging.DEBUG)
81
+ return f"Error during git diff: {e}"
82
+
83
+ @function_tool
84
+ def git_add(file_path: str = ".") -> str:
85
+ """Executes 'git add' to stage changes for the specified file or all changes (default '.')."""
86
+ logger.info(f"Executing git add {file_path}") # Keep INFO for tool execution start
87
+ try:
88
+ result = subprocess.run(["git", "add", file_path], capture_output=True, text=True, check=True, timeout=30)
89
+ logger.debug(f"Git add '{file_path}' completed successfully.")
90
+ return f"OK: Staged '{file_path}' successfully."
91
+ except FileNotFoundError:
92
+ logger.error("Git command not found.")
93
+ return "Error: git command not found."
94
+ except subprocess.CalledProcessError as e:
95
+ logger.error(f"Error executing git add '{file_path}': {e.stderr}")
96
+ return f"Error executing git add '{file_path}': {e.stderr}"
97
+ except subprocess.TimeoutExpired:
98
+ logger.error(f"Git add command timed out for '{file_path}'.")
99
+ return f"Error: Git add command timed out for '{file_path}'."
100
+ except Exception as e:
101
+ logger.error(f"Unexpected error during git add '{file_path}': {e}", exc_info=logger.level <= logging.DEBUG)
102
+ return f"Error during git add '{file_path}': {e}"
103
+
104
+ @function_tool
105
+ def git_commit(message: str) -> str:
106
+ """Executes 'git commit' with a provided commit message."""
107
+ logger.info(f"Executing git commit -m '{message[:50]}...'") # Keep INFO for tool execution start
108
+ if not message or not message.strip():
109
+ logger.warning("Git commit attempted with empty or whitespace-only message.")
110
+ return "Error: Commit message cannot be empty."
111
+ try:
112
+ # Using list form is generally safer than shell=True for complex args
113
+ result = subprocess.run(["git", "commit", "-m", message], capture_output=True, text=True, check=False, timeout=30) # Use check=False
114
+ output = result.stdout.strip()
115
+ stderr = result.stderr.strip()
116
+ logger.debug(f"Git commit raw output (Exit Code {result.returncode}):\nSTDOUT: {output}\nSTDERR: {stderr}")
117
+
118
+ # Handle common non-error cases explicitly
119
+ if "nothing to commit" in output or "nothing added to commit" in output or "no changes added to commit" in output:
120
+ logger.info("Git commit reported: Nothing to commit.")
121
+ return "OK: Nothing to commit."
122
+ if result.returncode == 0:
123
+ return f"OK: Committed with message '{message}'.\n{output}"
124
+ else:
125
+ # Log specific error if available
126
+ error_detail = stderr if stderr else output
127
+ logger.error(f"Error executing git commit (Exit Code {result.returncode}): {error_detail}")
128
+ return f"Error executing git commit: {error_detail}"
129
+
130
+ except FileNotFoundError:
131
+ logger.error("Git command not found.")
132
+ return "Error: git command not found."
133
+ except subprocess.TimeoutExpired:
134
+ logger.error("Git commit command timed out.")
135
+ return "Error: Git commit command timed out."
136
+ except Exception as e:
137
+ logger.error(f"Unexpected error during git commit: {e}", exc_info=logger.level <= logging.DEBUG)
138
+ return f"Error during git commit: {e}"
139
+
140
+ @function_tool
141
+ def git_push() -> str:
142
+ """Executes 'git push' to push staged commits to the remote repository."""
143
+ logger.info("Executing git push") # Keep INFO for tool execution start
144
+ try:
145
+ result = subprocess.run(["git", "push"], capture_output=True, text=True, check=True, timeout=120) # Longer timeout for push
146
+ output = result.stdout.strip() + "\n" + result.stderr.strip() # Combine stdout/stderr
147
+ logger.debug(f"Git push raw output:\n{output}")
148
+ return f"OK: Push completed.\n{output.strip()}"
149
+ except FileNotFoundError:
150
+ logger.error("Git command not found.")
151
+ return "Error: git command not found."
152
+ except subprocess.CalledProcessError as e:
153
+ error_output = e.stdout.strip() + "\n" + e.stderr.strip()
154
+ logger.error(f"Error executing git push: {error_output}")
155
+ return f"Error executing git push: {error_output.strip()}"
156
+ except subprocess.TimeoutExpired:
157
+ logger.error("Git push command timed out.")
158
+ return "Error: Git push command timed out."
159
+ except Exception as e:
160
+ logger.error(f"Unexpected error during git push: {e}", exc_info=logger.level <= logging.DEBUG)
161
+ return f"Error during git push: {e}"
162
+
163
+ @function_tool
164
+ def run_npm_test(args: str = "") -> str:
165
+ """Executes 'npm run test' with optional arguments."""
166
+ try:
167
+ # Use shlex.split for safer argument handling if args are provided
168
+ cmd_list = ["npm", "run", "test"] + (shlex.split(args) if args else [])
169
+ cmd_str = ' '.join(cmd_list) # For logging
170
+ logger.info(f"Executing npm test: {cmd_str}") # Keep INFO for tool execution start
171
+ result = subprocess.run(cmd_list, capture_output=True, text=True, check=False, timeout=120) # check=False to capture output on failure
172
+ output = f"Exit Code: {result.returncode}\nSTDOUT:\n{result.stdout.strip()}\nSTDERR:\n{result.stderr.strip()}"
173
+ if result.returncode == 0:
174
+ logger.debug(f"npm test completed successfully:\n{output}")
175
+ return f"OK: npm test finished.\n{output}"
176
+ else:
177
+ logger.error(f"npm test failed (Exit Code {result.returncode}):\n{output}")
178
+ return f"Error: npm test failed.\n{output}"
179
+ except FileNotFoundError:
180
+ logger.error("npm command not found. Is Node.js/npm installed and in PATH?")
181
+ return "Error: npm command not found."
182
+ except subprocess.TimeoutExpired:
183
+ logger.error("npm test command timed out.")
184
+ return "Error: npm test command timed out."
185
+ except Exception as e:
186
+ logger.error(f"Unexpected error during npm test: {e}", exc_info=logger.level <= logging.DEBUG)
187
+ return f"Error during npm test: {e}"
188
+
189
+ @function_tool
190
+ def run_pytest(args: str = "") -> str:
191
+ """Executes 'uv run pytest' with optional arguments."""
192
+ try:
193
+ # Use shlex.split for safer argument handling
194
+ cmd_list = ["uv", "run", "pytest"] + (shlex.split(args) if args else [])
195
+ cmd_str = ' '.join(cmd_list) # For logging
196
+ logger.info(f"Executing pytest via uv: {cmd_str}") # Keep INFO for tool execution start
197
+ result = subprocess.run(cmd_list, capture_output=True, text=True, check=False, timeout=120) # check=False to capture output on failure
198
+ output = f"Exit Code: {result.returncode}\nSTDOUT:\n{result.stdout.strip()}\nSTDERR:\n{result.stderr.strip()}"
199
+ # Pytest often returns non-zero exit code on test failures, report this clearly
200
+ if result.returncode == 0:
201
+ logger.debug(f"pytest completed successfully:\n{output}")
202
+ return f"OK: pytest finished successfully.\n{output}"
203
+ else:
204
+ logger.warning(f"pytest finished with failures (Exit Code {result.returncode}):\n{output}")
205
+ # Still return "OK" from tool perspective, but indicate failure in the message
206
+ return f"OK: Pytest finished with failures (Exit Code {result.returncode}).\n{output}"
207
+ except FileNotFoundError:
208
+ logger.error("uv command not found. Is uv installed and in PATH?")
209
+ return "Error: uv command not found."
210
+ except subprocess.TimeoutExpired:
211
+ logger.error("pytest command timed out.")
212
+ return "Error: pytest command timed out."
213
+ except Exception as e:
214
+ logger.error(f"Unexpected error during pytest: {e}", exc_info=logger.level <= logging.DEBUG)
215
+ return f"Error during pytest: {e}"
216
+
217
+ # --- Agent Instructions ---
218
+ # Define clear instructions for each agent's role and capabilities.
219
+ michael_instructions = """
220
+ You are Michael Toasted, the resolute leader of the Burnt Noodles creative team.
221
+ Your primary role is to understand the user's request, break it down into actionable steps,
222
+ and delegate tasks appropriately to your team members: Fiona Flame (Git operations) and Sam Ashes (Testing).
223
+ You should only execute simple Git status checks (`git_status`, `git_diff`) yourself. Delegate all other Git actions (add, commit, push) to Fiona. Delegate all testing actions (npm test, pytest) to Sam.
224
+ Synthesize the results from your team and provide the final response to the user.
225
+ Available Function Tools (for you): git_status, git_diff.
226
+ Available Agent Tools (for delegation): Fiona_Flame, Sam_Ashes.
227
+ """
228
+ fiona_instructions = """
229
+ You are Fiona Flame, the git specialist. Execute git commands precisely as requested using your available function tools:
230
+ `git_status`, `git_diff`, `git_add`, `git_commit`, `git_push`.
231
+ When asked to commit, analyze the diff if necessary and generate concise, informative conventional commit messages (e.g., 'feat: ...', 'fix: ...', 'refactor: ...', 'chore: ...').
232
+ Always stage changes using `git_add` before committing.
233
+ If asked to push, first ask the user (Michael) for confirmation before executing `git_push`.
234
+ If a task involves testing (like running tests after a commit), delegate it to the Sam_Ashes agent tool.
235
+ For tasks outside your Git domain, report back to Michael; do not use the Michael_Toasted tool directly.
236
+ Available Function Tools: git_status, git_diff, git_add, git_commit, git_push.
237
+ Available Agent Tools: Sam_Ashes.
238
+ """
239
+ sam_instructions = """
240
+ You are Sam Ashes, the meticulous testing operative. Execute test commands using your available function tools: `run_npm_test` or `run_pytest`.
241
+ Interpret the results: Report failures immediately and clearly. If tests pass, consider running with coverage (e.g., using `uv run pytest --cov` via the `run_pytest` tool) if appropriate or requested, and report the coverage summary.
242
+ For tasks outside testing (e.g., needing code changes before testing, or git operations), refer back to Michael; do not use the Michael_Toasted or Fiona_Flame tools directly.
243
+ Available Function Tools: run_npm_test, run_pytest.
244
+ Available Agent Tools: None (Report back to Michael for delegation).
245
+ """
246
+
247
+ # --- Blueprint Definition ---
248
+ # Inherits from BlueprintBase, defines metadata, creates agents, and sets up delegation.
249
+ class BurntNoodlesBlueprint(BlueprintBase):
250
+ """
251
+ Burnt Noodles Blueprint: A multi-agent team demonstrating Git operations and testing workflows.
252
+ - Michael Toasted: Coordinator, delegates tasks.
253
+ - Fiona Flame: Handles Git commands (status, diff, add, commit, push).
254
+ - Sam Ashes: Handles test execution (npm, pytest).
255
+ """
256
+ # Class variable for blueprint metadata, conforming to BlueprintBase structure.
257
+ metadata: ClassVar[Dict[str, Any]] = {
258
+ "name": "BurntNoodlesBlueprint",
259
+ "title": "Burnt Noodles",
260
+ "description": "A multi-agent team managing Git operations and code testing.",
261
+ "version": "1.1.0", # Incremented version
262
+ "author": "Open Swarm Team (Refactored)",
263
+ "tags": ["git", "test", "multi-agent", "collaboration", "refactor"],
264
+ "required_mcp_servers": [], # No external MCP servers needed for core functionality
265
+ }
266
+
267
+ # Caches for OpenAI client and Model instances to avoid redundant creation.
268
+ _openai_client_cache: Dict[str, AsyncOpenAI] = {}
269
+ _model_instance_cache: Dict[str, Model] = {}
270
+
271
+ def _get_model_instance(self, profile_name: str) -> Model:
272
+ """
273
+ Retrieves or creates an LLM Model instance based on the configuration profile.
274
+ Handles client instantiation and caching. Uses OpenAIChatCompletionsModel.
275
+ Args:
276
+ profile_name: The name of the LLM profile to use (e.g., 'default').
277
+ Returns:
278
+ An instance of the configured Model.
279
+ Raises:
280
+ ValueError: If configuration is missing or invalid.
281
+ """
282
+ # Check cache first
283
+ if profile_name in self._model_instance_cache:
284
+ logger.debug(f"Using cached Model instance for profile '{profile_name}'.")
285
+ return self._model_instance_cache[profile_name]
286
+
287
+ logger.debug(f"Creating new Model instance for profile '{profile_name}'.")
288
+ # Retrieve profile data using BlueprintBase helper method
289
+ profile_data = self.get_llm_profile(profile_name)
290
+ if not profile_data:
291
+ # Critical error if the profile (or default fallback) isn't found
292
+ logger.critical(f"Cannot create Model instance: LLM profile '{profile_name}' (or 'default') not found in configuration.")
293
+ raise ValueError(f"Missing LLM profile configuration for '{profile_name}' or 'default'.")
294
+
295
+ provider = profile_data.get("provider", "openai").lower()
296
+ model_name = profile_data.get("model")
297
+ if not model_name:
298
+ logger.critical(f"LLM profile '{profile_name}' is missing the required 'model' key.")
299
+ raise ValueError(f"Missing 'model' key in LLM profile '{profile_name}'.")
300
+
301
+ # Ensure we only handle OpenAI for now
302
+ if provider != "openai":
303
+ logger.error(f"Unsupported LLM provider '{provider}' in profile '{profile_name}'. Only 'openai' is supported in this blueprint.")
304
+ raise ValueError(f"Unsupported LLM provider: {provider}")
305
+
306
+ # Create or retrieve cached OpenAI client instance
307
+ client_cache_key = f"{provider}_{profile_data.get('base_url')}"
308
+ if client_cache_key not in self._openai_client_cache:
309
+ # Prepare arguments for AsyncOpenAI, filtering out None values
310
+ client_kwargs = { "api_key": profile_data.get("api_key"), "base_url": profile_data.get("base_url") }
311
+ filtered_client_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
312
+ log_client_kwargs = {k:v for k,v in filtered_client_kwargs.items() if k != 'api_key'} # Don't log API key
313
+ logger.debug(f"Creating new AsyncOpenAI client for profile '{profile_name}' with config: {log_client_kwargs}")
314
+ try:
315
+ # Create and cache the client
316
+ self._openai_client_cache[client_cache_key] = AsyncOpenAI(**filtered_client_kwargs)
317
+ except Exception as e:
318
+ logger.error(f"Failed to create AsyncOpenAI client for profile '{profile_name}': {e}", exc_info=True)
319
+ raise ValueError(f"Failed to initialize OpenAI client for profile '{profile_name}': {e}") from e
320
+
321
+ openai_client_instance = self._openai_client_cache[client_cache_key]
322
+
323
+ # Instantiate the specific Model implementation (OpenAIChatCompletionsModel)
324
+ logger.debug(f"Instantiating OpenAIChatCompletionsModel(model='{model_name}') with client instance for profile '{profile_name}'.")
325
+ try:
326
+ model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=openai_client_instance)
327
+ # Cache the model instance
328
+ self._model_instance_cache[profile_name] = model_instance
329
+ return model_instance
330
+ except Exception as e:
331
+ logger.error(f"Failed to instantiate OpenAIChatCompletionsModel for profile '{profile_name}': {e}", exc_info=True)
332
+ raise ValueError(f"Failed to initialize LLM provider for profile '{profile_name}': {e}") from e
333
+
334
+ def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
335
+ """
336
+ Creates the Burnt Noodles agent team: Michael (Coordinator), Fiona (Git), Sam (Testing).
337
+ Sets up tools and agent-as-tool delegation.
338
+ Args:
339
+ mcp_servers: List of started MCP server instances (not used by this BP).
340
+ Returns:
341
+ The starting agent instance (Michael Toasted).
342
+ """
343
+ logger.debug("Creating Burnt Noodles agent team...")
344
+ # Clear caches at the start of agent creation for this run
345
+ self._model_instance_cache = {}
346
+ self._openai_client_cache = {}
347
+
348
+ # Determine the LLM profile to use (e.g., from config or default)
349
+ default_profile_name = self.config.get("llm_profile", "default")
350
+ logger.debug(f"Using LLM profile '{default_profile_name}' for all Burnt Noodles agents.")
351
+ # Get the single Model instance to share among agents (or create if needed)
352
+ default_model_instance = self._get_model_instance(default_profile_name)
353
+
354
+ # Instantiate the specialist agents first
355
+ # Fiona gets Git function tools
356
+ fiona_flame = Agent(
357
+ name="Fiona_Flame", # Use names valid as tool names
358
+ model=default_model_instance,
359
+ instructions=fiona_instructions,
360
+ tools=[git_status, git_diff, git_add, git_commit, git_push] # Agent tools added later
361
+ )
362
+ # Sam gets Testing function tools
363
+ sam_ashes = Agent(
364
+ name="Sam_Ashes", # Use names valid as tool names
365
+ model=default_model_instance,
366
+ instructions=sam_instructions,
367
+ tools=[run_npm_test, run_pytest] # Agent tools added later
368
+ )
369
+
370
+ # Instantiate the coordinator agent (Michael)
371
+ # Michael gets limited function tools and the specialist agents as tools
372
+ michael_toasted = Agent(
373
+ name="Michael_Toasted",
374
+ model=default_model_instance,
375
+ instructions=michael_instructions,
376
+ tools=[
377
+ # Michael's direct function tools (limited scope)
378
+ git_status,
379
+ git_diff,
380
+ # Specialist agents exposed as tools for delegation
381
+ fiona_flame.as_tool(
382
+ tool_name="Fiona_Flame", # Explicit tool name
383
+ tool_description="Delegate Git operations (add, commit, push) or complex status/diff queries to Fiona."
384
+ ),
385
+ sam_ashes.as_tool(
386
+ tool_name="Sam_Ashes", # Explicit tool name
387
+ tool_description="Delegate testing tasks (npm test, pytest) to Sam."
388
+ ),
389
+ ],
390
+ mcp_servers=mcp_servers # Pass along MCP servers if needed (though not used here)
391
+ )
392
+
393
+ # Add cross-delegation tools *after* all agents are instantiated
394
+ # Fiona can delegate testing to Sam
395
+ fiona_flame.tools.append(
396
+ sam_ashes.as_tool(tool_name="Sam_Ashes", tool_description="Delegate testing tasks (npm test, pytest) to Sam.")
397
+ )
398
+ # Sam can delegate Git tasks back to Fiona (as per instructions, Sam should report to Michael,
399
+ # but having the tool technically available might be useful in complex future scenarios,
400
+ # rely on prompt engineering to prevent direct calls unless intended).
401
+ # sam_ashes.tools.append(
402
+ # fiona_flame.as_tool(tool_name="Fiona_Flame", tool_description="Delegate Git operations back to Fiona if needed.")
403
+ # )
404
+
405
+ logger.debug("Burnt Noodles agent team created successfully. Michael Toasted is the starting agent.")
406
+ # Return the coordinator agent as the entry point for the Runner
407
+ return michael_toasted
408
+
409
+ # Standard Python entry point for direct script execution
410
+ if __name__ == "__main__":
411
+ # Call the main class method from BlueprintBase to handle CLI parsing and execution.
412
+ BurntNoodlesBlueprint.main()
@@ -0,0 +1,98 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+ from typing import Dict, Any, List, ClassVar, Optional
5
+
6
+ # Ensure src is in path for BlueprintBase import
7
+ project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
8
+ src_path = os.path.join(project_root, 'src')
9
+ if src_path not in sys.path: sys.path.insert(0, src_path)
10
+
11
+ try:
12
+ from agents import Agent, Tool, function_tool, Runner
13
+ from agents.mcp import MCPServer
14
+ from agents.models.interface import Model
15
+ from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
16
+ from openai import AsyncOpenAI
17
+ from swarm.extensions.blueprint.blueprint_base import BlueprintBase
18
+ except ImportError as e:
19
+ print(f"ERROR: Import failed in ChatbotBlueprint: {e}. Check dependencies.")
20
+ print(f"sys.path: {sys.path}")
21
+ sys.exit(1)
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # --- Define the Blueprint ---
26
+ class ChatbotBlueprint(BlueprintBase):
27
+ """A simple conversational chatbot agent."""
28
+ metadata: ClassVar[Dict[str, Any]] = {
29
+ "name": "ChatbotBlueprint",
30
+ "title": "Simple Chatbot",
31
+ "description": "A basic conversational agent that responds to user input.",
32
+ "version": "1.1.0", # Refactored version
33
+ "author": "Open Swarm Team (Refactored)",
34
+ "tags": ["chatbot", "conversation", "simple"],
35
+ "required_mcp_servers": [],
36
+ "env_vars": [],
37
+ }
38
+
39
+ # Caches
40
+ _openai_client_cache: Dict[str, AsyncOpenAI] = {}
41
+ _model_instance_cache: Dict[str, Model] = {}
42
+
43
+ # --- Model Instantiation Helper --- (Standard helper)
44
+ def _get_model_instance(self, profile_name: str) -> Model:
45
+ """Retrieves or creates an LLM Model instance."""
46
+ # ... (Implementation is the same as previous refactors) ...
47
+ if profile_name in self._model_instance_cache:
48
+ logger.debug(f"Using cached Model instance for profile '{profile_name}'.")
49
+ return self._model_instance_cache[profile_name]
50
+ logger.debug(f"Creating new Model instance for profile '{profile_name}'.")
51
+ profile_data = self.get_llm_profile(profile_name)
52
+ if not profile_data: raise ValueError(f"Missing LLM profile '{profile_name}'.")
53
+ provider = profile_data.get("provider", "openai").lower()
54
+ model_name = profile_data.get("model")
55
+ if not model_name: raise ValueError(f"Missing 'model' in profile '{profile_name}'.")
56
+ if provider != "openai": raise ValueError(f"Unsupported provider: {provider}")
57
+ client_cache_key = f"{provider}_{profile_data.get('base_url')}"
58
+ if client_cache_key not in self._openai_client_cache:
59
+ client_kwargs = { "api_key": profile_data.get("api_key"), "base_url": profile_data.get("base_url") }
60
+ filtered_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
61
+ log_kwargs = {k:v for k,v in filtered_kwargs.items() if k != 'api_key'}
62
+ logger.debug(f"Creating new AsyncOpenAI client for '{profile_name}': {log_kwargs}")
63
+ try: self._openai_client_cache[client_cache_key] = AsyncOpenAI(**filtered_kwargs)
64
+ except Exception as e: raise ValueError(f"Failed to init client: {e}") from e
65
+ client = self._openai_client_cache[client_cache_key]
66
+ logger.debug(f"Instantiating OpenAIChatCompletionsModel(model='{model_name}') for '{profile_name}'.")
67
+ try:
68
+ model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=client)
69
+ self._model_instance_cache[profile_name] = model_instance
70
+ return model_instance
71
+ except Exception as e: raise ValueError(f"Failed to init LLM: {e}") from e
72
+
73
+ def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
74
+ """Creates the single Chatbot agent."""
75
+ logger.debug("Creating Chatbot agent...")
76
+ self._model_instance_cache = {}
77
+ self._openai_client_cache = {}
78
+
79
+ default_profile_name = self.config.get("llm_profile", "default")
80
+ logger.debug(f"Using LLM profile '{default_profile_name}' for Chatbot.")
81
+ model_instance = self._get_model_instance(default_profile_name)
82
+
83
+ chatbot_instructions = "You are a helpful and friendly chatbot. Respond directly to the user's input in a conversational manner."
84
+
85
+ chatbot_agent = Agent(
86
+ name="Chatbot",
87
+ model=model_instance,
88
+ instructions=chatbot_instructions,
89
+ tools=[], # No function tools needed for simple chat
90
+ mcp_servers=mcp_servers # Pass along, though likely unused
91
+ )
92
+
93
+ logger.debug("Chatbot agent created.")
94
+ return chatbot_agent
95
+
96
+ # Standard Python entry point
97
+ if __name__ == "__main__":
98
+ ChatbotBlueprint.main()
@@ -0,0 +1,33 @@
1
+ {% load static %}
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ {% include 'rest_mode/components/header.html' %}
6
+ <link rel="stylesheet" href="{% static 'rest_mode/css/chatbot.css' %}">
7
+ <script>
8
+ window.STATIC_URLS = {
9
+ layoutSidebarLeftExpand: "{% static 'contrib/tabler-icons/layout-sidebar-left-expand.svg' %}",
10
+ layoutSidebarLeftCollapse: "{% static 'contrib/tabler-icons/layout-sidebar-left-collapse.svg' %}"
11
+ };
12
+ </script>
13
+ </head>
14
+ <body>
15
+ {% include 'rest_mode/components/top_bar.html' %}
16
+ {% include 'rest_mode/components/settings_dialog.html' %}
17
+ <div class="container" data-theme-color="corporate" data-theme-dark="false" data-theme-layout="chatbot-layout">
18
+ <div class="side-panes chat-history-pane" id="chatHistoryPane">
19
+ {% include 'rest_mode/components/chat_sidebar.html' with conversations=conversations %}
20
+ </div>
21
+ <div class="vertical-divider" id="divider-left"></div>
22
+ <div class="main-pane">
23
+ {% include 'rest_mode/components/main_chat_pane.html' %}
24
+ </div>
25
+ <div id="toastContainer"></div>
26
+ </div>
27
+ <div class="settings-overlay"></div> <!-- Ensure overlay exists -->
28
+ <script type="module" src="{% static 'contrib/markedjs/marked.min.js' %}"></script>
29
+ <script type="module" src="{% static 'rest_mode/js/settings.js' %}" defer></script>
30
+ <script type="module" src="{% static 'rest_mode/js/main.js' %}"></script>
31
+ <script type="module" src="{% static 'rest_mode/js/chatLogic.js' %}" defer></script>
32
+ </body>
33
+ </html>