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.
@@ -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