django-agent-studio 0.1.9__py3-none-any.whl → 0.2.1__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.
- django_agent_studio/agents/builder.py +824 -24
- django_agent_studio/agents/dynamic.py +350 -40
- django_agent_studio/api/urls.py +7 -0
- django_agent_studio/api/views.py +133 -0
- django_agent_studio/static/agent-frontend/chat-widget.css +357 -0
- django_agent_studio/static/agent-frontend/chat-widget.js +242 -117
- django_agent_studio/static/django_agent_studio/js/builder.js +247 -0
- django_agent_studio/static/django_agent_studio/js/builder.js.map +1 -0
- django_agent_studio/static/django_agent_studio/js/style.css +1 -0
- django_agent_studio/templates/django_agent_studio/builder.html +35 -2128
- {django_agent_studio-0.1.9.dist-info → django_agent_studio-0.2.1.dist-info}/METADATA +9 -1
- {django_agent_studio-0.1.9.dist-info → django_agent_studio-0.2.1.dist-info}/RECORD +15 -12
- {django_agent_studio-0.1.9.dist-info → django_agent_studio-0.2.1.dist-info}/WHEEL +0 -0
- {django_agent_studio-0.1.9.dist-info → django_agent_studio-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {django_agent_studio-0.1.9.dist-info → django_agent_studio-0.2.1.dist-info}/top_level.txt +0 -0
|
@@ -41,6 +41,7 @@ You have access to tools that allow you to:
|
|
|
41
41
|
8. **Switch between agents and systems** in the UI
|
|
42
42
|
9. **Configure agent memory** (enable/disable the remember tool)
|
|
43
43
|
10. **Manage agent specifications** (human-readable behavior descriptions)
|
|
44
|
+
11. **Configure file uploads** (allowed types, size limits, OCR/vision providers)
|
|
44
45
|
|
|
45
46
|
## IMPORTANT: Tool Usage
|
|
46
47
|
|
|
@@ -97,15 +98,70 @@ You can control the builder UI to switch between different agents and systems:
|
|
|
97
98
|
When the user asks to work on a different agent or system, use these tools to switch context.
|
|
98
99
|
The UI will automatically update to show the selected agent/system.
|
|
99
100
|
|
|
101
|
+
## System Management
|
|
102
|
+
|
|
103
|
+
You can create and manage multi-agent systems directly:
|
|
104
|
+
- Use `create_system` to create a new system with an entry agent
|
|
105
|
+
- Use `add_agent_to_system` to add agents to an existing system
|
|
106
|
+
- Use `remove_agent_from_system` to remove agents from a system
|
|
107
|
+
- Use `update_system_config` to modify system settings (name, description, entry agent, shared knowledge)
|
|
108
|
+
- Use `delete_system` to delete a system (agents are NOT deleted)
|
|
109
|
+
|
|
110
|
+
**Creating a System:**
|
|
111
|
+
```
|
|
112
|
+
create_system({{
|
|
113
|
+
"name": "Customer Support",
|
|
114
|
+
"entry_agent_slug": "support-triage",
|
|
115
|
+
"description": "Handles customer inquiries",
|
|
116
|
+
"auto_discover": true // Automatically adds all sub-agents
|
|
117
|
+
}})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Shared Knowledge:**
|
|
121
|
+
Systems can have shared knowledge that applies to ALL agents in the system. Use `update_system_config` with `shared_knowledge`:
|
|
122
|
+
```
|
|
123
|
+
update_system_config({{
|
|
124
|
+
"system_slug": "customer-support",
|
|
125
|
+
"shared_knowledge": [
|
|
126
|
+
{{
|
|
127
|
+
"key": "company-info",
|
|
128
|
+
"title": "Company Information",
|
|
129
|
+
"content": "We are Acme Corp...",
|
|
130
|
+
"inject_as": "system", // Prepend to system prompt
|
|
131
|
+
"priority": 0,
|
|
132
|
+
"enabled": true
|
|
133
|
+
}}
|
|
134
|
+
]
|
|
135
|
+
}})
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**inject_as options:**
|
|
139
|
+
- `system`: Prepend to every agent's system prompt
|
|
140
|
+
- `context`: Add as conversation context
|
|
141
|
+
- `knowledge`: Make searchable via RAG
|
|
142
|
+
|
|
100
143
|
## Agent Memory
|
|
101
144
|
|
|
102
145
|
Agents have a built-in memory system that allows them to remember facts about users across conversations:
|
|
103
146
|
- Memory is **enabled by default** for all agents
|
|
104
|
-
- When enabled, the agent has
|
|
105
|
-
- Memories are scoped per-user and per-conversation
|
|
147
|
+
- When enabled, the agent has `remember`, `recall`, and `forget` tools
|
|
106
148
|
- Memory only works for **authenticated users** (not anonymous visitors)
|
|
107
|
-
- Use `get_memory_status` to check
|
|
108
|
-
- Use `set_memory_enabled` to enable or disable memory
|
|
149
|
+
- Use `get_memory_status` to check current memory configuration
|
|
150
|
+
- Use `set_memory_enabled` to quickly enable or disable memory
|
|
151
|
+
- Use `configure_memory` for advanced memory settings
|
|
152
|
+
|
|
153
|
+
**Memory Scopes:**
|
|
154
|
+
- `conversation` - Memories only for this chat session
|
|
155
|
+
- `user` - Memories persist across all conversations for this user (default)
|
|
156
|
+
- `system` - Memories shared with other agents in the system
|
|
157
|
+
|
|
158
|
+
**Advanced Configuration (via `configure_memory`):**
|
|
159
|
+
- `default_scope` - Default scope for new memories (default: "user")
|
|
160
|
+
- `allowed_scopes` - Which scopes the agent can use (default: all)
|
|
161
|
+
- `auto_recall` - Auto-load memories at conversation start (default: true)
|
|
162
|
+
- `max_memories_in_prompt` - Limit memories in system prompt (default: 50)
|
|
163
|
+
- `include_system_memories` - Include memories from other agents (default: true)
|
|
164
|
+
- `retention_days` - Auto-delete memories after N days (default: null = forever)
|
|
109
165
|
|
|
110
166
|
**When to disable memory:**
|
|
111
167
|
- Public-facing agents where you don't want user data stored
|
|
@@ -139,6 +195,28 @@ Every agent can have a **spec** - a human-readable description of its intended b
|
|
|
139
195
|
|
|
140
196
|
**Best practice:** When building an agent, start by writing or reviewing the spec, then craft the system prompt to implement that spec. This ensures alignment between intended and actual behavior.
|
|
141
197
|
|
|
198
|
+
## File Upload Configuration
|
|
199
|
+
|
|
200
|
+
Agents can accept file uploads from users. Use `update_file_config` to configure:
|
|
201
|
+
- **enabled**: Turn file uploads on/off for this agent
|
|
202
|
+
- **max_file_size_mb**: Maximum file size (default: 100MB)
|
|
203
|
+
- **allowed_types**: MIME type patterns like `image/*`, `application/pdf`, `text/*`
|
|
204
|
+
- **ocr_provider**: Extract text from images/PDFs using:
|
|
205
|
+
- `tesseract` (local, free)
|
|
206
|
+
- `google_vision` (Google Cloud Vision API)
|
|
207
|
+
- `aws_textract` (AWS Textract)
|
|
208
|
+
- `azure_di` (Azure Document Intelligence)
|
|
209
|
+
- **vision_provider**: AI image understanding using:
|
|
210
|
+
- `openai` (GPT-4 Vision)
|
|
211
|
+
- `anthropic` (Claude Vision)
|
|
212
|
+
- `gemini` (Google Gemini)
|
|
213
|
+
- **enable_thumbnails**: Generate preview thumbnails for images
|
|
214
|
+
|
|
215
|
+
**Example configurations:**
|
|
216
|
+
- Document processing agent: Enable PDF, DOCX, use `tesseract` for OCR
|
|
217
|
+
- Image analysis agent: Enable `image/*`, use `openai` vision provider
|
|
218
|
+
- General assistant: Enable common types, no OCR/vision needed
|
|
219
|
+
|
|
142
220
|
## Spec Document System (Advanced)
|
|
143
221
|
|
|
144
222
|
For organizations managing multiple agents, there's a **Spec Document System** that provides:
|
|
@@ -210,14 +288,41 @@ Be conversational and helpful. Guide users through the process step by step.
|
|
|
210
288
|
|
|
211
289
|
**Be conservative with ambiguous requests.** When in doubt, ask for clarification rather than taking action.
|
|
212
290
|
|
|
213
|
-
## IMPORTANT: Always
|
|
291
|
+
## IMPORTANT: Always Provide Clear Summaries
|
|
214
292
|
|
|
215
|
-
After
|
|
216
|
-
|
|
293
|
+
### After Individual Actions
|
|
294
|
+
After completing any actions (creating agents, updating prompts, adding tools, etc.), briefly mention what you did:
|
|
217
295
|
- "I've updated the system prompt to make the agent respond in pirate speak."
|
|
218
|
-
- "I've added
|
|
296
|
+
- "I've added the search_orders tool to the agent."
|
|
297
|
+
|
|
298
|
+
### After Completing a Task or Request
|
|
299
|
+
When you've finished fulfilling a user's request (especially multi-step tasks), ALWAYS provide a comprehensive **Task Completion Summary** that includes:
|
|
219
300
|
|
|
220
|
-
|
|
301
|
+
1. **What was accomplished**: A clear statement of what you achieved
|
|
302
|
+
2. **Changes made**: List the specific modifications (agents created, prompts updated, tools added, etc.)
|
|
303
|
+
3. **Current state**: Brief description of how the agent/system is now configured
|
|
304
|
+
4. **Next steps** (if applicable): Suggestions for testing or further improvements
|
|
305
|
+
|
|
306
|
+
**Example Task Completion Summary:**
|
|
307
|
+
```
|
|
308
|
+
## ✅ Task Complete
|
|
309
|
+
|
|
310
|
+
I've set up your Customer Support system with the following:
|
|
311
|
+
|
|
312
|
+
**Created:**
|
|
313
|
+
- Customer Support Bot (customer-support) - Main entry point
|
|
314
|
+
- Billing Specialist (billing-specialist) - Handles payment questions
|
|
315
|
+
- Tech Support (tech-support) - Handles technical issues
|
|
316
|
+
|
|
317
|
+
**Configured:**
|
|
318
|
+
- Added sub-agent routing to the main bot
|
|
319
|
+
- Each specialist has domain-specific system prompts
|
|
320
|
+
- All agents share access to the knowledge base
|
|
321
|
+
|
|
322
|
+
**Ready to test:** Try asking the Customer Support Bot about a billing issue - it should route to the Billing Specialist.
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
This summary format helps users understand exactly what was built and how to use it.
|
|
221
326
|
|
|
222
327
|
Current agent being edited: {agent_context}
|
|
223
328
|
"""
|
|
@@ -362,6 +467,48 @@ BUILDER_TOOLS = [
|
|
|
362
467
|
},
|
|
363
468
|
},
|
|
364
469
|
},
|
|
470
|
+
{
|
|
471
|
+
"type": "function",
|
|
472
|
+
"function": {
|
|
473
|
+
"name": "configure_memory",
|
|
474
|
+
"description": "Configure advanced memory settings for the agent. Memory allows agents to remember facts about users across conversations with different scopes: 'conversation' (this chat only), 'user' (persists across chats), 'system' (shared with other agents).",
|
|
475
|
+
"parameters": {
|
|
476
|
+
"type": "object",
|
|
477
|
+
"properties": {
|
|
478
|
+
"enabled": {
|
|
479
|
+
"type": "boolean",
|
|
480
|
+
"description": "Whether memory is enabled. Default: true",
|
|
481
|
+
},
|
|
482
|
+
"default_scope": {
|
|
483
|
+
"type": "string",
|
|
484
|
+
"enum": ["conversation", "user", "system"],
|
|
485
|
+
"description": "Default scope for new memories. 'conversation' = this chat only, 'user' = persists across chats, 'system' = shared with other agents. Default: 'user'",
|
|
486
|
+
},
|
|
487
|
+
"allowed_scopes": {
|
|
488
|
+
"type": "array",
|
|
489
|
+
"items": {"type": "string", "enum": ["conversation", "user", "system"]},
|
|
490
|
+
"description": "Which scopes the agent can use. Default: all scopes",
|
|
491
|
+
},
|
|
492
|
+
"auto_recall": {
|
|
493
|
+
"type": "boolean",
|
|
494
|
+
"description": "Whether to automatically recall memories at the start of conversations. Default: true",
|
|
495
|
+
},
|
|
496
|
+
"max_memories_in_prompt": {
|
|
497
|
+
"type": "integer",
|
|
498
|
+
"description": "Maximum number of memories to include in the system prompt. Default: 50",
|
|
499
|
+
},
|
|
500
|
+
"include_system_memories": {
|
|
501
|
+
"type": "boolean",
|
|
502
|
+
"description": "Whether to include system-scoped memories from other agents. Default: true",
|
|
503
|
+
},
|
|
504
|
+
"retention_days": {
|
|
505
|
+
"type": "integer",
|
|
506
|
+
"description": "Number of days to retain memories. Null = forever. Default: null",
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
},
|
|
365
512
|
# Spec Document tools
|
|
366
513
|
{
|
|
367
514
|
"type": "function",
|
|
@@ -697,6 +844,45 @@ BUILDER_TOOLS = [
|
|
|
697
844
|
},
|
|
698
845
|
},
|
|
699
846
|
},
|
|
847
|
+
{
|
|
848
|
+
"type": "function",
|
|
849
|
+
"function": {
|
|
850
|
+
"name": "update_file_config",
|
|
851
|
+
"description": "Update the file upload and processing configuration for the agent. Controls what files users can upload and how they are processed.",
|
|
852
|
+
"parameters": {
|
|
853
|
+
"type": "object",
|
|
854
|
+
"properties": {
|
|
855
|
+
"enabled": {
|
|
856
|
+
"type": "boolean",
|
|
857
|
+
"description": "Enable or disable file uploads for this agent",
|
|
858
|
+
},
|
|
859
|
+
"max_file_size_mb": {
|
|
860
|
+
"type": "integer",
|
|
861
|
+
"description": "Maximum file size in megabytes. Default: 100",
|
|
862
|
+
},
|
|
863
|
+
"allowed_types": {
|
|
864
|
+
"type": "array",
|
|
865
|
+
"items": {"type": "string"},
|
|
866
|
+
"description": "List of allowed MIME types or patterns (e.g., 'image/*', 'application/pdf', 'text/*'). Default: ['image/*', 'application/pdf', 'text/*']",
|
|
867
|
+
},
|
|
868
|
+
"ocr_provider": {
|
|
869
|
+
"type": "string",
|
|
870
|
+
"enum": ["tesseract", "google_vision", "aws_textract", "azure_di", None],
|
|
871
|
+
"description": "OCR provider for text extraction from images/PDFs. Options: tesseract (local), google_vision, aws_textract, azure_di. Set to null to disable OCR.",
|
|
872
|
+
},
|
|
873
|
+
"vision_provider": {
|
|
874
|
+
"type": "string",
|
|
875
|
+
"enum": ["openai", "anthropic", "gemini", None],
|
|
876
|
+
"description": "AI vision provider for image understanding. Options: openai (GPT-4V), anthropic (Claude Vision), gemini. Set to null to disable vision.",
|
|
877
|
+
},
|
|
878
|
+
"enable_thumbnails": {
|
|
879
|
+
"type": "boolean",
|
|
880
|
+
"description": "Generate thumbnails for uploaded images. Default: true",
|
|
881
|
+
},
|
|
882
|
+
},
|
|
883
|
+
},
|
|
884
|
+
},
|
|
885
|
+
},
|
|
700
886
|
{
|
|
701
887
|
"type": "function",
|
|
702
888
|
"function": {
|
|
@@ -1105,6 +1291,166 @@ BUILDER_TOOLS = [
|
|
|
1105
1291
|
},
|
|
1106
1292
|
},
|
|
1107
1293
|
},
|
|
1294
|
+
# ==========================================================================
|
|
1295
|
+
# System Management Tools - Create, modify, and delete multi-agent systems
|
|
1296
|
+
# ==========================================================================
|
|
1297
|
+
{
|
|
1298
|
+
"type": "function",
|
|
1299
|
+
"function": {
|
|
1300
|
+
"name": "create_system",
|
|
1301
|
+
"description": "Create a new multi-agent system. A system groups related agents that work together, with one agent as the entry point that handles initial requests.",
|
|
1302
|
+
"parameters": {
|
|
1303
|
+
"type": "object",
|
|
1304
|
+
"properties": {
|
|
1305
|
+
"name": {
|
|
1306
|
+
"type": "string",
|
|
1307
|
+
"description": "Human-readable name for the system (e.g., 'Customer Support')",
|
|
1308
|
+
},
|
|
1309
|
+
"slug": {
|
|
1310
|
+
"type": "string",
|
|
1311
|
+
"description": "Unique identifier slug (e.g., 'customer-support'). If not provided, will be generated from name.",
|
|
1312
|
+
},
|
|
1313
|
+
"description": {
|
|
1314
|
+
"type": "string",
|
|
1315
|
+
"description": "Description of what this system does",
|
|
1316
|
+
},
|
|
1317
|
+
"entry_agent_slug": {
|
|
1318
|
+
"type": "string",
|
|
1319
|
+
"description": "Slug of the agent that handles initial requests (entry point)",
|
|
1320
|
+
},
|
|
1321
|
+
"auto_discover": {
|
|
1322
|
+
"type": "boolean",
|
|
1323
|
+
"description": "If true, automatically discover and add all sub-agents reachable from the entry agent. Default: true",
|
|
1324
|
+
},
|
|
1325
|
+
},
|
|
1326
|
+
"required": ["name", "entry_agent_slug"],
|
|
1327
|
+
},
|
|
1328
|
+
},
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
"type": "function",
|
|
1332
|
+
"function": {
|
|
1333
|
+
"name": "add_agent_to_system",
|
|
1334
|
+
"description": "Add an agent to a multi-agent system as a member.",
|
|
1335
|
+
"parameters": {
|
|
1336
|
+
"type": "object",
|
|
1337
|
+
"properties": {
|
|
1338
|
+
"system_slug": {
|
|
1339
|
+
"type": "string",
|
|
1340
|
+
"description": "Slug of the system to add the agent to",
|
|
1341
|
+
},
|
|
1342
|
+
"agent_slug": {
|
|
1343
|
+
"type": "string",
|
|
1344
|
+
"description": "Slug of the agent to add",
|
|
1345
|
+
},
|
|
1346
|
+
"role": {
|
|
1347
|
+
"type": "string",
|
|
1348
|
+
"enum": ["specialist", "utility", "supervisor"],
|
|
1349
|
+
"description": "Role of the agent in the system. Default: 'specialist'",
|
|
1350
|
+
},
|
|
1351
|
+
"notes": {
|
|
1352
|
+
"type": "string",
|
|
1353
|
+
"description": "Optional notes about this agent's role in the system",
|
|
1354
|
+
},
|
|
1355
|
+
},
|
|
1356
|
+
"required": ["system_slug", "agent_slug"],
|
|
1357
|
+
},
|
|
1358
|
+
},
|
|
1359
|
+
},
|
|
1360
|
+
{
|
|
1361
|
+
"type": "function",
|
|
1362
|
+
"function": {
|
|
1363
|
+
"name": "remove_agent_from_system",
|
|
1364
|
+
"description": "Remove an agent from a multi-agent system. Cannot remove the entry point agent.",
|
|
1365
|
+
"parameters": {
|
|
1366
|
+
"type": "object",
|
|
1367
|
+
"properties": {
|
|
1368
|
+
"system_slug": {
|
|
1369
|
+
"type": "string",
|
|
1370
|
+
"description": "Slug of the system to remove the agent from",
|
|
1371
|
+
},
|
|
1372
|
+
"agent_slug": {
|
|
1373
|
+
"type": "string",
|
|
1374
|
+
"description": "Slug of the agent to remove",
|
|
1375
|
+
},
|
|
1376
|
+
},
|
|
1377
|
+
"required": ["system_slug", "agent_slug"],
|
|
1378
|
+
},
|
|
1379
|
+
},
|
|
1380
|
+
},
|
|
1381
|
+
{
|
|
1382
|
+
"type": "function",
|
|
1383
|
+
"function": {
|
|
1384
|
+
"name": "update_system_config",
|
|
1385
|
+
"description": "Update a multi-agent system's configuration including name, description, entry agent, and shared knowledge.",
|
|
1386
|
+
"parameters": {
|
|
1387
|
+
"type": "object",
|
|
1388
|
+
"properties": {
|
|
1389
|
+
"system_slug": {
|
|
1390
|
+
"type": "string",
|
|
1391
|
+
"description": "Slug of the system to update",
|
|
1392
|
+
},
|
|
1393
|
+
"name": {
|
|
1394
|
+
"type": "string",
|
|
1395
|
+
"description": "New name for the system",
|
|
1396
|
+
},
|
|
1397
|
+
"description": {
|
|
1398
|
+
"type": "string",
|
|
1399
|
+
"description": "New description for the system",
|
|
1400
|
+
},
|
|
1401
|
+
"entry_agent_slug": {
|
|
1402
|
+
"type": "string",
|
|
1403
|
+
"description": "Slug of the new entry point agent (must be a member of the system)",
|
|
1404
|
+
},
|
|
1405
|
+
"is_active": {
|
|
1406
|
+
"type": "boolean",
|
|
1407
|
+
"description": "Whether the system is active",
|
|
1408
|
+
},
|
|
1409
|
+
"shared_knowledge": {
|
|
1410
|
+
"type": "array",
|
|
1411
|
+
"description": "Shared knowledge items for all agents in the system. Each item has: key, title, content, inject_as ('system'|'context'|'knowledge'), priority, enabled",
|
|
1412
|
+
"items": {
|
|
1413
|
+
"type": "object",
|
|
1414
|
+
"properties": {
|
|
1415
|
+
"key": {"type": "string", "description": "Unique key for this knowledge item"},
|
|
1416
|
+
"title": {"type": "string", "description": "Title/name of the knowledge"},
|
|
1417
|
+
"content": {"type": "string", "description": "The knowledge content"},
|
|
1418
|
+
"inject_as": {
|
|
1419
|
+
"type": "string",
|
|
1420
|
+
"enum": ["system", "context", "knowledge"],
|
|
1421
|
+
"description": "How to inject: 'system' (prepend to system prompt), 'context' (add as context), 'knowledge' (RAG searchable)",
|
|
1422
|
+
},
|
|
1423
|
+
"priority": {"type": "integer", "description": "Priority order (lower = higher priority)"},
|
|
1424
|
+
"enabled": {"type": "boolean", "description": "Whether this knowledge is active"},
|
|
1425
|
+
},
|
|
1426
|
+
},
|
|
1427
|
+
},
|
|
1428
|
+
},
|
|
1429
|
+
"required": ["system_slug"],
|
|
1430
|
+
},
|
|
1431
|
+
},
|
|
1432
|
+
},
|
|
1433
|
+
{
|
|
1434
|
+
"type": "function",
|
|
1435
|
+
"function": {
|
|
1436
|
+
"name": "delete_system",
|
|
1437
|
+
"description": "Delete a multi-agent system. This removes the system and all its member associations, but does NOT delete the agents themselves.",
|
|
1438
|
+
"parameters": {
|
|
1439
|
+
"type": "object",
|
|
1440
|
+
"properties": {
|
|
1441
|
+
"system_slug": {
|
|
1442
|
+
"type": "string",
|
|
1443
|
+
"description": "Slug of the system to delete",
|
|
1444
|
+
},
|
|
1445
|
+
"confirm": {
|
|
1446
|
+
"type": "boolean",
|
|
1447
|
+
"description": "Must be true to confirm deletion",
|
|
1448
|
+
},
|
|
1449
|
+
},
|
|
1450
|
+
"required": ["system_slug", "confirm"],
|
|
1451
|
+
},
|
|
1452
|
+
},
|
|
1453
|
+
},
|
|
1108
1454
|
]
|
|
1109
1455
|
|
|
1110
1456
|
|
|
@@ -1124,12 +1470,16 @@ class BuilderAgentRuntime(AgentRuntime):
|
|
|
1124
1470
|
"""Execute the builder agent with agentic loop."""
|
|
1125
1471
|
# Get the agent being edited from context
|
|
1126
1472
|
# Check both metadata (from frontend) and params (from API)
|
|
1127
|
-
|
|
1473
|
+
initial_agent_id = ctx.metadata.get("agent_id") or ctx.params.get("agent_id")
|
|
1128
1474
|
agent_context = "No agent selected. Ask the user what kind of agent they want to create."
|
|
1129
1475
|
|
|
1130
|
-
|
|
1476
|
+
# Use a mutable container so switch_to_agent can update the current agent
|
|
1477
|
+
# This allows the builder to work on different agents during a single run
|
|
1478
|
+
current_agent = {"id": initial_agent_id}
|
|
1479
|
+
|
|
1480
|
+
if initial_agent_id:
|
|
1131
1481
|
try:
|
|
1132
|
-
agent = await sync_to_async(AgentDefinition.objects.get)(id=
|
|
1482
|
+
agent = await sync_to_async(AgentDefinition.objects.get)(id=initial_agent_id)
|
|
1133
1483
|
config = await sync_to_async(agent.get_effective_config)()
|
|
1134
1484
|
agent_context = f"""
|
|
1135
1485
|
Agent: {agent.name} ({agent.slug})
|
|
@@ -1154,11 +1504,13 @@ Knowledge: {len(config.get('knowledge', []))} sources
|
|
|
1154
1504
|
llm = get_llm_client_for_model(model)
|
|
1155
1505
|
|
|
1156
1506
|
# Create tool executor function for the agentic loop
|
|
1507
|
+
# Uses current_agent dict so switch_to_agent can update which agent we're working on
|
|
1157
1508
|
async def execute_tool(tool_name: str, tool_args: dict) -> dict:
|
|
1158
|
-
return await self._execute_tool(
|
|
1509
|
+
return await self._execute_tool(current_agent, tool_name, tool_args, ctx)
|
|
1159
1510
|
|
|
1160
1511
|
# Use the shared agentic loop
|
|
1161
1512
|
# Note: agentic_loop emits ASSISTANT_MESSAGE for the final response
|
|
1513
|
+
# ensure_final_response=True ensures a summary is generated if tools were used
|
|
1162
1514
|
result = await run_agentic_loop(
|
|
1163
1515
|
llm=llm,
|
|
1164
1516
|
messages=messages,
|
|
@@ -1168,6 +1520,7 @@ Knowledge: {len(config.get('knowledge', []))} sources
|
|
|
1168
1520
|
model=model,
|
|
1169
1521
|
max_iterations=10,
|
|
1170
1522
|
temperature=0.7,
|
|
1523
|
+
ensure_final_response=True,
|
|
1171
1524
|
)
|
|
1172
1525
|
|
|
1173
1526
|
return RunResult(
|
|
@@ -1177,17 +1530,28 @@ Knowledge: {len(config.get('knowledge', []))} sources
|
|
|
1177
1530
|
|
|
1178
1531
|
async def _execute_tool(
|
|
1179
1532
|
self,
|
|
1180
|
-
|
|
1533
|
+
current_agent: dict,
|
|
1181
1534
|
tool_name: str,
|
|
1182
1535
|
args: dict,
|
|
1183
1536
|
ctx: RunContext,
|
|
1184
1537
|
) -> dict:
|
|
1185
|
-
"""Execute a builder tool.
|
|
1538
|
+
"""Execute a builder tool.
|
|
1539
|
+
|
|
1540
|
+
Args:
|
|
1541
|
+
current_agent: Mutable dict with 'id' key tracking the current agent.
|
|
1542
|
+
This allows switch_to_agent to update which agent we're working on.
|
|
1543
|
+
tool_name: Name of the tool to execute
|
|
1544
|
+
args: Tool arguments
|
|
1545
|
+
ctx: Run context
|
|
1546
|
+
"""
|
|
1186
1547
|
# Import here to avoid circular imports
|
|
1187
1548
|
from django_agent_runtime.models import AgentKnowledge, DynamicTool
|
|
1188
1549
|
from django_agent_runtime.dynamic_tools.scanner import ProjectScanner
|
|
1189
1550
|
from django_agent_runtime.dynamic_tools.generator import ToolGenerator
|
|
1190
1551
|
|
|
1552
|
+
agent_id = current_agent.get("id")
|
|
1553
|
+
logger.info(f"_execute_tool: {tool_name} with current_agent={current_agent}")
|
|
1554
|
+
|
|
1191
1555
|
# Tools that don't require an agent
|
|
1192
1556
|
if tool_name == "scan_project_for_tools":
|
|
1193
1557
|
return await self._scan_project_for_tools(args, ctx)
|
|
@@ -1199,11 +1563,21 @@ Knowledge: {len(config.get('knowledge', []))} sources
|
|
|
1199
1563
|
return await self._get_function_details(args, ctx)
|
|
1200
1564
|
|
|
1201
1565
|
if tool_name == "create_agent":
|
|
1202
|
-
|
|
1566
|
+
result = await self._create_agent(args, ctx)
|
|
1567
|
+
# If agent was created successfully, update current_agent to point to it
|
|
1568
|
+
if result.get("success") and result.get("agent_id"):
|
|
1569
|
+
current_agent["id"] = result["agent_id"]
|
|
1570
|
+
logger.info(f"Updated current_agent to newly created agent: {result['agent_id']}")
|
|
1571
|
+
return result
|
|
1203
1572
|
|
|
1204
1573
|
# UI Control tools - these emit special events to control the frontend
|
|
1205
1574
|
if tool_name == "switch_to_agent":
|
|
1206
|
-
|
|
1575
|
+
result = await self._switch_to_agent(args, ctx)
|
|
1576
|
+
# If switch was successful, update current_agent to the new agent
|
|
1577
|
+
if result.get("success") and result.get("agent_id"):
|
|
1578
|
+
current_agent["id"] = result["agent_id"]
|
|
1579
|
+
logger.info(f"Updated current_agent to switched agent: {result['agent_id']}")
|
|
1580
|
+
return result
|
|
1207
1581
|
|
|
1208
1582
|
if tool_name == "switch_to_system":
|
|
1209
1583
|
return await self._switch_to_system(args, ctx)
|
|
@@ -1217,6 +1591,22 @@ Knowledge: {len(config.get('knowledge', []))} sources
|
|
|
1217
1591
|
if tool_name == "get_system_details":
|
|
1218
1592
|
return await self._get_system_details(args, ctx)
|
|
1219
1593
|
|
|
1594
|
+
# System management tools
|
|
1595
|
+
if tool_name == "create_system":
|
|
1596
|
+
return await self._create_system(args, ctx)
|
|
1597
|
+
|
|
1598
|
+
if tool_name == "add_agent_to_system":
|
|
1599
|
+
return await self._add_agent_to_system(args, ctx)
|
|
1600
|
+
|
|
1601
|
+
if tool_name == "remove_agent_from_system":
|
|
1602
|
+
return await self._remove_agent_from_system(args, ctx)
|
|
1603
|
+
|
|
1604
|
+
if tool_name == "update_system_config":
|
|
1605
|
+
return await self._update_system_config(args, ctx)
|
|
1606
|
+
|
|
1607
|
+
if tool_name == "delete_system":
|
|
1608
|
+
return await self._delete_system(args, ctx)
|
|
1609
|
+
|
|
1220
1610
|
# Tools that require an agent
|
|
1221
1611
|
if not agent_id:
|
|
1222
1612
|
return {"error": "No agent selected. Please create an agent first or use create_agent tool."}
|
|
@@ -1251,20 +1641,56 @@ Knowledge: {len(config.get('knowledge', []))} sources
|
|
|
1251
1641
|
return {"success": True, "message": "Agent name updated"}
|
|
1252
1642
|
|
|
1253
1643
|
elif tool_name == "get_agent_spec":
|
|
1644
|
+
# Get spec from linked SpecDocument
|
|
1645
|
+
from django_agent_runtime.models import SpecDocument
|
|
1646
|
+
spec_doc = await sync_to_async(
|
|
1647
|
+
lambda: SpecDocument.objects.filter(linked_agent=agent).first()
|
|
1648
|
+
)()
|
|
1649
|
+
spec_content = spec_doc.content if spec_doc else ""
|
|
1254
1650
|
return {
|
|
1255
|
-
"spec":
|
|
1256
|
-
"has_spec": bool(
|
|
1651
|
+
"spec": spec_content,
|
|
1652
|
+
"has_spec": bool(spec_content),
|
|
1653
|
+
"spec_document_id": str(spec_doc.id) if spec_doc else None,
|
|
1654
|
+
"spec_document_title": spec_doc.title if spec_doc else None,
|
|
1257
1655
|
"message": "The agent spec describes intended behavior in plain English, separate from the technical system prompt.",
|
|
1258
1656
|
}
|
|
1259
1657
|
|
|
1260
1658
|
elif tool_name == "update_agent_spec":
|
|
1261
|
-
|
|
1262
|
-
|
|
1659
|
+
# Update or create linked SpecDocument
|
|
1660
|
+
from django_agent_runtime.models import SpecDocument
|
|
1661
|
+
logger.info(f"update_agent_spec called for agent {agent.id} ({agent.slug})")
|
|
1662
|
+
|
|
1663
|
+
spec_content = args["spec"]
|
|
1664
|
+
|
|
1665
|
+
# Find or create the spec document for this agent
|
|
1666
|
+
def update_or_create_spec():
|
|
1667
|
+
spec_doc = SpecDocument.objects.filter(linked_agent=agent).first()
|
|
1668
|
+
if spec_doc:
|
|
1669
|
+
# Update existing document
|
|
1670
|
+
spec_doc.content = spec_content
|
|
1671
|
+
spec_doc.save() # This auto-creates a version
|
|
1672
|
+
return spec_doc, False
|
|
1673
|
+
else:
|
|
1674
|
+
# Create new document
|
|
1675
|
+
spec_doc = SpecDocument.objects.create(
|
|
1676
|
+
title=f"{agent.name} Specification",
|
|
1677
|
+
content=spec_content,
|
|
1678
|
+
linked_agent=agent,
|
|
1679
|
+
owner=agent.owner,
|
|
1680
|
+
)
|
|
1681
|
+
return spec_doc, True
|
|
1682
|
+
|
|
1683
|
+
spec_doc, created = await sync_to_async(update_or_create_spec)()
|
|
1684
|
+
logger.info(f"{'Created' if created else 'Updated'} spec document {spec_doc.id} for agent {agent.id}")
|
|
1685
|
+
|
|
1263
1686
|
await create_revision(agent, comment="Updated agent specification")
|
|
1264
1687
|
return {
|
|
1265
1688
|
"success": True,
|
|
1266
|
-
"message": "Agent specification updated",
|
|
1267
|
-
"
|
|
1689
|
+
"message": f"Agent specification {'created' if created else 'updated'} for '{agent.name}' ({agent.slug})",
|
|
1690
|
+
"agent_id": str(agent.id),
|
|
1691
|
+
"agent_slug": agent.slug,
|
|
1692
|
+
"spec_document_id": str(spec_doc.id),
|
|
1693
|
+
"spec_preview": spec_content[:200] + "..." if len(spec_content) > 200 else spec_content,
|
|
1268
1694
|
}
|
|
1269
1695
|
|
|
1270
1696
|
elif tool_name == "update_model_settings":
|
|
@@ -1301,10 +1727,70 @@ Knowledge: {len(config.get('knowledge', []))} sources
|
|
|
1301
1727
|
if version:
|
|
1302
1728
|
extra = version.extra_config or {}
|
|
1303
1729
|
enabled = extra.get("memory_enabled", True) # Default is True
|
|
1730
|
+
default_scope = extra.get("memory_default_scope", "user")
|
|
1731
|
+
allowed_scopes = extra.get("memory_allowed_scopes", ["conversation", "user", "system"])
|
|
1732
|
+
auto_recall = extra.get("memory_auto_recall", True)
|
|
1733
|
+
max_in_prompt = extra.get("memory_max_in_prompt", 50)
|
|
1734
|
+
include_system = extra.get("memory_include_system", True)
|
|
1735
|
+
retention_days = extra.get("memory_retention_days", None)
|
|
1304
1736
|
return {
|
|
1305
1737
|
"memory_enabled": enabled,
|
|
1738
|
+
"default_scope": default_scope,
|
|
1739
|
+
"allowed_scopes": allowed_scopes,
|
|
1740
|
+
"auto_recall": auto_recall,
|
|
1741
|
+
"max_memories_in_prompt": max_in_prompt,
|
|
1742
|
+
"include_system_memories": include_system,
|
|
1743
|
+
"retention_days": retention_days,
|
|
1306
1744
|
"message": f"Memory is {'enabled' if enabled else 'disabled'} for this agent",
|
|
1307
|
-
"note": "When enabled, the agent has
|
|
1745
|
+
"note": "When enabled, the agent has 'remember', 'recall', and 'forget' tools. Memory scopes: 'conversation' (this chat), 'user' (persists), 'system' (shared).",
|
|
1746
|
+
}
|
|
1747
|
+
return {"error": "No active version found"}
|
|
1748
|
+
|
|
1749
|
+
elif tool_name == "configure_memory":
|
|
1750
|
+
if version:
|
|
1751
|
+
if version.extra_config is None:
|
|
1752
|
+
version.extra_config = {}
|
|
1753
|
+
|
|
1754
|
+
changes = []
|
|
1755
|
+
if "enabled" in args:
|
|
1756
|
+
version.extra_config["memory_enabled"] = args["enabled"]
|
|
1757
|
+
changes.append(f"enabled={args['enabled']}")
|
|
1758
|
+
if "default_scope" in args:
|
|
1759
|
+
version.extra_config["memory_default_scope"] = args["default_scope"]
|
|
1760
|
+
changes.append(f"default_scope={args['default_scope']}")
|
|
1761
|
+
if "allowed_scopes" in args:
|
|
1762
|
+
version.extra_config["memory_allowed_scopes"] = args["allowed_scopes"]
|
|
1763
|
+
changes.append(f"allowed_scopes={args['allowed_scopes']}")
|
|
1764
|
+
if "auto_recall" in args:
|
|
1765
|
+
version.extra_config["memory_auto_recall"] = args["auto_recall"]
|
|
1766
|
+
changes.append(f"auto_recall={args['auto_recall']}")
|
|
1767
|
+
if "max_memories_in_prompt" in args:
|
|
1768
|
+
version.extra_config["memory_max_in_prompt"] = args["max_memories_in_prompt"]
|
|
1769
|
+
changes.append(f"max_memories={args['max_memories_in_prompt']}")
|
|
1770
|
+
if "include_system_memories" in args:
|
|
1771
|
+
version.extra_config["memory_include_system"] = args["include_system_memories"]
|
|
1772
|
+
changes.append(f"include_system={args['include_system_memories']}")
|
|
1773
|
+
if "retention_days" in args:
|
|
1774
|
+
version.extra_config["memory_retention_days"] = args["retention_days"]
|
|
1775
|
+
changes.append(f"retention_days={args['retention_days']}")
|
|
1776
|
+
|
|
1777
|
+
await sync_to_async(version.save)()
|
|
1778
|
+
await create_revision(agent, comment=f"Memory config: {', '.join(changes)}")
|
|
1779
|
+
|
|
1780
|
+
# Return current config
|
|
1781
|
+
extra = version.extra_config
|
|
1782
|
+
return {
|
|
1783
|
+
"success": True,
|
|
1784
|
+
"message": f"Memory configuration updated: {', '.join(changes)}",
|
|
1785
|
+
"config": {
|
|
1786
|
+
"enabled": extra.get("memory_enabled", True),
|
|
1787
|
+
"default_scope": extra.get("memory_default_scope", "user"),
|
|
1788
|
+
"allowed_scopes": extra.get("memory_allowed_scopes", ["conversation", "user", "system"]),
|
|
1789
|
+
"auto_recall": extra.get("memory_auto_recall", True),
|
|
1790
|
+
"max_memories_in_prompt": extra.get("memory_max_in_prompt", 50),
|
|
1791
|
+
"include_system_memories": extra.get("memory_include_system", True),
|
|
1792
|
+
"retention_days": extra.get("memory_retention_days", None),
|
|
1793
|
+
},
|
|
1308
1794
|
}
|
|
1309
1795
|
return {"error": "No active version found"}
|
|
1310
1796
|
|
|
@@ -1374,6 +1860,9 @@ Knowledge: {len(config.get('knowledge', []))} sources
|
|
|
1374
1860
|
elif tool_name == "update_rag_config":
|
|
1375
1861
|
return await self._update_rag_config(agent, args, ctx)
|
|
1376
1862
|
|
|
1863
|
+
elif tool_name == "update_file_config":
|
|
1864
|
+
return await self._update_file_config(agent, args, ctx)
|
|
1865
|
+
|
|
1377
1866
|
elif tool_name == "add_tool_from_function":
|
|
1378
1867
|
return await self._add_tool_from_function(agent, args, ctx)
|
|
1379
1868
|
|
|
@@ -1900,6 +2389,47 @@ Knowledge: {len(config.get('knowledge', []))} sources
|
|
|
1900
2389
|
logger.exception("Error updating RAG config")
|
|
1901
2390
|
return {"error": str(e)}
|
|
1902
2391
|
|
|
2392
|
+
async def _update_file_config(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
2393
|
+
"""Update the file upload and processing configuration for the agent."""
|
|
2394
|
+
try:
|
|
2395
|
+
# Get current config with defaults
|
|
2396
|
+
current_config = agent.file_config or {
|
|
2397
|
+
"enabled": False,
|
|
2398
|
+
"max_file_size_mb": 100,
|
|
2399
|
+
"allowed_types": ["image/*", "application/pdf", "text/*"],
|
|
2400
|
+
"ocr_provider": None,
|
|
2401
|
+
"vision_provider": None,
|
|
2402
|
+
"enable_thumbnails": True,
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
# Update with provided values
|
|
2406
|
+
if "enabled" in args:
|
|
2407
|
+
current_config["enabled"] = args["enabled"]
|
|
2408
|
+
if "max_file_size_mb" in args:
|
|
2409
|
+
current_config["max_file_size_mb"] = args["max_file_size_mb"]
|
|
2410
|
+
if "allowed_types" in args:
|
|
2411
|
+
current_config["allowed_types"] = args["allowed_types"]
|
|
2412
|
+
if "ocr_provider" in args:
|
|
2413
|
+
current_config["ocr_provider"] = args["ocr_provider"]
|
|
2414
|
+
if "vision_provider" in args:
|
|
2415
|
+
current_config["vision_provider"] = args["vision_provider"]
|
|
2416
|
+
if "enable_thumbnails" in args:
|
|
2417
|
+
current_config["enable_thumbnails"] = args["enable_thumbnails"]
|
|
2418
|
+
|
|
2419
|
+
# Save
|
|
2420
|
+
agent.file_config = current_config
|
|
2421
|
+
await sync_to_async(agent.save)(update_fields=["file_config"])
|
|
2422
|
+
await create_revision(agent, comment="Updated file upload configuration")
|
|
2423
|
+
|
|
2424
|
+
return {
|
|
2425
|
+
"success": True,
|
|
2426
|
+
"message": "File upload configuration updated",
|
|
2427
|
+
"config": current_config,
|
|
2428
|
+
}
|
|
2429
|
+
except Exception as e:
|
|
2430
|
+
logger.exception("Error updating file config")
|
|
2431
|
+
return {"error": str(e)}
|
|
2432
|
+
|
|
1903
2433
|
# ==========================================================================
|
|
1904
2434
|
# Multi-Agent / Sub-Agent Tools
|
|
1905
2435
|
# ==========================================================================
|
|
@@ -2348,6 +2878,276 @@ Knowledge: {len(config.get('knowledge', []))} sources
|
|
|
2348
2878
|
logger.exception("Error getting system details")
|
|
2349
2879
|
return {"error": str(e)}
|
|
2350
2880
|
|
|
2881
|
+
# ==================== System Management Methods ====================
|
|
2882
|
+
|
|
2883
|
+
async def _create_system(self, args: dict, ctx: RunContext) -> dict:
|
|
2884
|
+
"""Create a new multi-agent system."""
|
|
2885
|
+
from django_agent_runtime.models import AgentSystem
|
|
2886
|
+
from django_agent_runtime.services.multi_agent import create_system_from_entry_agent
|
|
2887
|
+
|
|
2888
|
+
name = args.get("name")
|
|
2889
|
+
entry_agent_slug = args.get("entry_agent_slug")
|
|
2890
|
+
|
|
2891
|
+
if not name or not entry_agent_slug:
|
|
2892
|
+
return {"error": "name and entry_agent_slug are required"}
|
|
2893
|
+
|
|
2894
|
+
# Generate slug if not provided
|
|
2895
|
+
slug = args.get("slug")
|
|
2896
|
+
if not slug:
|
|
2897
|
+
slug = slugify(name)
|
|
2898
|
+
|
|
2899
|
+
description = args.get("description", "")
|
|
2900
|
+
auto_discover = args.get("auto_discover", True)
|
|
2901
|
+
|
|
2902
|
+
try:
|
|
2903
|
+
# Check if slug already exists
|
|
2904
|
+
if await sync_to_async(AgentSystem.objects.filter(slug=slug).exists)():
|
|
2905
|
+
return {"error": f"System with slug '{slug}' already exists"}
|
|
2906
|
+
|
|
2907
|
+
# Get the entry agent
|
|
2908
|
+
try:
|
|
2909
|
+
entry_agent = await sync_to_async(AgentDefinition.objects.get)(slug=entry_agent_slug)
|
|
2910
|
+
except AgentDefinition.DoesNotExist:
|
|
2911
|
+
return {"error": f"Entry agent not found: {entry_agent_slug}"}
|
|
2912
|
+
|
|
2913
|
+
# Get owner from context if available
|
|
2914
|
+
owner = getattr(ctx, 'user', None)
|
|
2915
|
+
|
|
2916
|
+
# Create the system
|
|
2917
|
+
system = await sync_to_async(create_system_from_entry_agent)(
|
|
2918
|
+
slug=slug,
|
|
2919
|
+
name=name,
|
|
2920
|
+
entry_agent=entry_agent,
|
|
2921
|
+
description=description,
|
|
2922
|
+
owner=owner,
|
|
2923
|
+
auto_discover=auto_discover,
|
|
2924
|
+
)
|
|
2925
|
+
|
|
2926
|
+
# Get member count
|
|
2927
|
+
member_count = await sync_to_async(system.members.count)()
|
|
2928
|
+
|
|
2929
|
+
return {
|
|
2930
|
+
"success": True,
|
|
2931
|
+
"message": f"Created system '{name}' with {member_count} member(s)",
|
|
2932
|
+
"system_id": str(system.id),
|
|
2933
|
+
"system_slug": system.slug,
|
|
2934
|
+
"member_count": member_count,
|
|
2935
|
+
"entry_agent_slug": entry_agent.slug,
|
|
2936
|
+
}
|
|
2937
|
+
except Exception as e:
|
|
2938
|
+
logger.exception("Error creating system")
|
|
2939
|
+
return {"error": str(e)}
|
|
2940
|
+
|
|
2941
|
+
async def _add_agent_to_system(self, args: dict, ctx: RunContext) -> dict:
|
|
2942
|
+
"""Add an agent to a multi-agent system."""
|
|
2943
|
+
from django_agent_runtime.models import AgentSystem, AgentSystemMember
|
|
2944
|
+
from django_agent_runtime.services.multi_agent import add_agent_to_system
|
|
2945
|
+
|
|
2946
|
+
system_slug = args.get("system_slug")
|
|
2947
|
+
agent_slug = args.get("agent_slug")
|
|
2948
|
+
|
|
2949
|
+
if not system_slug or not agent_slug:
|
|
2950
|
+
return {"error": "system_slug and agent_slug are required"}
|
|
2951
|
+
|
|
2952
|
+
role = args.get("role", "specialist")
|
|
2953
|
+
notes = args.get("notes", "")
|
|
2954
|
+
|
|
2955
|
+
try:
|
|
2956
|
+
# Get the system
|
|
2957
|
+
try:
|
|
2958
|
+
system = await sync_to_async(AgentSystem.objects.get)(slug=system_slug)
|
|
2959
|
+
except AgentSystem.DoesNotExist:
|
|
2960
|
+
return {"error": f"System not found: {system_slug}"}
|
|
2961
|
+
|
|
2962
|
+
# Get the agent
|
|
2963
|
+
try:
|
|
2964
|
+
agent = await sync_to_async(AgentDefinition.objects.get)(slug=agent_slug)
|
|
2965
|
+
except AgentDefinition.DoesNotExist:
|
|
2966
|
+
return {"error": f"Agent not found: {agent_slug}"}
|
|
2967
|
+
|
|
2968
|
+
# Check if agent is already a member
|
|
2969
|
+
existing = await sync_to_async(
|
|
2970
|
+
system.members.filter(agent=agent).exists
|
|
2971
|
+
)()
|
|
2972
|
+
if existing:
|
|
2973
|
+
return {"error": f"Agent '{agent_slug}' is already a member of system '{system_slug}'"}
|
|
2974
|
+
|
|
2975
|
+
# Map role string to enum
|
|
2976
|
+
role_map = {
|
|
2977
|
+
"specialist": AgentSystemMember.Role.SPECIALIST,
|
|
2978
|
+
"utility": AgentSystemMember.Role.UTILITY,
|
|
2979
|
+
"supervisor": AgentSystemMember.Role.SUPERVISOR,
|
|
2980
|
+
"entry_point": AgentSystemMember.Role.ENTRY_POINT,
|
|
2981
|
+
}
|
|
2982
|
+
role_enum = role_map.get(role, AgentSystemMember.Role.SPECIALIST)
|
|
2983
|
+
|
|
2984
|
+
# Add the agent
|
|
2985
|
+
member = await sync_to_async(add_agent_to_system)(
|
|
2986
|
+
system=system,
|
|
2987
|
+
agent=agent,
|
|
2988
|
+
role=role_enum,
|
|
2989
|
+
notes=notes,
|
|
2990
|
+
)
|
|
2991
|
+
|
|
2992
|
+
return {
|
|
2993
|
+
"success": True,
|
|
2994
|
+
"message": f"Added agent '{agent_slug}' to system '{system_slug}' as {role}",
|
|
2995
|
+
"member_id": str(member.id),
|
|
2996
|
+
"agent_slug": agent_slug,
|
|
2997
|
+
"role": role,
|
|
2998
|
+
}
|
|
2999
|
+
except Exception as e:
|
|
3000
|
+
logger.exception("Error adding agent to system")
|
|
3001
|
+
return {"error": str(e)}
|
|
3002
|
+
|
|
3003
|
+
async def _remove_agent_from_system(self, args: dict, ctx: RunContext) -> dict:
|
|
3004
|
+
"""Remove an agent from a multi-agent system."""
|
|
3005
|
+
from django_agent_runtime.models import AgentSystem
|
|
3006
|
+
|
|
3007
|
+
system_slug = args.get("system_slug")
|
|
3008
|
+
agent_slug = args.get("agent_slug")
|
|
3009
|
+
|
|
3010
|
+
if not system_slug or not agent_slug:
|
|
3011
|
+
return {"error": "system_slug and agent_slug are required"}
|
|
3012
|
+
|
|
3013
|
+
try:
|
|
3014
|
+
# Get the system
|
|
3015
|
+
try:
|
|
3016
|
+
system = await sync_to_async(AgentSystem.objects.get)(slug=system_slug)
|
|
3017
|
+
except AgentSystem.DoesNotExist:
|
|
3018
|
+
return {"error": f"System not found: {system_slug}"}
|
|
3019
|
+
|
|
3020
|
+
# Get the agent
|
|
3021
|
+
try:
|
|
3022
|
+
agent = await sync_to_async(AgentDefinition.objects.get)(slug=agent_slug)
|
|
3023
|
+
except AgentDefinition.DoesNotExist:
|
|
3024
|
+
return {"error": f"Agent not found: {agent_slug}"}
|
|
3025
|
+
|
|
3026
|
+
# Check if this is the entry agent
|
|
3027
|
+
entry_agent = await sync_to_async(lambda: system.entry_agent)()
|
|
3028
|
+
if entry_agent and entry_agent.slug == agent_slug:
|
|
3029
|
+
return {"error": f"Cannot remove entry agent '{agent_slug}'. Change the entry agent first."}
|
|
3030
|
+
|
|
3031
|
+
# Find and delete the membership
|
|
3032
|
+
member = await sync_to_async(
|
|
3033
|
+
system.members.filter(agent=agent).first
|
|
3034
|
+
)()
|
|
3035
|
+
if not member:
|
|
3036
|
+
return {"error": f"Agent '{agent_slug}' is not a member of system '{system_slug}'"}
|
|
3037
|
+
|
|
3038
|
+
await sync_to_async(member.delete)()
|
|
3039
|
+
|
|
3040
|
+
return {
|
|
3041
|
+
"success": True,
|
|
3042
|
+
"message": f"Removed agent '{agent_slug}' from system '{system_slug}'",
|
|
3043
|
+
}
|
|
3044
|
+
except Exception as e:
|
|
3045
|
+
logger.exception("Error removing agent from system")
|
|
3046
|
+
return {"error": str(e)}
|
|
3047
|
+
|
|
3048
|
+
async def _update_system_config(self, args: dict, ctx: RunContext) -> dict:
|
|
3049
|
+
"""Update a multi-agent system's configuration."""
|
|
3050
|
+
from django_agent_runtime.models import AgentSystem
|
|
3051
|
+
|
|
3052
|
+
system_slug = args.get("system_slug")
|
|
3053
|
+
if not system_slug:
|
|
3054
|
+
return {"error": "system_slug is required"}
|
|
3055
|
+
|
|
3056
|
+
try:
|
|
3057
|
+
# Get the system
|
|
3058
|
+
try:
|
|
3059
|
+
system = await sync_to_async(AgentSystem.objects.get)(slug=system_slug)
|
|
3060
|
+
except AgentSystem.DoesNotExist:
|
|
3061
|
+
return {"error": f"System not found: {system_slug}"}
|
|
3062
|
+
|
|
3063
|
+
changes = []
|
|
3064
|
+
|
|
3065
|
+
# Update name
|
|
3066
|
+
if "name" in args:
|
|
3067
|
+
system.name = args["name"]
|
|
3068
|
+
changes.append(f"name='{args['name']}'")
|
|
3069
|
+
|
|
3070
|
+
# Update description
|
|
3071
|
+
if "description" in args:
|
|
3072
|
+
system.description = args["description"]
|
|
3073
|
+
changes.append("description updated")
|
|
3074
|
+
|
|
3075
|
+
# Update is_active
|
|
3076
|
+
if "is_active" in args:
|
|
3077
|
+
system.is_active = args["is_active"]
|
|
3078
|
+
changes.append(f"is_active={args['is_active']}")
|
|
3079
|
+
|
|
3080
|
+
# Update entry agent
|
|
3081
|
+
if "entry_agent_slug" in args:
|
|
3082
|
+
new_entry_slug = args["entry_agent_slug"]
|
|
3083
|
+
try:
|
|
3084
|
+
new_entry = await sync_to_async(AgentDefinition.objects.get)(slug=new_entry_slug)
|
|
3085
|
+
except AgentDefinition.DoesNotExist:
|
|
3086
|
+
return {"error": f"Entry agent not found: {new_entry_slug}"}
|
|
3087
|
+
|
|
3088
|
+
# Verify the new entry agent is a member
|
|
3089
|
+
is_member = await sync_to_async(
|
|
3090
|
+
system.members.filter(agent=new_entry).exists
|
|
3091
|
+
)()
|
|
3092
|
+
if not is_member:
|
|
3093
|
+
return {"error": f"Agent '{new_entry_slug}' must be a member of the system before becoming entry agent"}
|
|
3094
|
+
|
|
3095
|
+
system.entry_agent = new_entry
|
|
3096
|
+
changes.append(f"entry_agent='{new_entry_slug}'")
|
|
3097
|
+
|
|
3098
|
+
# Update shared knowledge
|
|
3099
|
+
if "shared_knowledge" in args:
|
|
3100
|
+
system.shared_knowledge = args["shared_knowledge"]
|
|
3101
|
+
changes.append(f"shared_knowledge ({len(args['shared_knowledge'])} items)")
|
|
3102
|
+
|
|
3103
|
+
if not changes:
|
|
3104
|
+
return {"message": "No changes specified"}
|
|
3105
|
+
|
|
3106
|
+
await sync_to_async(system.save)()
|
|
3107
|
+
|
|
3108
|
+
return {
|
|
3109
|
+
"success": True,
|
|
3110
|
+
"message": f"Updated system '{system_slug}': {', '.join(changes)}",
|
|
3111
|
+
"changes": changes,
|
|
3112
|
+
}
|
|
3113
|
+
except Exception as e:
|
|
3114
|
+
logger.exception("Error updating system config")
|
|
3115
|
+
return {"error": str(e)}
|
|
3116
|
+
|
|
3117
|
+
async def _delete_system(self, args: dict, ctx: RunContext) -> dict:
|
|
3118
|
+
"""Delete a multi-agent system."""
|
|
3119
|
+
from django_agent_runtime.models import AgentSystem
|
|
3120
|
+
|
|
3121
|
+
system_slug = args.get("system_slug")
|
|
3122
|
+
confirm = args.get("confirm", False)
|
|
3123
|
+
|
|
3124
|
+
if not system_slug:
|
|
3125
|
+
return {"error": "system_slug is required"}
|
|
3126
|
+
|
|
3127
|
+
if not confirm:
|
|
3128
|
+
return {"error": "Must set confirm=true to delete a system"}
|
|
3129
|
+
|
|
3130
|
+
try:
|
|
3131
|
+
# Get the system
|
|
3132
|
+
try:
|
|
3133
|
+
system = await sync_to_async(AgentSystem.objects.get)(slug=system_slug)
|
|
3134
|
+
except AgentSystem.DoesNotExist:
|
|
3135
|
+
return {"error": f"System not found: {system_slug}"}
|
|
3136
|
+
|
|
3137
|
+
system_name = system.name
|
|
3138
|
+
member_count = await sync_to_async(system.members.count)()
|
|
3139
|
+
|
|
3140
|
+
# Delete the system (cascades to members, versions, snapshots)
|
|
3141
|
+
await sync_to_async(system.delete)()
|
|
3142
|
+
|
|
3143
|
+
return {
|
|
3144
|
+
"success": True,
|
|
3145
|
+
"message": f"Deleted system '{system_name}' ({system_slug}) with {member_count} member(s). Agents were NOT deleted.",
|
|
3146
|
+
}
|
|
3147
|
+
except Exception as e:
|
|
3148
|
+
logger.exception("Error deleting system")
|
|
3149
|
+
return {"error": str(e)}
|
|
3150
|
+
|
|
2351
3151
|
# ==================== Spec Document Methods ====================
|
|
2352
3152
|
|
|
2353
3153
|
async def _list_spec_documents(self, agent, args: dict, ctx: RunContext) -> dict:
|