django-model-agent 2026.1.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_model_agent/README.md +267 -0
- django_model_agent/__init__.py +28 -0
- django_model_agent/base.py +753 -0
- django_model_agent/capabilities.py +862 -0
- django_model_agent/examples.py +462 -0
- django_model_agent/memory.py +263 -0
- django_model_agent/migrations/0001_initial.py +33 -0
- django_model_agent/migrations/__init__.py +0 -0
- django_model_agent/models.py +15 -0
- django_model_agent/tools.py +404 -0
- django_model_agent-2026.1.1.dist-info/METADATA +229 -0
- django_model_agent-2026.1.1.dist-info/RECORD +14 -0
- django_model_agent-2026.1.1.dist-info/WHEEL +4 -0
- django_model_agent-2026.1.1.dist-info/licenses/LICENSE.md +131 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# ModelAgent - Django Ninja-style Abstraction for Pydantic AI
|
|
2
|
+
|
|
3
|
+
> Experimental status: the schema/prompt/tool APIs are usable, but `ModelAgent.run()` still requires a project-specific `build_agent()` implementation.
|
|
4
|
+
|
|
5
|
+
This module provides a declarative way to bind Django models to Pydantic AI Agents,
|
|
6
|
+
inspired by Django Ninja's `ModelSchema` pattern.
|
|
7
|
+
|
|
8
|
+
## Concept
|
|
9
|
+
|
|
10
|
+
Just as Django Ninja's `ModelSchema` provides a declarative way to create Pydantic schemas
|
|
11
|
+
from Django models for API serialization, `ModelAgent` provides a declarative way to create
|
|
12
|
+
AI agents that understand and operate on Django model instances.
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Django Model = source of truth
|
|
16
|
+
ModelAgent = declarative wrapper
|
|
17
|
+
Pydantic AI = execution engine
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from django_model_agent import ModelAgent, ModelTool
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RestaurantAgent(ModelAgent):
|
|
27
|
+
model = Restaurant
|
|
28
|
+
|
|
29
|
+
fields = ["name", "address", "hours", "neighborhood"]
|
|
30
|
+
|
|
31
|
+
_system_prompts = """
|
|
32
|
+
You are an assistant that helps reason about restaurant information.
|
|
33
|
+
Use the provided model fields as your source of truth.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
_instructions_template = "agents/restaurant_instructions.jinja"
|
|
37
|
+
|
|
38
|
+
tools = [UpdateHoursTool, FlagForReviewTool]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Introspect what the agent exposes
|
|
42
|
+
restaurant = Restaurant.objects.get(pk=123)
|
|
43
|
+
agent = RestaurantAgent(restaurant)
|
|
44
|
+
schema = agent.schema
|
|
45
|
+
system_prompt = agent.get_system_prompts()
|
|
46
|
+
instructions = agent.get_instructions()
|
|
47
|
+
tools = agent.get_tools()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Core Components
|
|
51
|
+
|
|
52
|
+
### ModelAgent (`base.py`)
|
|
53
|
+
|
|
54
|
+
The base class that:
|
|
55
|
+
1. Binds to a Django model
|
|
56
|
+
2. Generates a Pydantic schema from model fields
|
|
57
|
+
3. Creates a Pydantic AI Agent
|
|
58
|
+
4. Handles loading instances and persisting changes
|
|
59
|
+
|
|
60
|
+
Init parameters:
|
|
61
|
+
- `instance`: The Django model instance to operate on
|
|
62
|
+
- `system_prompt`: Override or extend the class-level system prompts
|
|
63
|
+
- `instructions`: Override or extend the class-level instructions
|
|
64
|
+
- `field_set`: Name of a field set to use for schema generation
|
|
65
|
+
|
|
66
|
+
Key attributes:
|
|
67
|
+
- `model`: The Django model class
|
|
68
|
+
- `fields`: List of fields to expose (None = all)
|
|
69
|
+
- `exclude`: Fields to exclude from schema
|
|
70
|
+
- `_system_prompts`: Base prompt for the agent (combined with `@system_prompt` decorators)
|
|
71
|
+
- `_instructions_template`: Path to Django template for dynamic instructions
|
|
72
|
+
- `tools`: List of tool classes available to the agent
|
|
73
|
+
- `_field_sets`: Named groups of fields for role-based access
|
|
74
|
+
|
|
75
|
+
### ModelTool (`tools.py`)
|
|
76
|
+
|
|
77
|
+
Base class for creating tools that operate on model instances:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
class UpdateHoursTool(ModelTool):
|
|
81
|
+
name = "update_hours"
|
|
82
|
+
description = "Update restaurant hours"
|
|
83
|
+
|
|
84
|
+
def execute(self, *, hours: str) -> ToolResult:
|
|
85
|
+
self.instance.hours = hours
|
|
86
|
+
self.instance.save()
|
|
87
|
+
return ToolResult(success=True, message="Hours updated")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Specialized tool types:
|
|
91
|
+
- `ReadOnlyTool`: For read-only operations
|
|
92
|
+
- `UpdateTool`: For simple updates with change tracking
|
|
93
|
+
- `DiffAwareUpdateTool`: For proposing changes that require approval
|
|
94
|
+
|
|
95
|
+
### AgentMemory (`memory.py`)
|
|
96
|
+
|
|
97
|
+
Model-backed memory storage using Django's contenttypes framework:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
class AgentMemory(models.Model):
|
|
101
|
+
content_type = ForeignKey(ContentType)
|
|
102
|
+
object_id = PositiveIntegerField()
|
|
103
|
+
data = JSONField()
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Add memory to your agent with the mixin:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
class RestaurantAgent(AgentMemoryMixin, ModelAgent):
|
|
110
|
+
model = Restaurant
|
|
111
|
+
...
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
agent = RestaurantAgent(restaurant)
|
|
115
|
+
agent.memory.set("last_topic", "hours")
|
|
116
|
+
agent.save_memory()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Decorators (pydantic-ai style)
|
|
120
|
+
|
|
121
|
+
ModelAgent supports pydantic-ai style decorators for registering prompts, instructions, and tools:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
class RestaurantAgent(ModelAgent):
|
|
125
|
+
model = Restaurant
|
|
126
|
+
fields = ["name", "address", "hours"]
|
|
127
|
+
_system_prompts = "Base system prompt." # Optional class attribute
|
|
128
|
+
|
|
129
|
+
@ModelAgent.system_prompt
|
|
130
|
+
def dynamic_context(self) -> str:
|
|
131
|
+
"""Added to the system prompt at runtime."""
|
|
132
|
+
return f"Current restaurant: {self.instance.name}"
|
|
133
|
+
|
|
134
|
+
@ModelAgent.instructions
|
|
135
|
+
def state_instructions(self) -> str:
|
|
136
|
+
"""Dynamic instructions that change per-run."""
|
|
137
|
+
return f"Current state: {self.instance.state}"
|
|
138
|
+
|
|
139
|
+
@ModelAgent.tool
|
|
140
|
+
def get_hours(self) -> str:
|
|
141
|
+
"""Get the operating hours for this restaurant."""
|
|
142
|
+
return str(self.instance.hours)
|
|
143
|
+
|
|
144
|
+
@ModelAgent.tool
|
|
145
|
+
def update_name(self, new_name: str) -> str:
|
|
146
|
+
"""Update the restaurant name."""
|
|
147
|
+
self.instance.name = new_name
|
|
148
|
+
self.instance.save()
|
|
149
|
+
return f"Name updated to: {new_name}"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Decorator Reference
|
|
153
|
+
|
|
154
|
+
| Decorator | pydantic-ai equivalent | Purpose |
|
|
155
|
+
|-----------|----------------------|---------|
|
|
156
|
+
| `@ModelAgent.system_prompt` | `@agent.system_prompt` | Add to system prompt |
|
|
157
|
+
| `@ModelAgent.instructions` | `@agent.instructions` | Dynamic per-run instructions |
|
|
158
|
+
| `@ModelAgent.tool` | `@agent.tool` | Register a tool |
|
|
159
|
+
|
|
160
|
+
Multiple decorated methods of the same type are combined:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
class Agent(ModelAgent):
|
|
164
|
+
_system_prompts = "Base prompt."
|
|
165
|
+
|
|
166
|
+
@ModelAgent.system_prompt
|
|
167
|
+
def context_1(self) -> str:
|
|
168
|
+
return "Context part 1."
|
|
169
|
+
|
|
170
|
+
@ModelAgent.system_prompt
|
|
171
|
+
def context_2(self) -> str:
|
|
172
|
+
return "Context part 2."
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# Result: "Base prompt.\n\nContext part 1.\n\nContext part 2."
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Field Sets (Role-Based Access)
|
|
179
|
+
|
|
180
|
+
Expose different fields based on user role:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
class RestaurantAgent(ModelAgent):
|
|
184
|
+
model = Restaurant
|
|
185
|
+
|
|
186
|
+
_field_sets = {
|
|
187
|
+
"public": ["name", "hours", "address"],
|
|
188
|
+
"staff": ["name", "hours", "address", "internal_notes"],
|
|
189
|
+
"admin": None, # All fields
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# Use a specific field set
|
|
194
|
+
agent = RestaurantAgent(restaurant, field_set="staff")
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Multi-Agent Workflows
|
|
198
|
+
|
|
199
|
+
### Diff-Aware Updates
|
|
200
|
+
|
|
201
|
+
One agent proposes, another approves:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
# Proposer agent
|
|
205
|
+
class DataEntryAgent(ModelAgent):
|
|
206
|
+
tools = [ProposeChangeTool]
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# Reviewer agent
|
|
210
|
+
class ReviewerAgent(ModelAgent):
|
|
211
|
+
tools = [ApproveChangeTool, RejectChangeTool]
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Tool with FSM State Checking
|
|
215
|
+
|
|
216
|
+
Tools can restrict themselves to certain model states:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
class PublishTool(ModelTool):
|
|
220
|
+
name = "publish"
|
|
221
|
+
allowed_states = ["draft"] # Only works when state is "draft"
|
|
222
|
+
|
|
223
|
+
def execute(self, **kwargs):
|
|
224
|
+
self.instance.publish()
|
|
225
|
+
self.instance.save()
|
|
226
|
+
return ToolResult(success=True, message="Published")
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Running with pydantic-ai
|
|
230
|
+
|
|
231
|
+
`ModelAgent.run()` calls `build_agent()`. The base class intentionally raises `NotImplementedError` until you provide the integration:
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from pydantic_ai import Agent
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class RunnableRestaurantAgent(RestaurantAgent):
|
|
238
|
+
def build_agent(self) -> Agent:
|
|
239
|
+
return Agent(
|
|
240
|
+
model=self.schema,
|
|
241
|
+
system_prompt=self.get_system_prompts(),
|
|
242
|
+
instructions=self.get_instructions(),
|
|
243
|
+
tools=self.get_tools(),
|
|
244
|
+
)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Integration with Django Admin
|
|
248
|
+
|
|
249
|
+
You could wire ModelAgent into Django Admin actions:
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
@admin.action(description="Run agent task")
|
|
253
|
+
def run_agent_task(modeladmin, request, queryset):
|
|
254
|
+
for instance in queryset:
|
|
255
|
+
agent = RestaurantAgent(instance)
|
|
256
|
+
# ... run agent task
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## TODO / Future Directions
|
|
260
|
+
|
|
261
|
+
- [ ] Full integration with pydantic-ai when API stabilizes
|
|
262
|
+
- [ ] Streaming response support
|
|
263
|
+
- [ ] Tool result caching
|
|
264
|
+
- [ ] Batch operations across multiple instances
|
|
265
|
+
- [ ] Admin integration helpers
|
|
266
|
+
- [ ] Django management commands for agent tasks
|
|
267
|
+
- [ ] Webhook/callback support for async workflows
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .base import ModelAgent, ModelAgentContext
|
|
4
|
+
from .capabilities import (
|
|
5
|
+
AuditRecord,
|
|
6
|
+
DjangoAuditCapability,
|
|
7
|
+
DjangoFSMCapability,
|
|
8
|
+
DjangoMemoryCapability,
|
|
9
|
+
DjangoModelCapability,
|
|
10
|
+
model_tools_to_toolset,
|
|
11
|
+
)
|
|
12
|
+
from .tools import ModelTool
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"AuditRecord",
|
|
16
|
+
"DjangoAuditCapability",
|
|
17
|
+
"DjangoFSMCapability",
|
|
18
|
+
"DjangoMemoryCapability",
|
|
19
|
+
"DjangoModelCapability",
|
|
20
|
+
"ModelAgent",
|
|
21
|
+
"ModelAgentContext",
|
|
22
|
+
"ModelTool",
|
|
23
|
+
"model_tools_to_toolset",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
# AgentMemory is available but must be imported explicitly
|
|
27
|
+
# to avoid triggering Django model registration:
|
|
28
|
+
# from django_model_agent.memory import AgentMemory
|