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
|
@@ -25,18 +25,19 @@ logger = logging.getLogger(__name__)
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
# =============================================================================
|
|
28
|
-
# Memory Tool
|
|
28
|
+
# Memory Tool Definitions
|
|
29
29
|
# =============================================================================
|
|
30
30
|
|
|
31
|
+
# Conversation-scoped memory (ephemeral, lost when conversation ends)
|
|
31
32
|
MEMORY_TOOL_SCHEMA = {
|
|
32
33
|
"type": "function",
|
|
33
34
|
"function": {
|
|
34
35
|
"name": "remember",
|
|
35
36
|
"description": (
|
|
36
|
-
"Store information to remember
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
37
|
+
"Store information to remember. Use this to remember important facts about "
|
|
38
|
+
"the user, their preferences, project details, or anything useful to recall. "
|
|
39
|
+
"Use semantic dot-notation keys like 'user.name', 'user.preferences.theme', "
|
|
40
|
+
"'project.goal'. By default, memories persist across conversations for this user."
|
|
40
41
|
),
|
|
41
42
|
"parameters": {
|
|
42
43
|
"type": "object",
|
|
@@ -44,20 +45,90 @@ MEMORY_TOOL_SCHEMA = {
|
|
|
44
45
|
"key": {
|
|
45
46
|
"type": "string",
|
|
46
47
|
"description": (
|
|
47
|
-
"A
|
|
48
|
-
"(e.g., '
|
|
48
|
+
"A semantic key using dot-notation for what you're remembering "
|
|
49
|
+
"(e.g., 'user.name', 'user.preferences.language', 'project.goal')"
|
|
49
50
|
),
|
|
50
51
|
},
|
|
51
52
|
"value": {
|
|
52
53
|
"type": "string",
|
|
53
54
|
"description": "The information to remember",
|
|
54
55
|
},
|
|
56
|
+
"scope": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"enum": ["conversation", "user", "system"],
|
|
59
|
+
"description": (
|
|
60
|
+
"Memory scope: 'conversation' (this chat only), "
|
|
61
|
+
"'user' (persists across chats, default), "
|
|
62
|
+
"'system' (shared with other agents)"
|
|
63
|
+
),
|
|
64
|
+
},
|
|
55
65
|
},
|
|
56
66
|
"required": ["key", "value"],
|
|
57
67
|
},
|
|
58
68
|
},
|
|
59
69
|
}
|
|
60
70
|
|
|
71
|
+
# Tool to recall memories
|
|
72
|
+
RECALL_TOOL_SCHEMA = {
|
|
73
|
+
"type": "function",
|
|
74
|
+
"function": {
|
|
75
|
+
"name": "recall",
|
|
76
|
+
"description": (
|
|
77
|
+
"Recall stored memories. Use this to retrieve information you've previously "
|
|
78
|
+
"remembered about the user or project. You can recall a specific key or list "
|
|
79
|
+
"all memories matching a prefix."
|
|
80
|
+
),
|
|
81
|
+
"parameters": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"properties": {
|
|
84
|
+
"key": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"description": (
|
|
87
|
+
"The specific key to recall (e.g., 'user.name') or a prefix "
|
|
88
|
+
"to list all matching memories (e.g., 'user.preferences')"
|
|
89
|
+
),
|
|
90
|
+
},
|
|
91
|
+
"scope": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"enum": ["conversation", "user", "system", "all"],
|
|
94
|
+
"description": (
|
|
95
|
+
"Which scope to search: 'conversation', 'user', 'system', "
|
|
96
|
+
"or 'all' (default) to search all scopes"
|
|
97
|
+
),
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
"required": ["key"],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Tool to forget memories
|
|
106
|
+
FORGET_TOOL_SCHEMA = {
|
|
107
|
+
"type": "function",
|
|
108
|
+
"function": {
|
|
109
|
+
"name": "forget",
|
|
110
|
+
"description": (
|
|
111
|
+
"Forget/delete a stored memory. Use this when the user asks you to forget "
|
|
112
|
+
"something or when information is no longer relevant."
|
|
113
|
+
),
|
|
114
|
+
"parameters": {
|
|
115
|
+
"type": "object",
|
|
116
|
+
"properties": {
|
|
117
|
+
"key": {
|
|
118
|
+
"type": "string",
|
|
119
|
+
"description": "The key of the memory to forget (e.g., 'user.name')",
|
|
120
|
+
},
|
|
121
|
+
"scope": {
|
|
122
|
+
"type": "string",
|
|
123
|
+
"enum": ["conversation", "user", "system"],
|
|
124
|
+
"description": "The scope of the memory to forget",
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
"required": ["key"],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
|
|
61
132
|
|
|
62
133
|
class DynamicAgentRuntime(AgentRuntime):
|
|
63
134
|
"""
|
|
@@ -78,19 +149,26 @@ class DynamicAgentRuntime(AgentRuntime):
|
|
|
78
149
|
|
|
79
150
|
@property
|
|
80
151
|
def config(self) -> dict:
|
|
81
|
-
"""Get the effective configuration (cached)."""
|
|
152
|
+
"""Get the effective configuration (cached). Use get_config_async() in async contexts."""
|
|
82
153
|
if self._config is None:
|
|
83
154
|
self._config = self._definition.get_effective_config()
|
|
84
155
|
return self._config
|
|
85
|
-
|
|
156
|
+
|
|
157
|
+
async def get_config_async(self) -> dict:
|
|
158
|
+
"""Get the effective configuration in an async-safe way."""
|
|
159
|
+
from asgiref.sync import sync_to_async
|
|
160
|
+
if self._config is None:
|
|
161
|
+
self._config = await sync_to_async(self._definition.get_effective_config)()
|
|
162
|
+
return self._config
|
|
163
|
+
|
|
86
164
|
def refresh_config(self):
|
|
87
165
|
"""Refresh the configuration from the database."""
|
|
88
166
|
self._definition.refresh_from_db()
|
|
89
167
|
self._config = None
|
|
90
|
-
|
|
168
|
+
|
|
91
169
|
async def run(self, ctx: RunContext) -> RunResult:
|
|
92
170
|
"""Execute the agent with the dynamic configuration and agentic loop."""
|
|
93
|
-
config = self.
|
|
171
|
+
config = await self.get_config_async()
|
|
94
172
|
|
|
95
173
|
# Check if memory is enabled (default: True)
|
|
96
174
|
extra_config = config.get("extra", {})
|
|
@@ -99,8 +177,23 @@ class DynamicAgentRuntime(AgentRuntime):
|
|
|
99
177
|
# Build the messages list
|
|
100
178
|
messages = []
|
|
101
179
|
|
|
102
|
-
#
|
|
103
|
-
|
|
180
|
+
# Start with system-level shared knowledge (if agent is part of a system)
|
|
181
|
+
system_context = await self._get_system_context()
|
|
182
|
+
system_prefix = ""
|
|
183
|
+
if system_context:
|
|
184
|
+
system_prefix = system_context.get_system_prompt_prefix()
|
|
185
|
+
logger.debug(f"Injecting system context from '{system_context.system_name}'")
|
|
186
|
+
|
|
187
|
+
# Add agent's system prompt
|
|
188
|
+
agent_prompt = config.get("system_prompt", "")
|
|
189
|
+
|
|
190
|
+
# Combine system prefix with agent prompt
|
|
191
|
+
if system_prefix and agent_prompt:
|
|
192
|
+
system_prompt = f"{system_prefix}\n\n---\n\n{agent_prompt}"
|
|
193
|
+
elif system_prefix:
|
|
194
|
+
system_prompt = system_prefix
|
|
195
|
+
else:
|
|
196
|
+
system_prompt = agent_prompt
|
|
104
197
|
|
|
105
198
|
# Add knowledge that should always be included
|
|
106
199
|
knowledge_context = self._build_knowledge_context(config)
|
|
@@ -117,7 +210,7 @@ class DynamicAgentRuntime(AgentRuntime):
|
|
|
117
210
|
if memory_enabled:
|
|
118
211
|
memory_store = await self._get_memory_store(ctx)
|
|
119
212
|
if memory_store:
|
|
120
|
-
memory_context = await self._recall_memories(memory_store)
|
|
213
|
+
memory_context = await self._recall_memories(memory_store, ctx)
|
|
121
214
|
if memory_context:
|
|
122
215
|
system_prompt = f"{system_prompt}\n\n{memory_context}"
|
|
123
216
|
|
|
@@ -127,10 +220,12 @@ class DynamicAgentRuntime(AgentRuntime):
|
|
|
127
220
|
# Add conversation history
|
|
128
221
|
messages.extend(ctx.input_messages)
|
|
129
222
|
|
|
130
|
-
# Build tool schemas - include memory
|
|
223
|
+
# Build tool schemas - include memory tools if memory is enabled
|
|
131
224
|
tools = self._build_tool_schemas(config)
|
|
132
225
|
if memory_enabled:
|
|
133
226
|
tools.append(MEMORY_TOOL_SCHEMA)
|
|
227
|
+
tools.append(RECALL_TOOL_SCHEMA)
|
|
228
|
+
tools.append(FORGET_TOOL_SCHEMA)
|
|
134
229
|
|
|
135
230
|
tool_map = self._build_tool_map(config) # Maps tool name to execution info
|
|
136
231
|
|
|
@@ -146,9 +241,13 @@ class DynamicAgentRuntime(AgentRuntime):
|
|
|
146
241
|
|
|
147
242
|
# Create tool executor function for the agentic loop
|
|
148
243
|
async def execute_tool(tool_name: str, tool_args: dict) -> str:
|
|
149
|
-
# Handle the built-in
|
|
244
|
+
# Handle the built-in memory tools
|
|
150
245
|
if tool_name == "remember":
|
|
151
|
-
return await self._execute_remember_tool(tool_args, memory_store)
|
|
246
|
+
return await self._execute_remember_tool(tool_args, memory_store, ctx)
|
|
247
|
+
if tool_name == "recall":
|
|
248
|
+
return await self._execute_recall_tool(tool_args, memory_store, ctx)
|
|
249
|
+
if tool_name == "forget":
|
|
250
|
+
return await self._execute_forget_tool(tool_args, memory_store, ctx)
|
|
152
251
|
|
|
153
252
|
return await self._execute_tool(
|
|
154
253
|
tool_name, tool_args, tool_map, tool_executor, ctx
|
|
@@ -181,22 +280,50 @@ class DynamicAgentRuntime(AgentRuntime):
|
|
|
181
280
|
await ctx.emit(EventType.RUN_FAILED, {"error": str(e)})
|
|
182
281
|
raise
|
|
183
282
|
|
|
184
|
-
async def
|
|
283
|
+
async def _get_system_context(self):
|
|
185
284
|
"""
|
|
186
|
-
Get the
|
|
285
|
+
Get the SystemContext if this agent is part of an AgentSystem.
|
|
187
286
|
|
|
188
|
-
Returns
|
|
287
|
+
Returns:
|
|
288
|
+
SystemContext with shared knowledge, or None if agent is not in a system
|
|
189
289
|
"""
|
|
190
|
-
from
|
|
290
|
+
from asgiref.sync import sync_to_async
|
|
291
|
+
from django_agent_runtime.models import AgentSystemMember
|
|
191
292
|
|
|
192
|
-
|
|
293
|
+
try:
|
|
294
|
+
# Check if this agent is a member of any system
|
|
295
|
+
membership = await sync_to_async(
|
|
296
|
+
lambda: AgentSystemMember.objects.filter(
|
|
297
|
+
agent=self._definition
|
|
298
|
+
).select_related('system').first()
|
|
299
|
+
)()
|
|
300
|
+
|
|
301
|
+
if membership and membership.system:
|
|
302
|
+
# Get the SystemContext from the system
|
|
303
|
+
system_context = await sync_to_async(
|
|
304
|
+
membership.system.get_system_context
|
|
305
|
+
)()
|
|
306
|
+
return system_context
|
|
307
|
+
|
|
308
|
+
except Exception as e:
|
|
309
|
+
logger.warning(f"Failed to get system context: {e}")
|
|
310
|
+
|
|
311
|
+
return None
|
|
312
|
+
|
|
313
|
+
async def _get_memory_store(self, ctx: RunContext) -> Optional["DjangoSharedMemoryStore"]:
|
|
314
|
+
"""
|
|
315
|
+
Get the shared memory store for this user, if available.
|
|
316
|
+
|
|
317
|
+
Returns None if we don't have the required context (authenticated user).
|
|
318
|
+
Privacy enforcement: Only authenticated users can have persistent memories.
|
|
319
|
+
"""
|
|
320
|
+
from django_agent_runtime.persistence.stores import DjangoSharedMemoryStore
|
|
321
|
+
|
|
322
|
+
# Need authenticated user for memory (privacy enforcement)
|
|
193
323
|
user_id = ctx.metadata.get("user_id")
|
|
194
|
-
conversation_id = ctx.conversation_id
|
|
195
324
|
|
|
196
|
-
if not user_id
|
|
197
|
-
logger.debug(
|
|
198
|
-
f"Memory not available: user_id={user_id}, conversation_id={conversation_id}"
|
|
199
|
-
)
|
|
325
|
+
if not user_id:
|
|
326
|
+
logger.debug("Memory not available: no authenticated user")
|
|
200
327
|
return None
|
|
201
328
|
|
|
202
329
|
try:
|
|
@@ -207,54 +334,237 @@ class DynamicAgentRuntime(AgentRuntime):
|
|
|
207
334
|
User = get_user_model()
|
|
208
335
|
user = await sync_to_async(User.objects.get)(id=user_id)
|
|
209
336
|
|
|
210
|
-
return
|
|
337
|
+
return DjangoSharedMemoryStore(user=user)
|
|
211
338
|
except Exception as e:
|
|
212
339
|
logger.warning(f"Failed to create memory store: {e}")
|
|
213
340
|
return None
|
|
214
341
|
|
|
215
|
-
async def _recall_memories(
|
|
342
|
+
async def _recall_memories(
|
|
343
|
+
self,
|
|
344
|
+
memory_store: "DjangoSharedMemoryStore",
|
|
345
|
+
ctx: RunContext,
|
|
346
|
+
) -> str:
|
|
216
347
|
"""
|
|
217
|
-
Recall all memories for this
|
|
348
|
+
Recall all memories for this user and format for the prompt.
|
|
349
|
+
Includes user-scoped and system-scoped memories.
|
|
218
350
|
"""
|
|
219
351
|
try:
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
352
|
+
# Get user-scoped memories
|
|
353
|
+
user_memories = await memory_store.list(scope="user", limit=50)
|
|
354
|
+
|
|
355
|
+
# Get system-scoped memories (shared across agents)
|
|
356
|
+
system_memories = await memory_store.list(scope="system", limit=50)
|
|
357
|
+
|
|
358
|
+
# Get conversation-scoped memories
|
|
359
|
+
conversation_memories = []
|
|
360
|
+
if ctx.conversation_id:
|
|
361
|
+
conversation_memories = await memory_store.list(
|
|
362
|
+
scope="conversation",
|
|
363
|
+
conversation_id=ctx.conversation_id,
|
|
364
|
+
limit=50,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
all_memories = user_memories + system_memories + conversation_memories
|
|
368
|
+
|
|
369
|
+
if all_memories:
|
|
370
|
+
logger.info(f"Recalled {len(all_memories)} memories for user")
|
|
371
|
+
return self._format_memories_for_prompt(all_memories)
|
|
224
372
|
except Exception as e:
|
|
225
373
|
logger.warning(f"Failed to recall memories: {e}")
|
|
226
374
|
return ""
|
|
227
375
|
|
|
376
|
+
def _format_memories_for_prompt(self, memories: list) -> str:
|
|
377
|
+
"""Format memories for inclusion in a system prompt."""
|
|
378
|
+
if not memories:
|
|
379
|
+
return ""
|
|
380
|
+
|
|
381
|
+
lines = ["# Remembered Information", ""]
|
|
382
|
+
|
|
383
|
+
# Group by scope
|
|
384
|
+
by_scope = {"user": [], "system": [], "conversation": []}
|
|
385
|
+
for mem in memories:
|
|
386
|
+
scope = mem.scope if hasattr(mem, 'scope') else "user"
|
|
387
|
+
if scope in by_scope:
|
|
388
|
+
by_scope[scope].append(mem)
|
|
389
|
+
|
|
390
|
+
if by_scope["user"]:
|
|
391
|
+
lines.append("## About This User")
|
|
392
|
+
for mem in by_scope["user"]:
|
|
393
|
+
display_key = mem.key.replace(".", " > ").replace("_", " ").title()
|
|
394
|
+
lines.append(f"- **{display_key}**: {mem.value}")
|
|
395
|
+
lines.append("")
|
|
396
|
+
|
|
397
|
+
if by_scope["system"]:
|
|
398
|
+
lines.append("## Shared Knowledge")
|
|
399
|
+
for mem in by_scope["system"]:
|
|
400
|
+
display_key = mem.key.replace(".", " > ").replace("_", " ").title()
|
|
401
|
+
lines.append(f"- **{display_key}**: {mem.value}")
|
|
402
|
+
lines.append("")
|
|
403
|
+
|
|
404
|
+
if by_scope["conversation"]:
|
|
405
|
+
lines.append("## This Conversation")
|
|
406
|
+
for mem in by_scope["conversation"]:
|
|
407
|
+
display_key = mem.key.replace(".", " > ").replace("_", " ").title()
|
|
408
|
+
lines.append(f"- **{display_key}**: {mem.value}")
|
|
409
|
+
lines.append("")
|
|
410
|
+
|
|
411
|
+
return "\n".join(lines)
|
|
412
|
+
|
|
228
413
|
async def _execute_remember_tool(
|
|
229
414
|
self,
|
|
230
415
|
args: dict,
|
|
231
|
-
memory_store: Optional["
|
|
416
|
+
memory_store: Optional["DjangoSharedMemoryStore"],
|
|
417
|
+
ctx: RunContext,
|
|
232
418
|
) -> str:
|
|
233
419
|
"""Execute the remember tool to store a memory."""
|
|
234
420
|
if not memory_store:
|
|
235
421
|
return json.dumps({
|
|
236
|
-
"error": "Memory not available
|
|
237
|
-
"hint": "Memory requires a logged-in user
|
|
422
|
+
"error": "Memory not available",
|
|
423
|
+
"hint": "Memory requires a logged-in user",
|
|
238
424
|
})
|
|
239
425
|
|
|
240
426
|
key = args.get("key", "").strip()
|
|
241
427
|
value = args.get("value", "").strip()
|
|
428
|
+
scope = args.get("scope", "user").strip()
|
|
242
429
|
|
|
243
430
|
if not key:
|
|
244
431
|
return json.dumps({"error": "Missing required parameter: key"})
|
|
245
432
|
if not value:
|
|
246
433
|
return json.dumps({"error": "Missing required parameter: value"})
|
|
434
|
+
if scope not in ("conversation", "user", "system"):
|
|
435
|
+
return json.dumps({"error": f"Invalid scope: {scope}"})
|
|
247
436
|
|
|
248
437
|
try:
|
|
249
|
-
|
|
250
|
-
|
|
438
|
+
# For conversation scope, need conversation_id
|
|
439
|
+
conversation_id = ctx.conversation_id if scope == "conversation" else None
|
|
440
|
+
|
|
441
|
+
await memory_store.set(
|
|
442
|
+
key=key,
|
|
443
|
+
value=value,
|
|
444
|
+
scope=scope,
|
|
445
|
+
source=f"agent:{self.key}",
|
|
446
|
+
conversation_id=conversation_id,
|
|
447
|
+
)
|
|
448
|
+
logger.info(f"Stored memory: {key} (scope={scope})")
|
|
251
449
|
return json.dumps({
|
|
252
450
|
"success": True,
|
|
253
|
-
"message": f"Remembered: {key}",
|
|
451
|
+
"message": f"Remembered: {key} (scope: {scope})",
|
|
254
452
|
})
|
|
255
453
|
except Exception as e:
|
|
256
454
|
logger.exception(f"Failed to store memory: {key}")
|
|
257
455
|
return json.dumps({"error": str(e)})
|
|
456
|
+
|
|
457
|
+
async def _execute_recall_tool(
|
|
458
|
+
self,
|
|
459
|
+
args: dict,
|
|
460
|
+
memory_store: Optional["DjangoSharedMemoryStore"],
|
|
461
|
+
ctx: RunContext,
|
|
462
|
+
) -> str:
|
|
463
|
+
"""Execute the recall tool to retrieve memories."""
|
|
464
|
+
if not memory_store:
|
|
465
|
+
return json.dumps({
|
|
466
|
+
"error": "Memory not available",
|
|
467
|
+
"hint": "Memory requires a logged-in user",
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
key = args.get("key", "").strip()
|
|
471
|
+
scope = args.get("scope", "all").strip()
|
|
472
|
+
|
|
473
|
+
if not key:
|
|
474
|
+
return json.dumps({"error": "Missing required parameter: key"})
|
|
475
|
+
|
|
476
|
+
try:
|
|
477
|
+
results = []
|
|
478
|
+
|
|
479
|
+
# Determine which scopes to search
|
|
480
|
+
scopes_to_search = []
|
|
481
|
+
if scope == "all":
|
|
482
|
+
scopes_to_search = ["user", "system", "conversation"]
|
|
483
|
+
else:
|
|
484
|
+
scopes_to_search = [scope]
|
|
485
|
+
|
|
486
|
+
for s in scopes_to_search:
|
|
487
|
+
conversation_id = ctx.conversation_id if s == "conversation" else None
|
|
488
|
+
|
|
489
|
+
# Try exact match first
|
|
490
|
+
item = await memory_store.get(key, scope=s, conversation_id=conversation_id)
|
|
491
|
+
if item:
|
|
492
|
+
results.append({
|
|
493
|
+
"key": item.key,
|
|
494
|
+
"value": item.value,
|
|
495
|
+
"scope": item.scope,
|
|
496
|
+
"source": item.source,
|
|
497
|
+
})
|
|
498
|
+
else:
|
|
499
|
+
# Try prefix match
|
|
500
|
+
items = await memory_store.list(
|
|
501
|
+
prefix=key,
|
|
502
|
+
scope=s,
|
|
503
|
+
conversation_id=conversation_id,
|
|
504
|
+
limit=20,
|
|
505
|
+
)
|
|
506
|
+
for item in items:
|
|
507
|
+
results.append({
|
|
508
|
+
"key": item.key,
|
|
509
|
+
"value": item.value,
|
|
510
|
+
"scope": item.scope,
|
|
511
|
+
"source": item.source,
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
if results:
|
|
515
|
+
return json.dumps({"memories": results})
|
|
516
|
+
else:
|
|
517
|
+
return json.dumps({"memories": [], "message": f"No memories found for key: {key}"})
|
|
518
|
+
|
|
519
|
+
except Exception as e:
|
|
520
|
+
logger.exception(f"Failed to recall memory: {key}")
|
|
521
|
+
return json.dumps({"error": str(e)})
|
|
522
|
+
|
|
523
|
+
async def _execute_forget_tool(
|
|
524
|
+
self,
|
|
525
|
+
args: dict,
|
|
526
|
+
memory_store: Optional["DjangoSharedMemoryStore"],
|
|
527
|
+
ctx: RunContext,
|
|
528
|
+
) -> str:
|
|
529
|
+
"""Execute the forget tool to delete a memory."""
|
|
530
|
+
if not memory_store:
|
|
531
|
+
return json.dumps({
|
|
532
|
+
"error": "Memory not available",
|
|
533
|
+
"hint": "Memory requires a logged-in user",
|
|
534
|
+
})
|
|
535
|
+
|
|
536
|
+
key = args.get("key", "").strip()
|
|
537
|
+
scope = args.get("scope", "user").strip()
|
|
538
|
+
|
|
539
|
+
if not key:
|
|
540
|
+
return json.dumps({"error": "Missing required parameter: key"})
|
|
541
|
+
if scope not in ("conversation", "user", "system"):
|
|
542
|
+
return json.dumps({"error": f"Invalid scope: {scope}"})
|
|
543
|
+
|
|
544
|
+
try:
|
|
545
|
+
conversation_id = ctx.conversation_id if scope == "conversation" else None
|
|
546
|
+
|
|
547
|
+
deleted = await memory_store.delete(
|
|
548
|
+
key=key,
|
|
549
|
+
scope=scope,
|
|
550
|
+
conversation_id=conversation_id,
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
if deleted:
|
|
554
|
+
logger.info(f"Deleted memory: {key} (scope={scope})")
|
|
555
|
+
return json.dumps({
|
|
556
|
+
"success": True,
|
|
557
|
+
"message": f"Forgot: {key}",
|
|
558
|
+
})
|
|
559
|
+
else:
|
|
560
|
+
return json.dumps({
|
|
561
|
+
"success": False,
|
|
562
|
+
"message": f"No memory found with key: {key}",
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
except Exception as e:
|
|
566
|
+
logger.exception(f"Failed to forget memory: {key}")
|
|
567
|
+
return json.dumps({"error": str(e)})
|
|
258
568
|
|
|
259
569
|
def _build_knowledge_context(self, config: dict) -> str:
|
|
260
570
|
"""Build context string from always-included knowledge sources."""
|
django_agent_studio/api/urls.py
CHANGED
|
@@ -293,4 +293,11 @@ urlpatterns = [
|
|
|
293
293
|
views.SpecDocumentRenderView.as_view(),
|
|
294
294
|
name="spec_document_render",
|
|
295
295
|
),
|
|
296
|
+
|
|
297
|
+
# Agent-specific spec document (get/update the spec linked to an agent)
|
|
298
|
+
path(
|
|
299
|
+
"agents/<uuid:agent_id>/spec/",
|
|
300
|
+
views.AgentSpecDocumentView.as_view(),
|
|
301
|
+
name="agent_spec_document",
|
|
302
|
+
),
|
|
296
303
|
]
|