django-agent-studio 0.1.0__py3-none-any.whl → 0.1.5__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/__init__.py +29 -0
- django_agent_studio/agents/builder.py +1994 -0
- django_agent_studio/agents/dynamic.py +383 -0
- django_agent_studio/migrations/0001_initial.py +63 -0
- django_agent_studio/migrations/__init__.py +0 -0
- django_agent_studio/models/__init__.py +18 -0
- django_agent_studio/models/permissions.py +191 -0
- django_agent_studio/services/__init__.py +14 -0
- django_agent_studio/services/permissions.py +228 -0
- django_agent_studio/static/agent-frontend/chat-widget.css +48 -0
- django_agent_studio/static/agent-frontend/chat-widget.js +119 -100
- django_agent_studio/templates/django_agent_studio/base.html +3 -2
- django_agent_studio/templates/django_agent_studio/builder.html +69 -10
- {django_agent_studio-0.1.0.dist-info → django_agent_studio-0.1.5.dist-info}/METADATA +1 -1
- {django_agent_studio-0.1.0.dist-info → django_agent_studio-0.1.5.dist-info}/RECORD +17 -8
- {django_agent_studio-0.1.0.dist-info → django_agent_studio-0.1.5.dist-info}/WHEEL +1 -1
- {django_agent_studio-0.1.0.dist-info → django_agent_studio-0.1.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1994 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BuilderAgentRuntime - The agent that helps users build and customize other agents.
|
|
3
|
+
|
|
4
|
+
This is the "right pane" agent in the studio interface that guides users
|
|
5
|
+
through creating and modifying their custom agents.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from asgiref.sync import sync_to_async
|
|
13
|
+
from agent_runtime_core.registry import AgentRuntime
|
|
14
|
+
from agent_runtime_core.interfaces import RunContext, RunResult, EventType
|
|
15
|
+
from agent_runtime_core.agentic_loop import run_agentic_loop
|
|
16
|
+
from django_agent_runtime.runtime.llm import get_llm_client_for_model, DEFAULT_MODEL
|
|
17
|
+
from django_agent_runtime.models import AgentDefinition, AgentVersion, AgentRevision
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def create_revision(agent: AgentDefinition, comment: str = "", user=None) -> AgentRevision:
|
|
23
|
+
"""
|
|
24
|
+
Create a new revision for an agent after a change.
|
|
25
|
+
|
|
26
|
+
This should be called after any modification to the agent's configuration,
|
|
27
|
+
tools, or knowledge sources.
|
|
28
|
+
"""
|
|
29
|
+
return await sync_to_async(AgentRevision.create_from_agent)(agent, comment=comment, user=user)
|
|
30
|
+
|
|
31
|
+
BUILDER_SYSTEM_PROMPT = """You are an AI Agent Builder assistant. Your role is to help users create and customize their own AI agents (similar to custom GPTs).
|
|
32
|
+
|
|
33
|
+
You have access to tools that allow you to:
|
|
34
|
+
1. Create new agents
|
|
35
|
+
2. Update agent configurations (system prompts, models, settings)
|
|
36
|
+
3. Add/remove/modify tools for agents
|
|
37
|
+
4. Add/remove/modify knowledge sources for agents
|
|
38
|
+
5. View the current agent configuration
|
|
39
|
+
6. **Discover and add dynamic tools** from the Django project codebase
|
|
40
|
+
7. **Create multi-agent systems** by adding sub-agent tools
|
|
41
|
+
8. **Switch between agents and systems** in the UI
|
|
42
|
+
9. **Configure agent memory** (enable/disable the remember tool)
|
|
43
|
+
|
|
44
|
+
## IMPORTANT: Tool Usage
|
|
45
|
+
|
|
46
|
+
When calling tools, you MUST provide all required parameters. For example:
|
|
47
|
+
- `update_system_prompt` requires the `system_prompt` parameter with the FULL new prompt text
|
|
48
|
+
- `create_agent` requires the `name` parameter
|
|
49
|
+
|
|
50
|
+
Example of correct tool usage:
|
|
51
|
+
```
|
|
52
|
+
update_system_prompt({{"system_prompt": "You are a helpful pirate assistant. Respond to all questions in pirate speak, using 'Arrr', 'matey', 'ye', etc."}})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Dynamic Tool Discovery
|
|
56
|
+
|
|
57
|
+
You can scan the Django project to discover functions that can be turned into tools for agents:
|
|
58
|
+
- Use `scan_project_for_tools` to discover available functions in the codebase
|
|
59
|
+
- Use `list_discovered_functions` to browse and filter discovered functions
|
|
60
|
+
- Use `get_function_details` to see full details about a specific function
|
|
61
|
+
- Use `add_tool_from_function` to add a discovered function as a tool
|
|
62
|
+
- Use `list_agent_tools` to see all tools currently assigned to the agent
|
|
63
|
+
- Use `remove_tool` to remove a tool from the agent
|
|
64
|
+
|
|
65
|
+
## Multi-Agent Systems (Sub-Agents)
|
|
66
|
+
|
|
67
|
+
You can create multi-agent systems where one agent delegates to specialized sub-agents:
|
|
68
|
+
- Use `list_available_agents` to see agents that can be used as sub-agents
|
|
69
|
+
- Use `add_sub_agent_tool` to add another agent as a sub-agent tool
|
|
70
|
+
- Use `list_sub_agent_tools` to see current sub-agent tools
|
|
71
|
+
- Use `update_sub_agent_tool` to modify a sub-agent tool's configuration
|
|
72
|
+
- Use `remove_sub_agent_tool` to remove a sub-agent tool
|
|
73
|
+
|
|
74
|
+
**Example multi-agent setup:**
|
|
75
|
+
A "Customer Support Triage" agent might have sub-agent tools for:
|
|
76
|
+
- `billing_specialist`: Handles billing questions, refunds, invoices
|
|
77
|
+
- `technical_specialist`: Handles technical issues, bugs, how-to questions
|
|
78
|
+
|
|
79
|
+
When adding sub-agent tools:
|
|
80
|
+
1. First create the specialist agents with appropriate system prompts
|
|
81
|
+
2. Then add them as sub-agent tools to the triage/coordinator agent
|
|
82
|
+
3. Choose the right context_mode:
|
|
83
|
+
- `message_only` (default): Just pass the task message - best for focused delegation
|
|
84
|
+
- `summary`: Pass a brief context summary - good for context-aware responses
|
|
85
|
+
- `full`: Pass entire conversation - use sparingly, can be verbose
|
|
86
|
+
|
|
87
|
+
## UI Control - Switching Agents and Systems
|
|
88
|
+
|
|
89
|
+
You can control the builder UI to switch between different agents and systems:
|
|
90
|
+
- Use `list_all_agents` to see all available agents
|
|
91
|
+
- Use `switch_to_agent` to switch the UI to edit a different agent
|
|
92
|
+
- Use `list_all_systems` to see all multi-agent systems
|
|
93
|
+
- Use `switch_to_system` to select a system in the UI
|
|
94
|
+
- Use `get_system_details` to see detailed info about a system
|
|
95
|
+
|
|
96
|
+
When the user asks to work on a different agent or system, use these tools to switch context.
|
|
97
|
+
The UI will automatically update to show the selected agent/system.
|
|
98
|
+
|
|
99
|
+
## Agent Memory
|
|
100
|
+
|
|
101
|
+
Agents have a built-in memory system that allows them to remember facts about users across conversations:
|
|
102
|
+
- Memory is **enabled by default** for all agents
|
|
103
|
+
- When enabled, the agent has a `remember` tool it can use to store key-value facts
|
|
104
|
+
- Memories are scoped per-user and per-conversation
|
|
105
|
+
- Memory only works for **authenticated users** (not anonymous visitors)
|
|
106
|
+
- Use `get_memory_status` to check if memory is enabled
|
|
107
|
+
- Use `set_memory_enabled` to enable or disable memory
|
|
108
|
+
|
|
109
|
+
**When to disable memory:**
|
|
110
|
+
- Public-facing agents where you don't want user data stored
|
|
111
|
+
- Simple Q&A agents that don't need personalization
|
|
112
|
+
- Agents handling sensitive information that shouldn't be persisted
|
|
113
|
+
|
|
114
|
+
**When to keep memory enabled (default):**
|
|
115
|
+
- Personal assistants that should remember user preferences
|
|
116
|
+
- Support agents that benefit from knowing user history
|
|
117
|
+
- Any agent where personalization improves the experience
|
|
118
|
+
|
|
119
|
+
When helping users:
|
|
120
|
+
- Ask clarifying questions to understand what they want their agent to do
|
|
121
|
+
- Suggest appropriate system prompts based on the agent's purpose
|
|
122
|
+
- Recommend tools that would be useful for the agent's tasks
|
|
123
|
+
- Help them add relevant knowledge/context
|
|
124
|
+
- Explain the impact of different model choices and settings
|
|
125
|
+
- **Proactively suggest scanning for tools** when users describe functionality that might exist in their codebase
|
|
126
|
+
- **Suggest multi-agent patterns** when the user describes complex workflows that could benefit from specialized agents
|
|
127
|
+
- **Offer to switch agents** when the user mentions wanting to work on a different agent
|
|
128
|
+
|
|
129
|
+
Be conversational and helpful. Guide users through the process step by step.
|
|
130
|
+
|
|
131
|
+
## CRITICAL: Distinguish Instructions from Examples
|
|
132
|
+
|
|
133
|
+
**Before taking action, carefully determine whether the user is:**
|
|
134
|
+
1. **Giving you instructions** to modify agents
|
|
135
|
+
2. **Showing you example output** from an agent conversation
|
|
136
|
+
3. **Describing a problem** they observed and want to discuss
|
|
137
|
+
|
|
138
|
+
**Signs that text is EXAMPLE OUTPUT (not instructions):**
|
|
139
|
+
- Quoted conversation transcripts
|
|
140
|
+
- Text that reads like an agent responding to a user
|
|
141
|
+
- Phrases like "this was the output", "the agent said", "here's what happened"
|
|
142
|
+
- Multiple back-and-forth exchanges between user/assistant roles
|
|
143
|
+
|
|
144
|
+
**When you see example output or conversation transcripts:**
|
|
145
|
+
- Do NOT immediately start modifying agents based on the content
|
|
146
|
+
- ASK the user what they want you to do with this information
|
|
147
|
+
- Example: "I see you've shared a conversation from the S'Ai system. What would you like me to help with? Are you asking me to modify how these agents behave, or are you describing an issue you'd like to discuss?"
|
|
148
|
+
|
|
149
|
+
## CRITICAL: Confirm Before Bulk Changes
|
|
150
|
+
|
|
151
|
+
**Before making changes to multiple agents:**
|
|
152
|
+
- Summarize what you're about to do
|
|
153
|
+
- List which agents will be affected
|
|
154
|
+
- Ask for explicit confirmation
|
|
155
|
+
- Example: "I understand you want to update 5 agents to present as a unified system. This will modify: stage-assessor, state-assessor, epistemic-validator, integration-planner, and guardrail-monitor. Should I proceed?"
|
|
156
|
+
|
|
157
|
+
**Be conservative with ambiguous requests.** When in doubt, ask for clarification rather than taking action.
|
|
158
|
+
|
|
159
|
+
## IMPORTANT: Always End with a Summary
|
|
160
|
+
|
|
161
|
+
After completing any actions (creating agents, updating prompts, adding tools, etc.), ALWAYS end your response with a brief summary of what you did. For example:
|
|
162
|
+
- "I've created the 'Customer Support' agent with a helpful assistant prompt and added the search_orders tool."
|
|
163
|
+
- "I've updated the system prompt to make the agent respond in pirate speak."
|
|
164
|
+
- "I've added 3 sub-agent tools to the Orchestrator: billing_specialist, tech_support, and returns_handler."
|
|
165
|
+
|
|
166
|
+
This helps users understand what changes were made without having to expand the tool results.
|
|
167
|
+
|
|
168
|
+
Current agent being edited: {agent_context}
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
BUILDER_TOOLS = [
|
|
172
|
+
{
|
|
173
|
+
"type": "function",
|
|
174
|
+
"function": {
|
|
175
|
+
"name": "create_agent",
|
|
176
|
+
"description": "Create a new agent with the given name and description. Use this when no agent is selected or when the user wants to create a new agent.",
|
|
177
|
+
"parameters": {
|
|
178
|
+
"type": "object",
|
|
179
|
+
"properties": {
|
|
180
|
+
"name": {
|
|
181
|
+
"type": "string",
|
|
182
|
+
"description": "The name for the new agent (e.g., 'Tony', 'Customer Support Bot')",
|
|
183
|
+
},
|
|
184
|
+
"description": {
|
|
185
|
+
"type": "string",
|
|
186
|
+
"description": "A brief description of what the agent does",
|
|
187
|
+
},
|
|
188
|
+
"system_prompt": {
|
|
189
|
+
"type": "string",
|
|
190
|
+
"description": "Optional initial system prompt. Can be updated later.",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
"required": ["name"],
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"type": "function",
|
|
199
|
+
"function": {
|
|
200
|
+
"name": "update_system_prompt",
|
|
201
|
+
"description": "Update the agent's system prompt. This defines the agent's personality, role, and behavior.",
|
|
202
|
+
"parameters": {
|
|
203
|
+
"type": "object",
|
|
204
|
+
"properties": {
|
|
205
|
+
"system_prompt": {
|
|
206
|
+
"type": "string",
|
|
207
|
+
"description": "The new system prompt for the agent",
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
"required": ["system_prompt"],
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"type": "function",
|
|
216
|
+
"function": {
|
|
217
|
+
"name": "update_agent_name",
|
|
218
|
+
"description": "Update the agent's name and description.",
|
|
219
|
+
"parameters": {
|
|
220
|
+
"type": "object",
|
|
221
|
+
"properties": {
|
|
222
|
+
"name": {
|
|
223
|
+
"type": "string",
|
|
224
|
+
"description": "The new name for the agent",
|
|
225
|
+
},
|
|
226
|
+
"description": {
|
|
227
|
+
"type": "string",
|
|
228
|
+
"description": "A brief description of what the agent does",
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
"required": ["name"],
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"type": "function",
|
|
237
|
+
"function": {
|
|
238
|
+
"name": "update_model_settings",
|
|
239
|
+
"description": "Update the agent's model and settings (temperature, etc).",
|
|
240
|
+
"parameters": {
|
|
241
|
+
"type": "object",
|
|
242
|
+
"properties": {
|
|
243
|
+
"model": {
|
|
244
|
+
"type": "string",
|
|
245
|
+
"description": "The LLM model to use (e.g., 'gpt-4o', 'gpt-4o-mini', 'claude-3-opus')",
|
|
246
|
+
},
|
|
247
|
+
"temperature": {
|
|
248
|
+
"type": "number",
|
|
249
|
+
"description": "Temperature setting (0-2). Lower = more focused, higher = more creative",
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"type": "function",
|
|
257
|
+
"function": {
|
|
258
|
+
"name": "set_memory_enabled",
|
|
259
|
+
"description": "Enable or disable conversation memory for the agent. When enabled (default), the agent can remember facts about users across messages using the 'remember' tool. Memory only works for authenticated users.",
|
|
260
|
+
"parameters": {
|
|
261
|
+
"type": "object",
|
|
262
|
+
"properties": {
|
|
263
|
+
"enabled": {
|
|
264
|
+
"type": "boolean",
|
|
265
|
+
"description": "Whether to enable memory. True = agent can remember facts, False = no memory.",
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
"required": ["enabled"],
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"type": "function",
|
|
274
|
+
"function": {
|
|
275
|
+
"name": "get_memory_status",
|
|
276
|
+
"description": "Check if memory is enabled for the current agent.",
|
|
277
|
+
"parameters": {
|
|
278
|
+
"type": "object",
|
|
279
|
+
"properties": {},
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"type": "function",
|
|
285
|
+
"function": {
|
|
286
|
+
"name": "add_knowledge",
|
|
287
|
+
"description": "Add a knowledge source to the agent. Use inclusion_mode='always' for small context that should always be included, or 'rag' for larger documents that should be searched.",
|
|
288
|
+
"parameters": {
|
|
289
|
+
"type": "object",
|
|
290
|
+
"properties": {
|
|
291
|
+
"name": {
|
|
292
|
+
"type": "string",
|
|
293
|
+
"description": "Name/title for this knowledge source",
|
|
294
|
+
},
|
|
295
|
+
"content": {
|
|
296
|
+
"type": "string",
|
|
297
|
+
"description": "The knowledge content (text)",
|
|
298
|
+
},
|
|
299
|
+
"inclusion_mode": {
|
|
300
|
+
"type": "string",
|
|
301
|
+
"enum": ["always", "rag"],
|
|
302
|
+
"description": "How to include this knowledge: 'always' (in every prompt) or 'rag' (retrieved based on relevance). Default: 'always'",
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
"required": ["name", "content"],
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
"type": "function",
|
|
311
|
+
"function": {
|
|
312
|
+
"name": "index_knowledge",
|
|
313
|
+
"description": "Index RAG knowledge sources for similarity search. Call this after adding knowledge with inclusion_mode='rag'.",
|
|
314
|
+
"parameters": {
|
|
315
|
+
"type": "object",
|
|
316
|
+
"properties": {
|
|
317
|
+
"knowledge_id": {
|
|
318
|
+
"type": "string",
|
|
319
|
+
"description": "Optional: specific knowledge ID to index. If not provided, indexes all pending RAG knowledge.",
|
|
320
|
+
},
|
|
321
|
+
"force": {
|
|
322
|
+
"type": "boolean",
|
|
323
|
+
"description": "Force re-indexing even if already indexed. Default: false",
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"type": "function",
|
|
331
|
+
"function": {
|
|
332
|
+
"name": "get_rag_status",
|
|
333
|
+
"description": "Get the indexing status of RAG knowledge sources for the agent.",
|
|
334
|
+
"parameters": {
|
|
335
|
+
"type": "object",
|
|
336
|
+
"properties": {},
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
"type": "function",
|
|
342
|
+
"function": {
|
|
343
|
+
"name": "preview_rag_search",
|
|
344
|
+
"description": "Preview what knowledge would be retrieved for a given query. Useful for testing RAG configuration.",
|
|
345
|
+
"parameters": {
|
|
346
|
+
"type": "object",
|
|
347
|
+
"properties": {
|
|
348
|
+
"query": {
|
|
349
|
+
"type": "string",
|
|
350
|
+
"description": "The search query to test",
|
|
351
|
+
},
|
|
352
|
+
"top_k": {
|
|
353
|
+
"type": "integer",
|
|
354
|
+
"description": "Number of results to return. Default: 5",
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
"required": ["query"],
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
"type": "function",
|
|
363
|
+
"function": {
|
|
364
|
+
"name": "update_rag_config",
|
|
365
|
+
"description": "Update the RAG configuration for the agent (top_k, similarity_threshold, etc).",
|
|
366
|
+
"parameters": {
|
|
367
|
+
"type": "object",
|
|
368
|
+
"properties": {
|
|
369
|
+
"enabled": {
|
|
370
|
+
"type": "boolean",
|
|
371
|
+
"description": "Enable or disable RAG for this agent",
|
|
372
|
+
},
|
|
373
|
+
"top_k": {
|
|
374
|
+
"type": "integer",
|
|
375
|
+
"description": "Number of chunks to retrieve. Default: 5",
|
|
376
|
+
},
|
|
377
|
+
"similarity_threshold": {
|
|
378
|
+
"type": "number",
|
|
379
|
+
"description": "Minimum similarity score (0-1). Default: 0.7",
|
|
380
|
+
},
|
|
381
|
+
"chunk_size": {
|
|
382
|
+
"type": "integer",
|
|
383
|
+
"description": "Size of text chunks in characters. Default: 500",
|
|
384
|
+
},
|
|
385
|
+
"chunk_overlap": {
|
|
386
|
+
"type": "integer",
|
|
387
|
+
"description": "Overlap between chunks. Default: 50",
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"type": "function",
|
|
395
|
+
"function": {
|
|
396
|
+
"name": "get_current_config",
|
|
397
|
+
"description": "Get the current configuration of the agent being edited.",
|
|
398
|
+
"parameters": {
|
|
399
|
+
"type": "object",
|
|
400
|
+
"properties": {},
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
# Dynamic Tool Inspector tools
|
|
405
|
+
{
|
|
406
|
+
"type": "function",
|
|
407
|
+
"function": {
|
|
408
|
+
"name": "scan_project_for_tools",
|
|
409
|
+
"description": "Scan the Django project to discover functions that can be used as tools. Returns a list of discovered functions with their signatures and descriptions.",
|
|
410
|
+
"parameters": {
|
|
411
|
+
"type": "object",
|
|
412
|
+
"properties": {
|
|
413
|
+
"app_filter": {
|
|
414
|
+
"type": "array",
|
|
415
|
+
"items": {"type": "string"},
|
|
416
|
+
"description": "Optional list of app names to scan. If not provided, scans all project apps.",
|
|
417
|
+
},
|
|
418
|
+
"include_private": {
|
|
419
|
+
"type": "boolean",
|
|
420
|
+
"description": "Whether to include private functions (starting with _). Default: false",
|
|
421
|
+
},
|
|
422
|
+
"directory": {
|
|
423
|
+
"type": "string",
|
|
424
|
+
"description": "Optional specific directory to scan instead of Django apps.",
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
"type": "function",
|
|
432
|
+
"function": {
|
|
433
|
+
"name": "list_discovered_functions",
|
|
434
|
+
"description": "List functions that were discovered from the most recent scan. Can filter by type, module, or side effects.",
|
|
435
|
+
"parameters": {
|
|
436
|
+
"type": "object",
|
|
437
|
+
"properties": {
|
|
438
|
+
"function_type": {
|
|
439
|
+
"type": "string",
|
|
440
|
+
"enum": ["function", "method", "view", "model_method", "manager_method", "utility"],
|
|
441
|
+
"description": "Filter by function type",
|
|
442
|
+
},
|
|
443
|
+
"module_filter": {
|
|
444
|
+
"type": "string",
|
|
445
|
+
"description": "Filter by module path (partial match)",
|
|
446
|
+
},
|
|
447
|
+
"safe_only": {
|
|
448
|
+
"type": "boolean",
|
|
449
|
+
"description": "Only show functions without side effects",
|
|
450
|
+
},
|
|
451
|
+
"limit": {
|
|
452
|
+
"type": "integer",
|
|
453
|
+
"description": "Maximum number of results to return. Default: 50",
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
"type": "function",
|
|
461
|
+
"function": {
|
|
462
|
+
"name": "add_tool_from_function",
|
|
463
|
+
"description": "Add a discovered function as a dynamic tool to the agent. The function will be callable by the agent at runtime.",
|
|
464
|
+
"parameters": {
|
|
465
|
+
"type": "object",
|
|
466
|
+
"properties": {
|
|
467
|
+
"function_path": {
|
|
468
|
+
"type": "string",
|
|
469
|
+
"description": "Full import path to the function (e.g., 'myapp.utils.calculate_tax')",
|
|
470
|
+
},
|
|
471
|
+
"tool_name": {
|
|
472
|
+
"type": "string",
|
|
473
|
+
"description": "Optional custom name for the tool. If not provided, derives from function name.",
|
|
474
|
+
},
|
|
475
|
+
"description": {
|
|
476
|
+
"type": "string",
|
|
477
|
+
"description": "Optional custom description. If not provided, uses function docstring.",
|
|
478
|
+
},
|
|
479
|
+
"requires_confirmation": {
|
|
480
|
+
"type": "boolean",
|
|
481
|
+
"description": "Whether to require user confirmation before execution. Default: true for functions with side effects.",
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
"required": ["function_path"],
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
"type": "function",
|
|
490
|
+
"function": {
|
|
491
|
+
"name": "list_agent_tools",
|
|
492
|
+
"description": "List all tools currently assigned to the agent, including both static and dynamic tools.",
|
|
493
|
+
"parameters": {
|
|
494
|
+
"type": "object",
|
|
495
|
+
"properties": {
|
|
496
|
+
"include_inactive": {
|
|
497
|
+
"type": "boolean",
|
|
498
|
+
"description": "Whether to include inactive tools. Default: false",
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
"type": "function",
|
|
506
|
+
"function": {
|
|
507
|
+
"name": "remove_tool",
|
|
508
|
+
"description": "Remove a tool from the agent.",
|
|
509
|
+
"parameters": {
|
|
510
|
+
"type": "object",
|
|
511
|
+
"properties": {
|
|
512
|
+
"tool_name": {
|
|
513
|
+
"type": "string",
|
|
514
|
+
"description": "Name of the tool to remove",
|
|
515
|
+
},
|
|
516
|
+
"tool_id": {
|
|
517
|
+
"type": "string",
|
|
518
|
+
"description": "UUID of the tool to remove (alternative to tool_name)",
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
"type": "function",
|
|
526
|
+
"function": {
|
|
527
|
+
"name": "get_function_details",
|
|
528
|
+
"description": "Get detailed information about a specific discovered function, including its full signature, docstring, and parameters.",
|
|
529
|
+
"parameters": {
|
|
530
|
+
"type": "object",
|
|
531
|
+
"properties": {
|
|
532
|
+
"function_path": {
|
|
533
|
+
"type": "string",
|
|
534
|
+
"description": "Full import path to the function (e.g., 'myapp.utils.calculate_tax')",
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
"required": ["function_path"],
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
"type": "function",
|
|
543
|
+
"function": {
|
|
544
|
+
"name": "list_revisions",
|
|
545
|
+
"description": "List all revisions (version history) for the agent. Shows when changes were made and what changed.",
|
|
546
|
+
"parameters": {
|
|
547
|
+
"type": "object",
|
|
548
|
+
"properties": {
|
|
549
|
+
"limit": {
|
|
550
|
+
"type": "integer",
|
|
551
|
+
"description": "Maximum number of revisions to return. Default: 20",
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
"type": "function",
|
|
559
|
+
"function": {
|
|
560
|
+
"name": "get_revision",
|
|
561
|
+
"description": "Get the full configuration snapshot from a specific revision.",
|
|
562
|
+
"parameters": {
|
|
563
|
+
"type": "object",
|
|
564
|
+
"properties": {
|
|
565
|
+
"revision_number": {
|
|
566
|
+
"type": "integer",
|
|
567
|
+
"description": "The revision number to retrieve",
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
"required": ["revision_number"],
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
"type": "function",
|
|
576
|
+
"function": {
|
|
577
|
+
"name": "restore_revision",
|
|
578
|
+
"description": "Restore the agent to a previous revision. This creates a new revision with the old configuration.",
|
|
579
|
+
"parameters": {
|
|
580
|
+
"type": "object",
|
|
581
|
+
"properties": {
|
|
582
|
+
"revision_number": {
|
|
583
|
+
"type": "integer",
|
|
584
|
+
"description": "The revision number to restore",
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
"required": ["revision_number"],
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
},
|
|
591
|
+
# ==========================================================================
|
|
592
|
+
# Multi-Agent / Sub-Agent Tools
|
|
593
|
+
# ==========================================================================
|
|
594
|
+
{
|
|
595
|
+
"type": "function",
|
|
596
|
+
"function": {
|
|
597
|
+
"name": "list_available_agents",
|
|
598
|
+
"description": "List all available agents that can be used as sub-agents. Returns agents that the current agent can delegate to.",
|
|
599
|
+
"parameters": {
|
|
600
|
+
"type": "object",
|
|
601
|
+
"properties": {
|
|
602
|
+
"include_inactive": {
|
|
603
|
+
"type": "boolean",
|
|
604
|
+
"description": "Whether to include inactive agents. Default: false",
|
|
605
|
+
},
|
|
606
|
+
"search": {
|
|
607
|
+
"type": "string",
|
|
608
|
+
"description": "Optional search term to filter agents by name or description",
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
"type": "function",
|
|
616
|
+
"function": {
|
|
617
|
+
"name": "add_sub_agent_tool",
|
|
618
|
+
"description": "Add another agent as a sub-agent tool. This allows the current agent to delegate tasks to the sub-agent. Requires CREATOR or ADMIN permission level.",
|
|
619
|
+
"parameters": {
|
|
620
|
+
"type": "object",
|
|
621
|
+
"properties": {
|
|
622
|
+
"sub_agent_slug": {
|
|
623
|
+
"type": "string",
|
|
624
|
+
"description": "The slug of the agent to add as a sub-agent (e.g., 'billing-specialist')",
|
|
625
|
+
},
|
|
626
|
+
"tool_name": {
|
|
627
|
+
"type": "string",
|
|
628
|
+
"description": "The name for this sub-agent tool (e.g., 'billing_specialist'). Should be snake_case.",
|
|
629
|
+
},
|
|
630
|
+
"description": {
|
|
631
|
+
"type": "string",
|
|
632
|
+
"description": "Description of when to use this sub-agent (e.g., 'Consult for billing and payment questions')",
|
|
633
|
+
},
|
|
634
|
+
"context_mode": {
|
|
635
|
+
"type": "string",
|
|
636
|
+
"enum": ["message_only", "summary", "full"],
|
|
637
|
+
"description": "How much context to pass to the sub-agent. 'message_only' (default): just the task message. 'summary': brief context summary. 'full': entire conversation history.",
|
|
638
|
+
},
|
|
639
|
+
},
|
|
640
|
+
"required": ["sub_agent_slug", "tool_name", "description"],
|
|
641
|
+
},
|
|
642
|
+
},
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
"type": "function",
|
|
646
|
+
"function": {
|
|
647
|
+
"name": "list_sub_agent_tools",
|
|
648
|
+
"description": "List all sub-agent tools configured for the current agent.",
|
|
649
|
+
"parameters": {
|
|
650
|
+
"type": "object",
|
|
651
|
+
"properties": {},
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
"type": "function",
|
|
657
|
+
"function": {
|
|
658
|
+
"name": "remove_sub_agent_tool",
|
|
659
|
+
"description": "Remove a sub-agent tool from the current agent. Requires CREATOR or ADMIN permission level.",
|
|
660
|
+
"parameters": {
|
|
661
|
+
"type": "object",
|
|
662
|
+
"properties": {
|
|
663
|
+
"tool_name": {
|
|
664
|
+
"type": "string",
|
|
665
|
+
"description": "The name of the sub-agent tool to remove",
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
"required": ["tool_name"],
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
"type": "function",
|
|
674
|
+
"function": {
|
|
675
|
+
"name": "update_sub_agent_tool",
|
|
676
|
+
"description": "Update a sub-agent tool's configuration (description, context_mode). Requires CREATOR or ADMIN permission level.",
|
|
677
|
+
"parameters": {
|
|
678
|
+
"type": "object",
|
|
679
|
+
"properties": {
|
|
680
|
+
"tool_name": {
|
|
681
|
+
"type": "string",
|
|
682
|
+
"description": "The name of the sub-agent tool to update",
|
|
683
|
+
},
|
|
684
|
+
"description": {
|
|
685
|
+
"type": "string",
|
|
686
|
+
"description": "New description for the sub-agent tool",
|
|
687
|
+
},
|
|
688
|
+
"context_mode": {
|
|
689
|
+
"type": "string",
|
|
690
|
+
"enum": ["message_only", "summary", "full"],
|
|
691
|
+
"description": "New context mode for the sub-agent",
|
|
692
|
+
},
|
|
693
|
+
},
|
|
694
|
+
"required": ["tool_name"],
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
},
|
|
698
|
+
# ==========================================================================
|
|
699
|
+
# UI Control Tools - Allow builder to switch agents/systems in the UI
|
|
700
|
+
# ==========================================================================
|
|
701
|
+
{
|
|
702
|
+
"type": "function",
|
|
703
|
+
"function": {
|
|
704
|
+
"name": "switch_to_agent",
|
|
705
|
+
"description": "Switch the UI to edit a different agent. This changes which agent is being edited in the builder interface.",
|
|
706
|
+
"parameters": {
|
|
707
|
+
"type": "object",
|
|
708
|
+
"properties": {
|
|
709
|
+
"agent_slug": {
|
|
710
|
+
"type": "string",
|
|
711
|
+
"description": "The slug of the agent to switch to (e.g., 'billing-specialist')",
|
|
712
|
+
},
|
|
713
|
+
"agent_id": {
|
|
714
|
+
"type": "string",
|
|
715
|
+
"description": "The UUID of the agent to switch to (alternative to slug)",
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
"type": "function",
|
|
723
|
+
"function": {
|
|
724
|
+
"name": "switch_to_system",
|
|
725
|
+
"description": "Switch the UI to work with a multi-agent system. This selects a system in the builder interface.",
|
|
726
|
+
"parameters": {
|
|
727
|
+
"type": "object",
|
|
728
|
+
"properties": {
|
|
729
|
+
"system_slug": {
|
|
730
|
+
"type": "string",
|
|
731
|
+
"description": "The slug of the system to switch to (e.g., 'customer-support')",
|
|
732
|
+
},
|
|
733
|
+
"system_id": {
|
|
734
|
+
"type": "string",
|
|
735
|
+
"description": "The UUID of the system to switch to (alternative to slug)",
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
"type": "function",
|
|
743
|
+
"function": {
|
|
744
|
+
"name": "list_all_agents",
|
|
745
|
+
"description": "List all available agents in the system. Use this to find agents to switch to or to see what agents exist.",
|
|
746
|
+
"parameters": {
|
|
747
|
+
"type": "object",
|
|
748
|
+
"properties": {
|
|
749
|
+
"include_inactive": {
|
|
750
|
+
"type": "boolean",
|
|
751
|
+
"description": "Whether to include inactive agents. Default: false",
|
|
752
|
+
},
|
|
753
|
+
"search": {
|
|
754
|
+
"type": "string",
|
|
755
|
+
"description": "Optional search term to filter agents by name or description",
|
|
756
|
+
},
|
|
757
|
+
},
|
|
758
|
+
},
|
|
759
|
+
},
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
"type": "function",
|
|
763
|
+
"function": {
|
|
764
|
+
"name": "list_all_systems",
|
|
765
|
+
"description": "List all available multi-agent systems. Use this to find systems to switch to or to see what systems exist.",
|
|
766
|
+
"parameters": {
|
|
767
|
+
"type": "object",
|
|
768
|
+
"properties": {
|
|
769
|
+
"include_inactive": {
|
|
770
|
+
"type": "boolean",
|
|
771
|
+
"description": "Whether to include inactive systems. Default: false",
|
|
772
|
+
},
|
|
773
|
+
"search": {
|
|
774
|
+
"type": "string",
|
|
775
|
+
"description": "Optional search term to filter systems by name or description",
|
|
776
|
+
},
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
"type": "function",
|
|
783
|
+
"function": {
|
|
784
|
+
"name": "get_system_details",
|
|
785
|
+
"description": "Get detailed information about a multi-agent system, including its members and configuration.",
|
|
786
|
+
"parameters": {
|
|
787
|
+
"type": "object",
|
|
788
|
+
"properties": {
|
|
789
|
+
"system_slug": {
|
|
790
|
+
"type": "string",
|
|
791
|
+
"description": "The slug of the system to get details for",
|
|
792
|
+
},
|
|
793
|
+
"system_id": {
|
|
794
|
+
"type": "string",
|
|
795
|
+
"description": "The UUID of the system (alternative to slug)",
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
},
|
|
800
|
+
},
|
|
801
|
+
]
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
class BuilderAgentRuntime(AgentRuntime):
|
|
805
|
+
"""
|
|
806
|
+
The agent builder assistant that helps users create custom agents.
|
|
807
|
+
|
|
808
|
+
This agent has tools to modify AgentDefinition objects and guides
|
|
809
|
+
users through the agent creation process.
|
|
810
|
+
"""
|
|
811
|
+
|
|
812
|
+
@property
|
|
813
|
+
def key(self) -> str:
|
|
814
|
+
return "agent-builder"
|
|
815
|
+
|
|
816
|
+
async def run(self, ctx: RunContext) -> RunResult:
|
|
817
|
+
"""Execute the builder agent with agentic loop."""
|
|
818
|
+
# Get the agent being edited from context
|
|
819
|
+
# Check both metadata (from frontend) and params (from API)
|
|
820
|
+
agent_id = ctx.metadata.get("agent_id") or ctx.params.get("agent_id")
|
|
821
|
+
agent_context = "No agent selected. Ask the user what kind of agent they want to create."
|
|
822
|
+
|
|
823
|
+
if agent_id:
|
|
824
|
+
try:
|
|
825
|
+
agent = await sync_to_async(AgentDefinition.objects.get)(id=agent_id)
|
|
826
|
+
config = await sync_to_async(agent.get_effective_config)()
|
|
827
|
+
agent_context = f"""
|
|
828
|
+
Agent: {agent.name} ({agent.slug})
|
|
829
|
+
Description: {agent.description or 'Not set'}
|
|
830
|
+
Model: {config.get('model', 'gpt-4o')}
|
|
831
|
+
System Prompt: {config.get('system_prompt', 'Not set')[:500]}...
|
|
832
|
+
Tools: {len(config.get('tools', []))} configured
|
|
833
|
+
Knowledge: {len(config.get('knowledge', []))} sources
|
|
834
|
+
"""
|
|
835
|
+
except AgentDefinition.DoesNotExist:
|
|
836
|
+
agent_context = "Agent not found. A new agent will be created."
|
|
837
|
+
|
|
838
|
+
# Build messages
|
|
839
|
+
system_prompt = BUILDER_SYSTEM_PROMPT.format(agent_context=agent_context)
|
|
840
|
+
messages = [{"role": "system", "content": system_prompt}]
|
|
841
|
+
messages.extend(ctx.input_messages)
|
|
842
|
+
|
|
843
|
+
# Get model from params (allows per-request override) or use default
|
|
844
|
+
model = ctx.params.get("model", DEFAULT_MODEL)
|
|
845
|
+
|
|
846
|
+
# Get LLM client for the specified model (auto-detects provider)
|
|
847
|
+
llm = get_llm_client_for_model(model)
|
|
848
|
+
|
|
849
|
+
# Create tool executor function for the agentic loop
|
|
850
|
+
async def execute_tool(tool_name: str, tool_args: dict) -> dict:
|
|
851
|
+
return await self._execute_tool(agent_id, tool_name, tool_args, ctx)
|
|
852
|
+
|
|
853
|
+
# Use the shared agentic loop
|
|
854
|
+
# Note: agentic_loop emits ASSISTANT_MESSAGE for the final response
|
|
855
|
+
result = await run_agentic_loop(
|
|
856
|
+
llm=llm,
|
|
857
|
+
messages=messages,
|
|
858
|
+
tools=BUILDER_TOOLS,
|
|
859
|
+
execute_tool=execute_tool,
|
|
860
|
+
ctx=ctx,
|
|
861
|
+
model=model,
|
|
862
|
+
max_iterations=10,
|
|
863
|
+
temperature=0.7,
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
return RunResult(
|
|
867
|
+
final_output={"response": result.final_content},
|
|
868
|
+
final_messages=result.messages,
|
|
869
|
+
)
|
|
870
|
+
|
|
871
|
+
async def _execute_tool(
|
|
872
|
+
self,
|
|
873
|
+
agent_id: Optional[str],
|
|
874
|
+
tool_name: str,
|
|
875
|
+
args: dict,
|
|
876
|
+
ctx: RunContext,
|
|
877
|
+
) -> dict:
|
|
878
|
+
"""Execute a builder tool."""
|
|
879
|
+
# Import here to avoid circular imports
|
|
880
|
+
from django_agent_runtime.models import AgentKnowledge, DynamicTool
|
|
881
|
+
from django_agent_runtime.dynamic_tools.scanner import ProjectScanner
|
|
882
|
+
from django_agent_runtime.dynamic_tools.generator import ToolGenerator
|
|
883
|
+
|
|
884
|
+
# Tools that don't require an agent
|
|
885
|
+
if tool_name == "scan_project_for_tools":
|
|
886
|
+
return await self._scan_project_for_tools(args, ctx)
|
|
887
|
+
|
|
888
|
+
if tool_name == "list_discovered_functions":
|
|
889
|
+
return await self._list_discovered_functions(args, ctx)
|
|
890
|
+
|
|
891
|
+
if tool_name == "get_function_details":
|
|
892
|
+
return await self._get_function_details(args, ctx)
|
|
893
|
+
|
|
894
|
+
if tool_name == "create_agent":
|
|
895
|
+
return await self._create_agent(args, ctx)
|
|
896
|
+
|
|
897
|
+
# UI Control tools - these emit special events to control the frontend
|
|
898
|
+
if tool_name == "switch_to_agent":
|
|
899
|
+
return await self._switch_to_agent(args, ctx)
|
|
900
|
+
|
|
901
|
+
if tool_name == "switch_to_system":
|
|
902
|
+
return await self._switch_to_system(args, ctx)
|
|
903
|
+
|
|
904
|
+
if tool_name == "list_all_agents":
|
|
905
|
+
return await self._list_all_agents(args, ctx)
|
|
906
|
+
|
|
907
|
+
if tool_name == "list_all_systems":
|
|
908
|
+
return await self._list_all_systems(args, ctx)
|
|
909
|
+
|
|
910
|
+
if tool_name == "get_system_details":
|
|
911
|
+
return await self._get_system_details(args, ctx)
|
|
912
|
+
|
|
913
|
+
# Tools that require an agent
|
|
914
|
+
if not agent_id:
|
|
915
|
+
return {"error": "No agent selected. Please create an agent first or use create_agent tool."}
|
|
916
|
+
|
|
917
|
+
try:
|
|
918
|
+
agent = await sync_to_async(AgentDefinition.objects.get)(id=agent_id)
|
|
919
|
+
version = await sync_to_async(agent.versions.filter(is_active=True).first)()
|
|
920
|
+
|
|
921
|
+
if tool_name == "get_current_config":
|
|
922
|
+
return await sync_to_async(agent.get_effective_config)()
|
|
923
|
+
|
|
924
|
+
elif tool_name == "update_system_prompt":
|
|
925
|
+
if "system_prompt" not in args:
|
|
926
|
+
return {
|
|
927
|
+
"error": "Missing required parameter: system_prompt",
|
|
928
|
+
"hint": "You must provide the full new system_prompt text. Example: update_system_prompt({\"system_prompt\": \"You are a helpful pirate assistant...\"})"
|
|
929
|
+
}
|
|
930
|
+
if version:
|
|
931
|
+
version.system_prompt = args["system_prompt"]
|
|
932
|
+
await sync_to_async(version.save)()
|
|
933
|
+
await create_revision(agent, comment="Updated system prompt")
|
|
934
|
+
return {"success": True, "message": "System prompt updated"}
|
|
935
|
+
return {"error": "No active version found"}
|
|
936
|
+
|
|
937
|
+
elif tool_name == "update_agent_name":
|
|
938
|
+
old_name = agent.name
|
|
939
|
+
agent.name = args["name"]
|
|
940
|
+
if "description" in args:
|
|
941
|
+
agent.description = args["description"]
|
|
942
|
+
await sync_to_async(agent.save)()
|
|
943
|
+
await create_revision(agent, comment=f"Renamed from '{old_name}' to '{agent.name}'")
|
|
944
|
+
return {"success": True, "message": "Agent name updated"}
|
|
945
|
+
|
|
946
|
+
elif tool_name == "update_model_settings":
|
|
947
|
+
if version:
|
|
948
|
+
changes = []
|
|
949
|
+
if "model" in args:
|
|
950
|
+
version.model = args["model"]
|
|
951
|
+
changes.append(f"model={args['model']}")
|
|
952
|
+
if "temperature" in args:
|
|
953
|
+
version.model_settings["temperature"] = args["temperature"]
|
|
954
|
+
changes.append(f"temperature={args['temperature']}")
|
|
955
|
+
await sync_to_async(version.save)()
|
|
956
|
+
await create_revision(agent, comment=f"Updated model settings: {', '.join(changes)}")
|
|
957
|
+
return {"success": True, "message": "Model settings updated"}
|
|
958
|
+
return {"error": "No active version found"}
|
|
959
|
+
|
|
960
|
+
elif tool_name == "set_memory_enabled":
|
|
961
|
+
if version:
|
|
962
|
+
enabled = args.get("enabled", True)
|
|
963
|
+
if version.extra_config is None:
|
|
964
|
+
version.extra_config = {}
|
|
965
|
+
version.extra_config["memory_enabled"] = enabled
|
|
966
|
+
await sync_to_async(version.save)()
|
|
967
|
+
status = "enabled" if enabled else "disabled"
|
|
968
|
+
await create_revision(agent, comment=f"Memory {status}")
|
|
969
|
+
return {
|
|
970
|
+
"success": True,
|
|
971
|
+
"message": f"Memory {status} for this agent",
|
|
972
|
+
"memory_enabled": enabled,
|
|
973
|
+
}
|
|
974
|
+
return {"error": "No active version found"}
|
|
975
|
+
|
|
976
|
+
elif tool_name == "get_memory_status":
|
|
977
|
+
if version:
|
|
978
|
+
extra = version.extra_config or {}
|
|
979
|
+
enabled = extra.get("memory_enabled", True) # Default is True
|
|
980
|
+
return {
|
|
981
|
+
"memory_enabled": enabled,
|
|
982
|
+
"message": f"Memory is {'enabled' if enabled else 'disabled'} for this agent",
|
|
983
|
+
"note": "When enabled, the agent has a 'remember' tool to store facts about users. Memory only works for authenticated users.",
|
|
984
|
+
}
|
|
985
|
+
return {"error": "No active version found"}
|
|
986
|
+
|
|
987
|
+
elif tool_name == "add_knowledge":
|
|
988
|
+
inclusion_mode = args.get("inclusion_mode", "always")
|
|
989
|
+
knowledge = await sync_to_async(AgentKnowledge.objects.create)(
|
|
990
|
+
agent=agent,
|
|
991
|
+
name=args["name"],
|
|
992
|
+
knowledge_type="text",
|
|
993
|
+
content=args["content"],
|
|
994
|
+
inclusion_mode=inclusion_mode,
|
|
995
|
+
)
|
|
996
|
+
await create_revision(agent, comment=f"Added knowledge: {args['name']} ({inclusion_mode})")
|
|
997
|
+
|
|
998
|
+
result = {
|
|
999
|
+
"success": True,
|
|
1000
|
+
"message": f"Knowledge '{args['name']}' added with mode '{inclusion_mode}'",
|
|
1001
|
+
"knowledge_id": str(knowledge.id),
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
# If RAG mode, remind to index
|
|
1005
|
+
if inclusion_mode == "rag":
|
|
1006
|
+
result["note"] = "Use index_knowledge to index this knowledge for RAG search."
|
|
1007
|
+
|
|
1008
|
+
return result
|
|
1009
|
+
|
|
1010
|
+
elif tool_name == "index_knowledge":
|
|
1011
|
+
return await self._index_knowledge(agent, args, ctx)
|
|
1012
|
+
|
|
1013
|
+
elif tool_name == "get_rag_status":
|
|
1014
|
+
return await self._get_rag_status(agent, args, ctx)
|
|
1015
|
+
|
|
1016
|
+
elif tool_name == "preview_rag_search":
|
|
1017
|
+
return await self._preview_rag_search(agent, args, ctx)
|
|
1018
|
+
|
|
1019
|
+
elif tool_name == "update_rag_config":
|
|
1020
|
+
return await self._update_rag_config(agent, args, ctx)
|
|
1021
|
+
|
|
1022
|
+
elif tool_name == "add_tool_from_function":
|
|
1023
|
+
return await self._add_tool_from_function(agent, args, ctx)
|
|
1024
|
+
|
|
1025
|
+
elif tool_name == "list_agent_tools":
|
|
1026
|
+
return await self._list_agent_tools(agent, args, ctx)
|
|
1027
|
+
|
|
1028
|
+
elif tool_name == "remove_tool":
|
|
1029
|
+
return await self._remove_tool(agent, args, ctx)
|
|
1030
|
+
|
|
1031
|
+
elif tool_name == "list_revisions":
|
|
1032
|
+
return await self._list_revisions(agent, args, ctx)
|
|
1033
|
+
|
|
1034
|
+
elif tool_name == "get_revision":
|
|
1035
|
+
return await self._get_revision(agent, args, ctx)
|
|
1036
|
+
|
|
1037
|
+
elif tool_name == "restore_revision":
|
|
1038
|
+
return await self._restore_revision(agent, args, ctx)
|
|
1039
|
+
|
|
1040
|
+
# Multi-agent / Sub-agent tools
|
|
1041
|
+
elif tool_name == "list_available_agents":
|
|
1042
|
+
return await self._list_available_agents(agent, args, ctx)
|
|
1043
|
+
|
|
1044
|
+
elif tool_name == "add_sub_agent_tool":
|
|
1045
|
+
return await self._add_sub_agent_tool(agent, args, ctx)
|
|
1046
|
+
|
|
1047
|
+
elif tool_name == "list_sub_agent_tools":
|
|
1048
|
+
return await self._list_sub_agent_tools(agent, args, ctx)
|
|
1049
|
+
|
|
1050
|
+
elif tool_name == "remove_sub_agent_tool":
|
|
1051
|
+
return await self._remove_sub_agent_tool(agent, args, ctx)
|
|
1052
|
+
|
|
1053
|
+
elif tool_name == "update_sub_agent_tool":
|
|
1054
|
+
return await self._update_sub_agent_tool(agent, args, ctx)
|
|
1055
|
+
|
|
1056
|
+
else:
|
|
1057
|
+
return {"error": f"Unknown tool: {tool_name}"}
|
|
1058
|
+
|
|
1059
|
+
except AgentDefinition.DoesNotExist:
|
|
1060
|
+
return {"error": "Agent not found"}
|
|
1061
|
+
except Exception as e:
|
|
1062
|
+
logger.exception(f"Error executing builder tool {tool_name}")
|
|
1063
|
+
return {"error": str(e)}
|
|
1064
|
+
|
|
1065
|
+
async def _create_agent(self, args: dict, ctx: RunContext) -> dict:
|
|
1066
|
+
"""Create a new agent with the given name and description."""
|
|
1067
|
+
from django.utils.text import slugify
|
|
1068
|
+
|
|
1069
|
+
name = args.get("name", "New Agent")
|
|
1070
|
+
description = args.get("description", "")
|
|
1071
|
+
system_prompt = args.get("system_prompt", "You are a helpful assistant.")
|
|
1072
|
+
|
|
1073
|
+
# Generate a unique slug
|
|
1074
|
+
base_slug = slugify(name)
|
|
1075
|
+
slug = base_slug
|
|
1076
|
+
counter = 1
|
|
1077
|
+
while await sync_to_async(AgentDefinition.objects.filter(slug=slug).exists)():
|
|
1078
|
+
slug = f"{base_slug}-{counter}"
|
|
1079
|
+
counter += 1
|
|
1080
|
+
|
|
1081
|
+
try:
|
|
1082
|
+
# Create the agent
|
|
1083
|
+
agent = await sync_to_async(AgentDefinition.objects.create)(
|
|
1084
|
+
name=name,
|
|
1085
|
+
slug=slug,
|
|
1086
|
+
description=description,
|
|
1087
|
+
is_active=True,
|
|
1088
|
+
)
|
|
1089
|
+
|
|
1090
|
+
# Create the initial version
|
|
1091
|
+
await sync_to_async(AgentVersion.objects.create)(
|
|
1092
|
+
agent=agent,
|
|
1093
|
+
version="1.0",
|
|
1094
|
+
system_prompt=system_prompt,
|
|
1095
|
+
model="gpt-4o",
|
|
1096
|
+
is_active=True,
|
|
1097
|
+
is_draft=False,
|
|
1098
|
+
)
|
|
1099
|
+
|
|
1100
|
+
# Create initial revision
|
|
1101
|
+
await create_revision(agent, comment="Initial creation")
|
|
1102
|
+
|
|
1103
|
+
return {
|
|
1104
|
+
"success": True,
|
|
1105
|
+
"agent_id": str(agent.id),
|
|
1106
|
+
"slug": agent.slug,
|
|
1107
|
+
"message": f"Created agent '{name}' (slug: {slug}). You can now configure it with tools and knowledge.",
|
|
1108
|
+
}
|
|
1109
|
+
except Exception as e:
|
|
1110
|
+
logger.exception("Error creating agent")
|
|
1111
|
+
return {"error": str(e)}
|
|
1112
|
+
|
|
1113
|
+
async def _scan_project_for_tools(self, args: dict, ctx: RunContext) -> dict:
|
|
1114
|
+
"""Scan the Django project for functions that can be used as tools."""
|
|
1115
|
+
from django_agent_runtime.dynamic_tools.scanner import ProjectScanner
|
|
1116
|
+
|
|
1117
|
+
try:
|
|
1118
|
+
scanner = ProjectScanner(
|
|
1119
|
+
include_private=args.get("include_private", False),
|
|
1120
|
+
include_tests=False,
|
|
1121
|
+
app_filter=args.get("app_filter"),
|
|
1122
|
+
)
|
|
1123
|
+
|
|
1124
|
+
# Scan either a specific directory or all Django apps
|
|
1125
|
+
if args.get("directory"):
|
|
1126
|
+
functions = scanner.scan_directory(args["directory"])
|
|
1127
|
+
else:
|
|
1128
|
+
functions = scanner.scan()
|
|
1129
|
+
|
|
1130
|
+
# Store in context for later use
|
|
1131
|
+
ctx.state["discovered_functions"] = [f.to_dict() for f in functions]
|
|
1132
|
+
|
|
1133
|
+
# Return summary
|
|
1134
|
+
by_type = {}
|
|
1135
|
+
for f in functions:
|
|
1136
|
+
ftype = f.function_type
|
|
1137
|
+
by_type[ftype] = by_type.get(ftype, 0) + 1
|
|
1138
|
+
|
|
1139
|
+
return {
|
|
1140
|
+
"success": True,
|
|
1141
|
+
"total_discovered": len(functions),
|
|
1142
|
+
"by_type": by_type,
|
|
1143
|
+
"message": f"Discovered {len(functions)} functions. Use list_discovered_functions to see details.",
|
|
1144
|
+
}
|
|
1145
|
+
except Exception as e:
|
|
1146
|
+
logger.exception("Error scanning project")
|
|
1147
|
+
return {"error": str(e)}
|
|
1148
|
+
|
|
1149
|
+
async def _list_discovered_functions(self, args: dict, ctx: RunContext) -> dict:
|
|
1150
|
+
"""List discovered functions from the most recent scan."""
|
|
1151
|
+
functions = ctx.state.get("discovered_functions", [])
|
|
1152
|
+
|
|
1153
|
+
if not functions:
|
|
1154
|
+
return {
|
|
1155
|
+
"error": "No functions discovered yet. Run scan_project_for_tools first."
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
# Apply filters
|
|
1159
|
+
filtered = functions
|
|
1160
|
+
|
|
1161
|
+
if args.get("function_type"):
|
|
1162
|
+
filtered = [f for f in filtered if f["function_type"] == args["function_type"]]
|
|
1163
|
+
|
|
1164
|
+
if args.get("module_filter"):
|
|
1165
|
+
module_filter = args["module_filter"].lower()
|
|
1166
|
+
filtered = [f for f in filtered if module_filter in f["module_path"].lower()]
|
|
1167
|
+
|
|
1168
|
+
if args.get("safe_only"):
|
|
1169
|
+
filtered = [f for f in filtered if not f["has_side_effects"]]
|
|
1170
|
+
|
|
1171
|
+
# Apply limit
|
|
1172
|
+
limit = args.get("limit", 50)
|
|
1173
|
+
filtered = filtered[:limit]
|
|
1174
|
+
|
|
1175
|
+
# Return simplified view
|
|
1176
|
+
results = []
|
|
1177
|
+
for f in filtered:
|
|
1178
|
+
results.append({
|
|
1179
|
+
"name": f["name"],
|
|
1180
|
+
"function_path": f["function_path"],
|
|
1181
|
+
"function_type": f["function_type"],
|
|
1182
|
+
"signature": f["signature"],
|
|
1183
|
+
"has_side_effects": f["has_side_effects"],
|
|
1184
|
+
"docstring_preview": (f["docstring"][:100] + "...") if len(f.get("docstring", "")) > 100 else f.get("docstring", ""),
|
|
1185
|
+
})
|
|
1186
|
+
|
|
1187
|
+
return {
|
|
1188
|
+
"total": len(functions),
|
|
1189
|
+
"filtered": len(results),
|
|
1190
|
+
"functions": results,
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
async def _get_function_details(self, args: dict, ctx: RunContext) -> dict:
|
|
1194
|
+
"""Get detailed information about a specific function."""
|
|
1195
|
+
functions = ctx.state.get("discovered_functions", [])
|
|
1196
|
+
function_path = args.get("function_path", "")
|
|
1197
|
+
|
|
1198
|
+
for f in functions:
|
|
1199
|
+
if f["function_path"] == function_path:
|
|
1200
|
+
return {
|
|
1201
|
+
"found": True,
|
|
1202
|
+
"function": f,
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
return {
|
|
1206
|
+
"found": False,
|
|
1207
|
+
"error": f"Function '{function_path}' not found in discovered functions. Run scan_project_for_tools first.",
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
async def _add_tool_from_function(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
1211
|
+
"""Add a discovered function as a dynamic tool to the agent."""
|
|
1212
|
+
from django_agent_runtime.models import DynamicTool
|
|
1213
|
+
from django_agent_runtime.dynamic_tools.generator import ToolGenerator
|
|
1214
|
+
|
|
1215
|
+
function_path = args.get("function_path", "")
|
|
1216
|
+
functions = ctx.state.get("discovered_functions", [])
|
|
1217
|
+
|
|
1218
|
+
# Find the function in discovered functions
|
|
1219
|
+
func_info = None
|
|
1220
|
+
for f in functions:
|
|
1221
|
+
if f["function_path"] == function_path:
|
|
1222
|
+
func_info = f
|
|
1223
|
+
break
|
|
1224
|
+
|
|
1225
|
+
if not func_info:
|
|
1226
|
+
return {
|
|
1227
|
+
"error": f"Function '{function_path}' not found. Run scan_project_for_tools first."
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
# Generate tool schema
|
|
1231
|
+
generator = ToolGenerator()
|
|
1232
|
+
|
|
1233
|
+
# Determine tool name
|
|
1234
|
+
tool_name = args.get("tool_name")
|
|
1235
|
+
if not tool_name:
|
|
1236
|
+
tool_name = func_info["name"]
|
|
1237
|
+
if func_info.get("class_name"):
|
|
1238
|
+
tool_name = f"{func_info['class_name'].lower()}_{tool_name}"
|
|
1239
|
+
|
|
1240
|
+
# Determine description
|
|
1241
|
+
description = args.get("description")
|
|
1242
|
+
if not description:
|
|
1243
|
+
description = func_info.get("docstring", "").split("\n\n")[0].strip()
|
|
1244
|
+
if not description:
|
|
1245
|
+
description = f"Execute {func_info['name'].replace('_', ' ')}"
|
|
1246
|
+
|
|
1247
|
+
# Determine requires_confirmation
|
|
1248
|
+
requires_confirmation = args.get("requires_confirmation")
|
|
1249
|
+
if requires_confirmation is None:
|
|
1250
|
+
requires_confirmation = func_info.get("has_side_effects", True)
|
|
1251
|
+
|
|
1252
|
+
# Build parameters schema from function parameters
|
|
1253
|
+
properties = {}
|
|
1254
|
+
required = []
|
|
1255
|
+
for param in func_info.get("parameters", []):
|
|
1256
|
+
param_name = param.get("name", "")
|
|
1257
|
+
if param_name.startswith("*"):
|
|
1258
|
+
continue
|
|
1259
|
+
|
|
1260
|
+
param_type = "string"
|
|
1261
|
+
annotation = param.get("annotation", "")
|
|
1262
|
+
if annotation:
|
|
1263
|
+
type_map = {"str": "string", "int": "integer", "float": "number", "bool": "boolean"}
|
|
1264
|
+
param_type = type_map.get(annotation.split("[")[0], "string")
|
|
1265
|
+
|
|
1266
|
+
properties[param_name] = {"type": param_type}
|
|
1267
|
+
if not param.get("has_default", False):
|
|
1268
|
+
required.append(param_name)
|
|
1269
|
+
|
|
1270
|
+
parameters_schema = {"type": "object", "properties": properties}
|
|
1271
|
+
if required:
|
|
1272
|
+
parameters_schema["required"] = required
|
|
1273
|
+
|
|
1274
|
+
# Check if tool already exists
|
|
1275
|
+
existing = await sync_to_async(DynamicTool.objects.filter(agent=agent, name=tool_name).first)()
|
|
1276
|
+
if existing:
|
|
1277
|
+
return {
|
|
1278
|
+
"error": f"Tool '{tool_name}' already exists for this agent. Remove it first or use a different name."
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
# Create the dynamic tool
|
|
1282
|
+
tool = await sync_to_async(DynamicTool.objects.create)(
|
|
1283
|
+
agent=agent,
|
|
1284
|
+
name=tool_name,
|
|
1285
|
+
description=description,
|
|
1286
|
+
function_path=function_path,
|
|
1287
|
+
source_file=func_info.get("file_path", ""),
|
|
1288
|
+
source_line=func_info.get("line_number"),
|
|
1289
|
+
parameters_schema=parameters_schema,
|
|
1290
|
+
is_safe=not func_info.get("has_side_effects", True),
|
|
1291
|
+
requires_confirmation=requires_confirmation,
|
|
1292
|
+
)
|
|
1293
|
+
|
|
1294
|
+
# Create revision
|
|
1295
|
+
await create_revision(agent, comment=f"Added tool: {tool_name}")
|
|
1296
|
+
|
|
1297
|
+
return {
|
|
1298
|
+
"success": True,
|
|
1299
|
+
"message": f"Tool '{tool_name}' added successfully",
|
|
1300
|
+
"tool_id": str(tool.id),
|
|
1301
|
+
"tool_name": tool_name,
|
|
1302
|
+
"function_path": function_path,
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
async def _list_agent_tools(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
1306
|
+
"""List all tools assigned to the agent."""
|
|
1307
|
+
from django_agent_runtime.models import AgentTool, DynamicTool
|
|
1308
|
+
|
|
1309
|
+
include_inactive = args.get("include_inactive", False)
|
|
1310
|
+
|
|
1311
|
+
# Get static tools (AgentTool)
|
|
1312
|
+
static_query = agent.tools.all()
|
|
1313
|
+
if not include_inactive:
|
|
1314
|
+
static_query = static_query.filter(is_active=True)
|
|
1315
|
+
|
|
1316
|
+
static_tools = []
|
|
1317
|
+
for tool in await sync_to_async(list)(static_query):
|
|
1318
|
+
static_tools.append({
|
|
1319
|
+
"id": str(tool.id),
|
|
1320
|
+
"name": tool.name,
|
|
1321
|
+
"type": "static",
|
|
1322
|
+
"tool_type": tool.tool_type,
|
|
1323
|
+
"description": tool.description,
|
|
1324
|
+
"is_active": tool.is_active,
|
|
1325
|
+
})
|
|
1326
|
+
|
|
1327
|
+
# Get dynamic tools (DynamicTool)
|
|
1328
|
+
dynamic_query = agent.dynamic_tools.all()
|
|
1329
|
+
if not include_inactive:
|
|
1330
|
+
dynamic_query = dynamic_query.filter(is_active=True)
|
|
1331
|
+
|
|
1332
|
+
dynamic_tools = []
|
|
1333
|
+
for tool in await sync_to_async(list)(dynamic_query):
|
|
1334
|
+
dynamic_tools.append({
|
|
1335
|
+
"id": str(tool.id),
|
|
1336
|
+
"name": tool.name,
|
|
1337
|
+
"type": "dynamic",
|
|
1338
|
+
"function_path": tool.function_path,
|
|
1339
|
+
"description": tool.description,
|
|
1340
|
+
"is_active": tool.is_active,
|
|
1341
|
+
"is_safe": tool.is_safe,
|
|
1342
|
+
"requires_confirmation": tool.requires_confirmation,
|
|
1343
|
+
})
|
|
1344
|
+
|
|
1345
|
+
return {
|
|
1346
|
+
"static_tools": static_tools,
|
|
1347
|
+
"dynamic_tools": dynamic_tools,
|
|
1348
|
+
"total_static": len(static_tools),
|
|
1349
|
+
"total_dynamic": len(dynamic_tools),
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
async def _remove_tool(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
1353
|
+
"""Remove a tool from the agent."""
|
|
1354
|
+
from django_agent_runtime.models import AgentTool, DynamicTool
|
|
1355
|
+
|
|
1356
|
+
tool_name = args.get("tool_name")
|
|
1357
|
+
tool_id = args.get("tool_id")
|
|
1358
|
+
|
|
1359
|
+
if not tool_name and not tool_id:
|
|
1360
|
+
return {"error": "Either tool_name or tool_id must be provided"}
|
|
1361
|
+
|
|
1362
|
+
# Try to find and remove static tool
|
|
1363
|
+
static_query = agent.tools.all()
|
|
1364
|
+
if tool_id:
|
|
1365
|
+
static_query = static_query.filter(id=tool_id)
|
|
1366
|
+
elif tool_name:
|
|
1367
|
+
static_query = static_query.filter(name=tool_name)
|
|
1368
|
+
|
|
1369
|
+
static_tool = await sync_to_async(static_query.first)()
|
|
1370
|
+
if static_tool:
|
|
1371
|
+
name = static_tool.name
|
|
1372
|
+
await sync_to_async(static_tool.delete)()
|
|
1373
|
+
# Create revision
|
|
1374
|
+
await create_revision(agent, comment=f"Removed tool: {name}")
|
|
1375
|
+
return {"success": True, "message": f"Static tool '{name}' removed"}
|
|
1376
|
+
|
|
1377
|
+
# Try to find and remove dynamic tool
|
|
1378
|
+
dynamic_query = agent.dynamic_tools.all()
|
|
1379
|
+
if tool_id:
|
|
1380
|
+
dynamic_query = dynamic_query.filter(id=tool_id)
|
|
1381
|
+
elif tool_name:
|
|
1382
|
+
dynamic_query = dynamic_query.filter(name=tool_name)
|
|
1383
|
+
|
|
1384
|
+
dynamic_tool = await sync_to_async(dynamic_query.first)()
|
|
1385
|
+
if dynamic_tool:
|
|
1386
|
+
name = dynamic_tool.name
|
|
1387
|
+
await sync_to_async(dynamic_tool.delete)()
|
|
1388
|
+
# Create revision
|
|
1389
|
+
await create_revision(agent, comment=f"Removed tool: {name}")
|
|
1390
|
+
return {"success": True, "message": f"Dynamic tool '{name}' removed"}
|
|
1391
|
+
|
|
1392
|
+
return {"error": f"Tool not found: {tool_name or tool_id}"}
|
|
1393
|
+
|
|
1394
|
+
async def _list_revisions(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
1395
|
+
"""List all revisions for the agent."""
|
|
1396
|
+
limit = args.get("limit", 20)
|
|
1397
|
+
|
|
1398
|
+
revisions = await sync_to_async(list)(
|
|
1399
|
+
agent.revisions.all()[:limit]
|
|
1400
|
+
)
|
|
1401
|
+
|
|
1402
|
+
result = []
|
|
1403
|
+
for rev in revisions:
|
|
1404
|
+
result.append({
|
|
1405
|
+
"revision_number": rev.revision_number,
|
|
1406
|
+
"comment": rev.comment,
|
|
1407
|
+
"created_at": rev.created_at.isoformat(),
|
|
1408
|
+
"created_by": str(rev.created_by) if rev.created_by else None,
|
|
1409
|
+
})
|
|
1410
|
+
|
|
1411
|
+
return {
|
|
1412
|
+
"revisions": result,
|
|
1413
|
+
"total": len(result),
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
async def _get_revision(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
1417
|
+
"""Get a specific revision's content."""
|
|
1418
|
+
revision_number = args.get("revision_number")
|
|
1419
|
+
|
|
1420
|
+
if not revision_number:
|
|
1421
|
+
return {"error": "revision_number is required"}
|
|
1422
|
+
|
|
1423
|
+
revision = await sync_to_async(
|
|
1424
|
+
agent.revisions.filter(revision_number=revision_number).first
|
|
1425
|
+
)()
|
|
1426
|
+
|
|
1427
|
+
if not revision:
|
|
1428
|
+
return {"error": f"Revision {revision_number} not found"}
|
|
1429
|
+
|
|
1430
|
+
return {
|
|
1431
|
+
"revision_number": revision.revision_number,
|
|
1432
|
+
"comment": revision.comment,
|
|
1433
|
+
"created_at": revision.created_at.isoformat(),
|
|
1434
|
+
"content": revision.content,
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
async def _restore_revision(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
1438
|
+
"""Restore the agent to a previous revision."""
|
|
1439
|
+
revision_number = args.get("revision_number")
|
|
1440
|
+
|
|
1441
|
+
if not revision_number:
|
|
1442
|
+
return {"error": "revision_number is required"}
|
|
1443
|
+
|
|
1444
|
+
revision = await sync_to_async(
|
|
1445
|
+
agent.revisions.filter(revision_number=revision_number).first
|
|
1446
|
+
)()
|
|
1447
|
+
|
|
1448
|
+
if not revision:
|
|
1449
|
+
return {"error": f"Revision {revision_number} not found"}
|
|
1450
|
+
|
|
1451
|
+
# Restore creates a new revision with the old content
|
|
1452
|
+
new_revision = await sync_to_async(revision.restore)()
|
|
1453
|
+
|
|
1454
|
+
return {
|
|
1455
|
+
"success": True,
|
|
1456
|
+
"message": f"Restored to revision {revision_number}",
|
|
1457
|
+
"new_revision_number": new_revision.revision_number,
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
# ==========================================================================
|
|
1461
|
+
# RAG Knowledge Management Tools
|
|
1462
|
+
# ==========================================================================
|
|
1463
|
+
|
|
1464
|
+
async def _index_knowledge(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
1465
|
+
"""Index RAG knowledge sources for similarity search."""
|
|
1466
|
+
from django_agent_runtime.rag import KnowledgeIndexer
|
|
1467
|
+
|
|
1468
|
+
try:
|
|
1469
|
+
indexer = KnowledgeIndexer()
|
|
1470
|
+
knowledge_id = args.get("knowledge_id")
|
|
1471
|
+
force = args.get("force", False)
|
|
1472
|
+
|
|
1473
|
+
if knowledge_id:
|
|
1474
|
+
# Index specific knowledge
|
|
1475
|
+
result = await indexer.index_knowledge(knowledge_id, force=force)
|
|
1476
|
+
else:
|
|
1477
|
+
# Index all pending RAG knowledge for this agent
|
|
1478
|
+
result = await indexer.index_agent_knowledge(str(agent.id), force=force)
|
|
1479
|
+
|
|
1480
|
+
return result
|
|
1481
|
+
|
|
1482
|
+
except Exception as e:
|
|
1483
|
+
logger.exception("Error indexing knowledge")
|
|
1484
|
+
return {"error": str(e)}
|
|
1485
|
+
|
|
1486
|
+
async def _get_rag_status(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
1487
|
+
"""Get the indexing status of RAG knowledge sources."""
|
|
1488
|
+
from django_agent_runtime.rag import KnowledgeIndexer
|
|
1489
|
+
|
|
1490
|
+
try:
|
|
1491
|
+
indexer = KnowledgeIndexer()
|
|
1492
|
+
return await indexer.get_indexing_status(str(agent.id))
|
|
1493
|
+
except Exception as e:
|
|
1494
|
+
logger.exception("Error getting RAG status")
|
|
1495
|
+
return {"error": str(e)}
|
|
1496
|
+
|
|
1497
|
+
async def _preview_rag_search(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
1498
|
+
"""Preview RAG search results for a query."""
|
|
1499
|
+
from django_agent_runtime.rag import KnowledgeRetriever
|
|
1500
|
+
|
|
1501
|
+
query = args.get("query")
|
|
1502
|
+
if not query:
|
|
1503
|
+
return {"error": "query is required"}
|
|
1504
|
+
|
|
1505
|
+
try:
|
|
1506
|
+
retriever = KnowledgeRetriever()
|
|
1507
|
+
return await retriever.preview_search(
|
|
1508
|
+
agent_id=str(agent.id),
|
|
1509
|
+
query=query,
|
|
1510
|
+
top_k=args.get("top_k", 5),
|
|
1511
|
+
)
|
|
1512
|
+
except Exception as e:
|
|
1513
|
+
logger.exception("Error previewing RAG search")
|
|
1514
|
+
return {"error": str(e)}
|
|
1515
|
+
|
|
1516
|
+
async def _update_rag_config(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
1517
|
+
"""Update the RAG configuration for the agent."""
|
|
1518
|
+
try:
|
|
1519
|
+
# Get current config
|
|
1520
|
+
current_config = agent.rag_config or {}
|
|
1521
|
+
|
|
1522
|
+
# Update with provided values
|
|
1523
|
+
if "enabled" in args:
|
|
1524
|
+
current_config["enabled"] = args["enabled"]
|
|
1525
|
+
if "top_k" in args:
|
|
1526
|
+
current_config["top_k"] = args["top_k"]
|
|
1527
|
+
if "similarity_threshold" in args:
|
|
1528
|
+
current_config["similarity_threshold"] = args["similarity_threshold"]
|
|
1529
|
+
if "chunk_size" in args:
|
|
1530
|
+
current_config["chunk_size"] = args["chunk_size"]
|
|
1531
|
+
if "chunk_overlap" in args:
|
|
1532
|
+
current_config["chunk_overlap"] = args["chunk_overlap"]
|
|
1533
|
+
|
|
1534
|
+
# Save
|
|
1535
|
+
agent.rag_config = current_config
|
|
1536
|
+
await sync_to_async(agent.save)(update_fields=["rag_config"])
|
|
1537
|
+
await create_revision(agent, comment="Updated RAG configuration")
|
|
1538
|
+
|
|
1539
|
+
return {
|
|
1540
|
+
"success": True,
|
|
1541
|
+
"message": "RAG configuration updated",
|
|
1542
|
+
"config": current_config,
|
|
1543
|
+
}
|
|
1544
|
+
except Exception as e:
|
|
1545
|
+
logger.exception("Error updating RAG config")
|
|
1546
|
+
return {"error": str(e)}
|
|
1547
|
+
|
|
1548
|
+
# ==========================================================================
|
|
1549
|
+
# Multi-Agent / Sub-Agent Tools
|
|
1550
|
+
# ==========================================================================
|
|
1551
|
+
|
|
1552
|
+
async def _list_available_agents(self, current_agent, args: dict, ctx: RunContext) -> dict:
|
|
1553
|
+
"""List all agents that can be used as sub-agents."""
|
|
1554
|
+
include_inactive = args.get("include_inactive", False)
|
|
1555
|
+
search = args.get("search", "")
|
|
1556
|
+
|
|
1557
|
+
try:
|
|
1558
|
+
# Get all agents except the current one (can't be sub-agent of itself)
|
|
1559
|
+
query = AgentDefinition.objects.exclude(id=current_agent.id)
|
|
1560
|
+
|
|
1561
|
+
if not include_inactive:
|
|
1562
|
+
query = query.filter(is_active=True)
|
|
1563
|
+
|
|
1564
|
+
if search:
|
|
1565
|
+
from django.db.models import Q
|
|
1566
|
+
query = query.filter(
|
|
1567
|
+
Q(name__icontains=search) |
|
|
1568
|
+
Q(description__icontains=search) |
|
|
1569
|
+
Q(slug__icontains=search)
|
|
1570
|
+
)
|
|
1571
|
+
|
|
1572
|
+
agents = await sync_to_async(list)(query.order_by("name")[:50])
|
|
1573
|
+
|
|
1574
|
+
results = []
|
|
1575
|
+
for agent in agents:
|
|
1576
|
+
results.append({
|
|
1577
|
+
"slug": agent.slug,
|
|
1578
|
+
"name": agent.name,
|
|
1579
|
+
"description": agent.description or "",
|
|
1580
|
+
"is_active": agent.is_active,
|
|
1581
|
+
})
|
|
1582
|
+
|
|
1583
|
+
return {
|
|
1584
|
+
"agents": results,
|
|
1585
|
+
"total": len(results),
|
|
1586
|
+
"message": f"Found {len(results)} available agents",
|
|
1587
|
+
}
|
|
1588
|
+
except Exception as e:
|
|
1589
|
+
logger.exception("Error listing available agents")
|
|
1590
|
+
return {"error": str(e)}
|
|
1591
|
+
|
|
1592
|
+
async def _add_sub_agent_tool(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
1593
|
+
"""Add a sub-agent tool to the agent."""
|
|
1594
|
+
from django_agent_runtime.models import SubAgentTool
|
|
1595
|
+
from django_agent_studio.services.permissions import get_permission_service
|
|
1596
|
+
|
|
1597
|
+
sub_agent_slug = args.get("sub_agent_slug")
|
|
1598
|
+
tool_name = args.get("tool_name")
|
|
1599
|
+
description = args.get("description")
|
|
1600
|
+
context_mode = args.get("context_mode", "message_only")
|
|
1601
|
+
|
|
1602
|
+
if not all([sub_agent_slug, tool_name, description]):
|
|
1603
|
+
return {"error": "sub_agent_slug, tool_name, and description are required"}
|
|
1604
|
+
|
|
1605
|
+
# Check permissions
|
|
1606
|
+
user = ctx.metadata.get("user")
|
|
1607
|
+
if user:
|
|
1608
|
+
permission_service = get_permission_service()
|
|
1609
|
+
if not permission_service.can_create_tool(user, agent):
|
|
1610
|
+
return {
|
|
1611
|
+
"error": "Permission denied: You need CREATOR or ADMIN access level to add sub-agent tools.",
|
|
1612
|
+
"hint": "Contact an administrator to request elevated permissions.",
|
|
1613
|
+
"current_level": permission_service.get_user_access_level(user, agent),
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
try:
|
|
1617
|
+
# Find the sub-agent
|
|
1618
|
+
sub_agent = await sync_to_async(AgentDefinition.objects.get)(slug=sub_agent_slug)
|
|
1619
|
+
|
|
1620
|
+
# Check for circular reference
|
|
1621
|
+
if sub_agent.id == agent.id:
|
|
1622
|
+
return {"error": "An agent cannot be a sub-agent of itself"}
|
|
1623
|
+
|
|
1624
|
+
# Check if tool name already exists
|
|
1625
|
+
existing = await sync_to_async(
|
|
1626
|
+
SubAgentTool.objects.filter(parent_agent=agent, name=tool_name).exists
|
|
1627
|
+
)()
|
|
1628
|
+
if existing:
|
|
1629
|
+
return {
|
|
1630
|
+
"error": f"A sub-agent tool named '{tool_name}' already exists",
|
|
1631
|
+
"hint": "Use a different name or remove the existing tool first",
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
# Create the sub-agent tool
|
|
1635
|
+
sub_agent_tool = await sync_to_async(SubAgentTool.objects.create)(
|
|
1636
|
+
parent_agent=agent,
|
|
1637
|
+
sub_agent=sub_agent,
|
|
1638
|
+
name=tool_name,
|
|
1639
|
+
description=description,
|
|
1640
|
+
context_mode=context_mode,
|
|
1641
|
+
)
|
|
1642
|
+
|
|
1643
|
+
# Create revision
|
|
1644
|
+
await create_revision(agent, comment=f"Added sub-agent tool: {tool_name} -> {sub_agent.name}")
|
|
1645
|
+
|
|
1646
|
+
return {
|
|
1647
|
+
"success": True,
|
|
1648
|
+
"message": f"Added sub-agent tool '{tool_name}' pointing to '{sub_agent.name}'",
|
|
1649
|
+
"tool_id": str(sub_agent_tool.id),
|
|
1650
|
+
"tool_name": tool_name,
|
|
1651
|
+
"sub_agent_slug": sub_agent_slug,
|
|
1652
|
+
"context_mode": context_mode,
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
except AgentDefinition.DoesNotExist:
|
|
1656
|
+
return {
|
|
1657
|
+
"error": f"Sub-agent '{sub_agent_slug}' not found",
|
|
1658
|
+
"hint": "Use list_available_agents to see available agents",
|
|
1659
|
+
}
|
|
1660
|
+
except Exception as e:
|
|
1661
|
+
logger.exception("Error adding sub-agent tool")
|
|
1662
|
+
return {"error": str(e)}
|
|
1663
|
+
|
|
1664
|
+
async def _list_sub_agent_tools(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
1665
|
+
"""List all sub-agent tools for the agent."""
|
|
1666
|
+
from django_agent_runtime.models import SubAgentTool
|
|
1667
|
+
|
|
1668
|
+
try:
|
|
1669
|
+
tools = await sync_to_async(list)(
|
|
1670
|
+
agent.sub_agent_tools.select_related("sub_agent").all()
|
|
1671
|
+
)
|
|
1672
|
+
|
|
1673
|
+
results = []
|
|
1674
|
+
for tool in tools:
|
|
1675
|
+
results.append({
|
|
1676
|
+
"id": str(tool.id),
|
|
1677
|
+
"name": tool.name,
|
|
1678
|
+
"description": tool.description,
|
|
1679
|
+
"sub_agent_slug": tool.sub_agent.slug,
|
|
1680
|
+
"sub_agent_name": tool.sub_agent.name,
|
|
1681
|
+
"context_mode": tool.context_mode,
|
|
1682
|
+
"is_active": tool.is_active,
|
|
1683
|
+
})
|
|
1684
|
+
|
|
1685
|
+
return {
|
|
1686
|
+
"sub_agent_tools": results,
|
|
1687
|
+
"total": len(results),
|
|
1688
|
+
}
|
|
1689
|
+
except Exception as e:
|
|
1690
|
+
logger.exception("Error listing sub-agent tools")
|
|
1691
|
+
return {"error": str(e)}
|
|
1692
|
+
|
|
1693
|
+
async def _remove_sub_agent_tool(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
1694
|
+
"""Remove a sub-agent tool from the agent."""
|
|
1695
|
+
from django_agent_runtime.models import SubAgentTool
|
|
1696
|
+
from django_agent_studio.services.permissions import get_permission_service
|
|
1697
|
+
|
|
1698
|
+
tool_name = args.get("tool_name")
|
|
1699
|
+
if not tool_name:
|
|
1700
|
+
return {"error": "tool_name is required"}
|
|
1701
|
+
|
|
1702
|
+
# Check permissions
|
|
1703
|
+
user = ctx.metadata.get("user")
|
|
1704
|
+
if user:
|
|
1705
|
+
permission_service = get_permission_service()
|
|
1706
|
+
if not permission_service.can_create_tool(user, agent):
|
|
1707
|
+
return {
|
|
1708
|
+
"error": "Permission denied: You need CREATOR or ADMIN access level to remove sub-agent tools.",
|
|
1709
|
+
"hint": "Contact an administrator to request elevated permissions.",
|
|
1710
|
+
"current_level": permission_service.get_user_access_level(user, agent),
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
try:
|
|
1714
|
+
tool = await sync_to_async(
|
|
1715
|
+
agent.sub_agent_tools.filter(name=tool_name).first
|
|
1716
|
+
)()
|
|
1717
|
+
|
|
1718
|
+
if not tool:
|
|
1719
|
+
return {
|
|
1720
|
+
"error": f"Sub-agent tool '{tool_name}' not found",
|
|
1721
|
+
"hint": "Use list_sub_agent_tools to see existing tools",
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
sub_agent_name = await sync_to_async(lambda: tool.sub_agent.name)()
|
|
1725
|
+
await sync_to_async(tool.delete)()
|
|
1726
|
+
|
|
1727
|
+
# Create revision
|
|
1728
|
+
await create_revision(agent, comment=f"Removed sub-agent tool: {tool_name}")
|
|
1729
|
+
|
|
1730
|
+
return {
|
|
1731
|
+
"success": True,
|
|
1732
|
+
"message": f"Removed sub-agent tool '{tool_name}' (was pointing to '{sub_agent_name}')",
|
|
1733
|
+
}
|
|
1734
|
+
except Exception as e:
|
|
1735
|
+
logger.exception("Error removing sub-agent tool")
|
|
1736
|
+
return {"error": str(e)}
|
|
1737
|
+
|
|
1738
|
+
async def _update_sub_agent_tool(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
1739
|
+
"""Update a sub-agent tool's configuration."""
|
|
1740
|
+
from django_agent_runtime.models import SubAgentTool
|
|
1741
|
+
from django_agent_studio.services.permissions import get_permission_service
|
|
1742
|
+
|
|
1743
|
+
tool_name = args.get("tool_name")
|
|
1744
|
+
if not tool_name:
|
|
1745
|
+
return {"error": "tool_name is required"}
|
|
1746
|
+
|
|
1747
|
+
# Check permissions
|
|
1748
|
+
user = ctx.metadata.get("user")
|
|
1749
|
+
if user:
|
|
1750
|
+
permission_service = get_permission_service()
|
|
1751
|
+
if not permission_service.can_create_tool(user, agent):
|
|
1752
|
+
return {
|
|
1753
|
+
"error": "Permission denied: You need CREATOR or ADMIN access level to update sub-agent tools.",
|
|
1754
|
+
"hint": "Contact an administrator to request elevated permissions.",
|
|
1755
|
+
"current_level": permission_service.get_user_access_level(user, agent),
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
try:
|
|
1759
|
+
tool = await sync_to_async(
|
|
1760
|
+
agent.sub_agent_tools.filter(name=tool_name).first
|
|
1761
|
+
)()
|
|
1762
|
+
|
|
1763
|
+
if not tool:
|
|
1764
|
+
return {
|
|
1765
|
+
"error": f"Sub-agent tool '{tool_name}' not found",
|
|
1766
|
+
"hint": "Use list_sub_agent_tools to see existing tools",
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
changes = []
|
|
1770
|
+
if "description" in args:
|
|
1771
|
+
tool.description = args["description"]
|
|
1772
|
+
changes.append("description")
|
|
1773
|
+
if "context_mode" in args:
|
|
1774
|
+
tool.context_mode = args["context_mode"]
|
|
1775
|
+
changes.append(f"context_mode={args['context_mode']}")
|
|
1776
|
+
|
|
1777
|
+
if not changes:
|
|
1778
|
+
return {"error": "No changes specified. Provide description or context_mode."}
|
|
1779
|
+
|
|
1780
|
+
await sync_to_async(tool.save)()
|
|
1781
|
+
|
|
1782
|
+
# Create revision
|
|
1783
|
+
await create_revision(agent, comment=f"Updated sub-agent tool {tool_name}: {', '.join(changes)}")
|
|
1784
|
+
|
|
1785
|
+
return {
|
|
1786
|
+
"success": True,
|
|
1787
|
+
"message": f"Updated sub-agent tool '{tool_name}'",
|
|
1788
|
+
"changes": changes,
|
|
1789
|
+
}
|
|
1790
|
+
except Exception as e:
|
|
1791
|
+
logger.exception("Error updating sub-agent tool")
|
|
1792
|
+
return {"error": str(e)}
|
|
1793
|
+
|
|
1794
|
+
# ==========================================================================
|
|
1795
|
+
# UI Control Tools - Allow builder to switch agents/systems in the UI
|
|
1796
|
+
# ==========================================================================
|
|
1797
|
+
|
|
1798
|
+
async def _switch_to_agent(self, args: dict, ctx: RunContext) -> dict:
|
|
1799
|
+
"""Switch the UI to edit a different agent."""
|
|
1800
|
+
agent_slug = args.get("agent_slug")
|
|
1801
|
+
agent_id = args.get("agent_id")
|
|
1802
|
+
|
|
1803
|
+
if not agent_slug and not agent_id:
|
|
1804
|
+
return {"error": "Either agent_slug or agent_id must be provided"}
|
|
1805
|
+
|
|
1806
|
+
try:
|
|
1807
|
+
if agent_id:
|
|
1808
|
+
agent = await sync_to_async(AgentDefinition.objects.get)(id=agent_id)
|
|
1809
|
+
else:
|
|
1810
|
+
agent = await sync_to_async(AgentDefinition.objects.get)(slug=agent_slug)
|
|
1811
|
+
|
|
1812
|
+
# Emit a special UI control event that the frontend can listen for
|
|
1813
|
+
await ctx.emit("ui.control", {
|
|
1814
|
+
"type": "ui_control",
|
|
1815
|
+
"action": "switch_agent",
|
|
1816
|
+
"agent_id": str(agent.id),
|
|
1817
|
+
"agent_slug": agent.slug,
|
|
1818
|
+
"agent_name": agent.name,
|
|
1819
|
+
})
|
|
1820
|
+
|
|
1821
|
+
return {
|
|
1822
|
+
"success": True,
|
|
1823
|
+
"message": f"Switched to agent '{agent.name}' ({agent.slug})",
|
|
1824
|
+
"agent_id": str(agent.id),
|
|
1825
|
+
"agent_slug": agent.slug,
|
|
1826
|
+
"agent_name": agent.name,
|
|
1827
|
+
}
|
|
1828
|
+
except AgentDefinition.DoesNotExist:
|
|
1829
|
+
return {"error": f"Agent not found: {agent_slug or agent_id}"}
|
|
1830
|
+
except Exception as e:
|
|
1831
|
+
logger.exception("Error switching agent")
|
|
1832
|
+
return {"error": str(e)}
|
|
1833
|
+
|
|
1834
|
+
async def _switch_to_system(self, args: dict, ctx: RunContext) -> dict:
|
|
1835
|
+
"""Switch the UI to work with a multi-agent system."""
|
|
1836
|
+
from django_agent_runtime.models import AgentSystem
|
|
1837
|
+
|
|
1838
|
+
system_slug = args.get("system_slug")
|
|
1839
|
+
system_id = args.get("system_id")
|
|
1840
|
+
|
|
1841
|
+
if not system_slug and not system_id:
|
|
1842
|
+
return {"error": "Either system_slug or system_id must be provided"}
|
|
1843
|
+
|
|
1844
|
+
try:
|
|
1845
|
+
if system_id:
|
|
1846
|
+
system = await sync_to_async(AgentSystem.objects.get)(id=system_id)
|
|
1847
|
+
else:
|
|
1848
|
+
system = await sync_to_async(AgentSystem.objects.get)(slug=system_slug)
|
|
1849
|
+
|
|
1850
|
+
# Emit a special UI control event
|
|
1851
|
+
await ctx.emit("ui.control", {
|
|
1852
|
+
"type": "ui_control",
|
|
1853
|
+
"action": "switch_system",
|
|
1854
|
+
"system_id": str(system.id),
|
|
1855
|
+
"system_slug": system.slug,
|
|
1856
|
+
"system_name": system.name,
|
|
1857
|
+
})
|
|
1858
|
+
|
|
1859
|
+
return {
|
|
1860
|
+
"success": True,
|
|
1861
|
+
"message": f"Switched to system '{system.name}' ({system.slug})",
|
|
1862
|
+
"system_id": str(system.id),
|
|
1863
|
+
"system_slug": system.slug,
|
|
1864
|
+
"system_name": system.name,
|
|
1865
|
+
}
|
|
1866
|
+
except AgentSystem.DoesNotExist:
|
|
1867
|
+
return {"error": f"System not found: {system_slug or system_id}"}
|
|
1868
|
+
except Exception as e:
|
|
1869
|
+
logger.exception("Error switching system")
|
|
1870
|
+
return {"error": str(e)}
|
|
1871
|
+
|
|
1872
|
+
async def _list_all_agents(self, args: dict, ctx: RunContext) -> dict:
|
|
1873
|
+
"""List all available agents in the system."""
|
|
1874
|
+
include_inactive = args.get("include_inactive", False)
|
|
1875
|
+
search = args.get("search", "")
|
|
1876
|
+
|
|
1877
|
+
try:
|
|
1878
|
+
query = AgentDefinition.objects.all()
|
|
1879
|
+
if not include_inactive:
|
|
1880
|
+
query = query.filter(is_active=True)
|
|
1881
|
+
if search:
|
|
1882
|
+
query = query.filter(name__icontains=search) | query.filter(description__icontains=search)
|
|
1883
|
+
|
|
1884
|
+
agents = await sync_to_async(list)(query.order_by("name")[:50])
|
|
1885
|
+
|
|
1886
|
+
result = []
|
|
1887
|
+
for agent in agents:
|
|
1888
|
+
result.append({
|
|
1889
|
+
"id": str(agent.id),
|
|
1890
|
+
"slug": agent.slug,
|
|
1891
|
+
"name": agent.name,
|
|
1892
|
+
"description": agent.description or "",
|
|
1893
|
+
"is_active": agent.is_active,
|
|
1894
|
+
"icon": agent.icon or "🤖",
|
|
1895
|
+
})
|
|
1896
|
+
|
|
1897
|
+
return {
|
|
1898
|
+
"agents": result,
|
|
1899
|
+
"total": len(result),
|
|
1900
|
+
}
|
|
1901
|
+
except Exception as e:
|
|
1902
|
+
logger.exception("Error listing agents")
|
|
1903
|
+
return {"error": str(e)}
|
|
1904
|
+
|
|
1905
|
+
async def _list_all_systems(self, args: dict, ctx: RunContext) -> dict:
|
|
1906
|
+
"""List all available multi-agent systems."""
|
|
1907
|
+
from django_agent_runtime.models import AgentSystem
|
|
1908
|
+
|
|
1909
|
+
include_inactive = args.get("include_inactive", False)
|
|
1910
|
+
search = args.get("search", "")
|
|
1911
|
+
|
|
1912
|
+
try:
|
|
1913
|
+
query = AgentSystem.objects.all()
|
|
1914
|
+
if not include_inactive:
|
|
1915
|
+
query = query.filter(is_active=True)
|
|
1916
|
+
if search:
|
|
1917
|
+
query = query.filter(name__icontains=search) | query.filter(description__icontains=search)
|
|
1918
|
+
|
|
1919
|
+
systems = await sync_to_async(list)(query.order_by("name")[:50])
|
|
1920
|
+
|
|
1921
|
+
result = []
|
|
1922
|
+
for system in systems:
|
|
1923
|
+
member_count = await sync_to_async(system.members.count)()
|
|
1924
|
+
entry_agent = await sync_to_async(lambda: system.entry_agent)()
|
|
1925
|
+
result.append({
|
|
1926
|
+
"id": str(system.id),
|
|
1927
|
+
"slug": system.slug,
|
|
1928
|
+
"name": system.name,
|
|
1929
|
+
"description": system.description or "",
|
|
1930
|
+
"is_active": system.is_active,
|
|
1931
|
+
"member_count": member_count,
|
|
1932
|
+
"entry_agent_slug": entry_agent.slug if entry_agent else None,
|
|
1933
|
+
})
|
|
1934
|
+
|
|
1935
|
+
return {
|
|
1936
|
+
"systems": result,
|
|
1937
|
+
"total": len(result),
|
|
1938
|
+
}
|
|
1939
|
+
except Exception as e:
|
|
1940
|
+
logger.exception("Error listing systems")
|
|
1941
|
+
return {"error": str(e)}
|
|
1942
|
+
|
|
1943
|
+
async def _get_system_details(self, args: dict, ctx: RunContext) -> dict:
|
|
1944
|
+
"""Get detailed information about a multi-agent system."""
|
|
1945
|
+
from django_agent_runtime.models import AgentSystem
|
|
1946
|
+
|
|
1947
|
+
system_slug = args.get("system_slug")
|
|
1948
|
+
system_id = args.get("system_id")
|
|
1949
|
+
|
|
1950
|
+
if not system_slug and not system_id:
|
|
1951
|
+
return {"error": "Either system_slug or system_id must be provided"}
|
|
1952
|
+
|
|
1953
|
+
try:
|
|
1954
|
+
if system_id:
|
|
1955
|
+
system = await sync_to_async(AgentSystem.objects.get)(id=system_id)
|
|
1956
|
+
else:
|
|
1957
|
+
system = await sync_to_async(AgentSystem.objects.get)(slug=system_slug)
|
|
1958
|
+
|
|
1959
|
+
# Get members
|
|
1960
|
+
members = await sync_to_async(list)(system.members.select_related("agent").all())
|
|
1961
|
+
member_list = []
|
|
1962
|
+
for member in members:
|
|
1963
|
+
agent = await sync_to_async(lambda m=member: m.agent)()
|
|
1964
|
+
member_list.append({
|
|
1965
|
+
"id": str(member.id),
|
|
1966
|
+
"agent_id": str(agent.id),
|
|
1967
|
+
"agent_slug": agent.slug,
|
|
1968
|
+
"agent_name": agent.name,
|
|
1969
|
+
"role": member.role or "",
|
|
1970
|
+
"is_entry_point": member.is_entry_point,
|
|
1971
|
+
})
|
|
1972
|
+
|
|
1973
|
+
# Get entry agent
|
|
1974
|
+
entry_agent = await sync_to_async(lambda: system.entry_agent)()
|
|
1975
|
+
|
|
1976
|
+
return {
|
|
1977
|
+
"id": str(system.id),
|
|
1978
|
+
"slug": system.slug,
|
|
1979
|
+
"name": system.name,
|
|
1980
|
+
"description": system.description or "",
|
|
1981
|
+
"is_active": system.is_active,
|
|
1982
|
+
"entry_agent": {
|
|
1983
|
+
"id": str(entry_agent.id),
|
|
1984
|
+
"slug": entry_agent.slug,
|
|
1985
|
+
"name": entry_agent.name,
|
|
1986
|
+
} if entry_agent else None,
|
|
1987
|
+
"members": member_list,
|
|
1988
|
+
"member_count": len(member_list),
|
|
1989
|
+
}
|
|
1990
|
+
except AgentSystem.DoesNotExist:
|
|
1991
|
+
return {"error": f"System not found: {system_slug or system_id}"}
|
|
1992
|
+
except Exception as e:
|
|
1993
|
+
logger.exception("Error getting system details")
|
|
1994
|
+
return {"error": str(e)}
|