django-agentic 0.3.0__tar.gz

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.
Files changed (36) hide show
  1. django_agentic-0.3.0/LICENSE +21 -0
  2. django_agentic-0.3.0/PKG-INFO +441 -0
  3. django_agentic-0.3.0/README.md +397 -0
  4. django_agentic-0.3.0/django_agentic/__init__.py +44 -0
  5. django_agentic-0.3.0/django_agentic/admin.py +163 -0
  6. django_agentic-0.3.0/django_agentic/agent.py +101 -0
  7. django_agentic-0.3.0/django_agentic/apps.py +13 -0
  8. django_agentic-0.3.0/django_agentic/context.py +5 -0
  9. django_agentic-0.3.0/django_agentic/credits.py +89 -0
  10. django_agentic-0.3.0/django_agentic/management/__init__.py +1 -0
  11. django_agentic-0.3.0/django_agentic/management/commands/__init__.py +1 -0
  12. django_agentic-0.3.0/django_agentic/management/commands/reset_free_credits.py +16 -0
  13. django_agentic-0.3.0/django_agentic/migrations/0001_initial.py +116 -0
  14. django_agentic-0.3.0/django_agentic/migrations/0002_add_model_type.py +18 -0
  15. django_agentic-0.3.0/django_agentic/migrations/0003_remove_db_table_overrides.py +44 -0
  16. django_agentic-0.3.0/django_agentic/migrations/0004_seed_default_models.py +57 -0
  17. django_agentic-0.3.0/django_agentic/migrations/__init__.py +1 -0
  18. django_agentic-0.3.0/django_agentic/models.py +204 -0
  19. django_agentic-0.3.0/django_agentic/py.typed +0 -0
  20. django_agentic-0.3.0/django_agentic/serializers.py +28 -0
  21. django_agentic-0.3.0/django_agentic/service.py +677 -0
  22. django_agentic-0.3.0/django_agentic/tests/__init__.py +1 -0
  23. django_agentic-0.3.0/django_agentic/tests/test_credits.py +124 -0
  24. django_agentic-0.3.0/django_agentic/tests/test_models.py +133 -0
  25. django_agentic-0.3.0/django_agentic/tests/test_service.py +149 -0
  26. django_agentic-0.3.0/django_agentic/tests/test_views.py +149 -0
  27. django_agentic-0.3.0/django_agentic/tests/urls.py +6 -0
  28. django_agentic-0.3.0/django_agentic/urls.py +13 -0
  29. django_agentic-0.3.0/django_agentic/views.py +237 -0
  30. django_agentic-0.3.0/django_agentic.egg-info/PKG-INFO +441 -0
  31. django_agentic-0.3.0/django_agentic.egg-info/SOURCES.txt +34 -0
  32. django_agentic-0.3.0/django_agentic.egg-info/dependency_links.txt +1 -0
  33. django_agentic-0.3.0/django_agentic.egg-info/requires.txt +21 -0
  34. django_agentic-0.3.0/django_agentic.egg-info/top_level.txt +1 -0
  35. django_agentic-0.3.0/pyproject.toml +53 -0
  36. django_agentic-0.3.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Opportunote
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,441 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-agentic
3
+ Version: 0.3.0
4
+ Summary: AI framework for Django — LangGraph + LangChain. Entity-aware agents, credit management, usage logging, prompt caching, HITL.
5
+ Author-email: Opportunote <hello@opportunote.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Opportunote/django-agentic
8
+ Project-URL: Documentation, https://github.com/Opportunote/django-agentic#readme
9
+ Project-URL: Repository, https://github.com/Opportunote/django-agentic
10
+ Project-URL: Changelog, https://github.com/Opportunote/django-agentic/releases
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Web Environment
13
+ Classifier: Framework :: Django
14
+ Classifier: Framework :: Django :: 4.2
15
+ Classifier: Framework :: Django :: 5.0
16
+ Classifier: Framework :: Django :: 5.1
17
+ Classifier: Framework :: Django :: 6.0
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Requires-Python: >=3.11
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: django>=4.2
28
+ Requires-Dist: djangorestframework>=3.14
29
+ Requires-Dist: langchain>=0.3
30
+ Requires-Dist: langchain-core>=0.3
31
+ Requires-Dist: langgraph>=1.0
32
+ Requires-Dist: pydantic>=2.0
33
+ Provides-Extra: anthropic
34
+ Requires-Dist: langchain-anthropic>=0.3; extra == "anthropic"
35
+ Provides-Extra: openai
36
+ Requires-Dist: langchain-openai>=0.3; extra == "openai"
37
+ Provides-Extra: postgres
38
+ Requires-Dist: langgraph-checkpoint-postgres>=2.0; extra == "postgres"
39
+ Provides-Extra: redis
40
+ Requires-Dist: langgraph-checkpoint-redis>=1.0; extra == "redis"
41
+ Provides-Extra: sqlite
42
+ Requires-Dist: langgraph-checkpoint-sqlite>=1.0; extra == "sqlite"
43
+ Dynamic: license-file
44
+
45
+ # django-agentic
46
+
47
+ AI framework for Django: entity-aware chatbot agents with tools, human-in-the-loop approval,
48
+ credit management, and usage logging. Built on LangGraph + LangChain.
49
+
50
+ ```bash
51
+ pip install django-agentic[anthropic]
52
+ ```
53
+
54
+ ---
55
+
56
+ ## What you get out of the box
57
+
58
+ - **Agent chatbot** — a chat API that knows which Django model the user is viewing, can call
59
+ tools to read or write data, and asks for confirmation before making changes
60
+ - **REST API** — namespaced endpoints under `agentic/` (credits, agent chat, history)
61
+ - **Human-in-the-Loop (HITL)** — write tools pause for user approval via
62
+ `HumanInTheLoopMiddleware`; read tools execute immediately
63
+ - **Prompt caching** — automatic via `AnthropicPromptCachingMiddleware` (up to 90% cost
64
+ reduction on repeated requests)
65
+ - **Multi-provider** — OpenAI and Anthropic, selected per-user based on credit tier
66
+ - **Credit system** — free monthly credits + purchased credits per user, with automatic
67
+ model tier selection (paid model when credits allow, free model as fallback)
68
+ - **Usage logging** — every LLM call logged with token counts, cost, cache metrics, and
69
+ credit deductions
70
+ - **Structured invocation** — `ai_service.invoke()` for workflow nodes that need structured
71
+ Pydantic output (extraction, classification, generation)
72
+ - **Configurable checkpointer** — InMemorySaver by default, swap to PostgresSaver or
73
+ RedisSaver for production persistence
74
+ - **Django admin** — all models registered with Chart.js usage dashboards
75
+
76
+ ---
77
+
78
+ ## How it works
79
+
80
+ ```
81
+ Browser -> POST /api/agentic/agent/chat
82
+ |
83
+ agent_chat view (DRF)
84
+ |
85
+ AIService.chat() <-> resolve_model_for_user (credit check + model selection)
86
+ |
87
+ AgentRegistry -> YourCustomAgent (extends ModelAgent)
88
+ |
89
+ create_agent() <-> HumanInTheLoopMiddleware + AnthropicPromptCachingMiddleware
90
+ |
91
+ LLM <-> OpenAI / Anthropic
92
+ |
93
+ [write tool called]
94
+ |
95
+ HumanInTheLoopMiddleware -> interrupt -> returns actions to browser
96
+ |
97
+ User approves/rejects in UI
98
+ |
99
+ POST /api/agentic/agent/resume -> Command(resume=decisions) -> tool executes -> LLM responds
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Quick start
105
+
106
+ ### 1. Install
107
+
108
+ ```bash
109
+ pip install django-agentic[anthropic] # or django-agentic[openai]
110
+ ```
111
+
112
+ ### 2. Configure
113
+
114
+ ```python
115
+ # settings.py
116
+ INSTALLED_APPS = [
117
+ ...
118
+ "rest_framework",
119
+ "django.contrib.contenttypes",
120
+ "django_agentic",
121
+ ]
122
+
123
+ DJANGO_AGENTIC = {
124
+ "DEFAULT_MODEL": "claude-sonnet-4-20250514",
125
+ "ANTHROPIC_API_KEY": "sk-ant-...",
126
+ }
127
+ ```
128
+
129
+ ### 3. Add URLs
130
+
131
+ ```python
132
+ # urls.py
133
+ urlpatterns = [
134
+ path("api/", include("django_agentic.urls")),
135
+ ]
136
+ ```
137
+
138
+ ### 4. Migrate
139
+
140
+ ```bash
141
+ python manage.py migrate
142
+ ```
143
+
144
+ This creates the tables and seeds 9 default AI models (GPT-4.1 family + Claude family)
145
+ with current pricing. Go to Django admin > AI Configuration to pick your free-tier and
146
+ paid-tier defaults.
147
+
148
+ ---
149
+
150
+ ## Creating an agent
151
+
152
+ Subclass `ModelAgent` and implement three methods. django_agentic handles everything else:
153
+ provider selection, prompt caching, chat history, usage logging, credits, and HITL.
154
+
155
+ ```python
156
+ # myapp/agents.py
157
+ from django_agentic.agent import ModelAgent
158
+
159
+ class ProductAgent(ModelAgent):
160
+
161
+ def get_static_instructions(self) -> str:
162
+ """Cacheable system prompt. Cached by Anthropic for 5 min."""
163
+ return (
164
+ "You are a product assistant. Use tools to read and update data. "
165
+ "Write tools require user approval -- call them immediately, "
166
+ "the system handles confirmation."
167
+ )
168
+
169
+ def get_dynamic_context(self) -> str:
170
+ """Ephemeral context -- entity state, rebuilt per request."""
171
+ p = self.entity
172
+ return f"Product: {p.name} (ID: {p.pk})\nPrice: {p.price}\nStock: {p.stock}"
173
+
174
+ def get_tools(self) -> list:
175
+ """Tools the LLM can call. Passed to create_agent which binds them
176
+ to the LLM automatically -- no need to describe them in the prompt."""
177
+ return [get_product_details, update_price, delete_product]
178
+
179
+ def get_tools_requiring_approval(self) -> list[str]:
180
+ """Tool names that trigger HITL interrupt before execution."""
181
+ return ["update_price", "delete_product"]
182
+
183
+ def summarise_action(self, tool_name: str, tool_input: dict) -> str:
184
+ """Human-readable summary for the HITL confirmation card."""
185
+ if tool_name == "update_price":
186
+ return f"Update price to ${tool_input.get('new_price', '?')}"
187
+ return f"Execute: {tool_name}"
188
+ ```
189
+
190
+ Register the mapping in settings:
191
+
192
+ ```python
193
+ DJANGO_AGENTIC = {
194
+ "AGENT_MAPPINGS": {
195
+ "catalog.Product": "catalog.agents.ProductAgent",
196
+ },
197
+ }
198
+ ```
199
+
200
+ ---
201
+
202
+ ## Creating tools
203
+
204
+ Tools are plain Python functions decorated with `@tool` from `langchain_core.tools`.
205
+ The consuming app imports only `@tool` -- no other langchain imports needed.
206
+
207
+ **Read tool** (runs immediately, no approval):
208
+
209
+ ```python
210
+ from langchain_core.tools import tool
211
+ import json
212
+
213
+ @tool
214
+ def get_product_details(product_id: str) -> str:
215
+ """Get full details of a product."""
216
+ from myapp.models import Product
217
+ p = Product.objects.get(pk=product_id)
218
+ return json.dumps({"name": p.name, "price": str(p.price), "stock": p.stock})
219
+ ```
220
+
221
+ **Write tool** (requires HITL approval):
222
+
223
+ ```python
224
+ @tool
225
+ def update_price(product_id: str, new_price: float) -> str:
226
+ """Update the price of a product."""
227
+ from myapp.models import Product
228
+ p = Product.objects.get(pk=product_id)
229
+ old = p.price
230
+ p.price = new_price
231
+ p.save(update_fields=["price"])
232
+ return json.dumps({"success": True, "old_price": str(old), "new_price": str(new_price)})
233
+ ```
234
+
235
+ Tools listed in `get_tools_requiring_approval()` are gated by `HumanInTheLoopMiddleware`.
236
+ Read tools not in the approval list execute immediately.
237
+
238
+ ---
239
+
240
+ ## Human-in-the-Loop (HITL)
241
+
242
+ HITL is driven by LangChain's `HumanInTheLoopMiddleware` -- no custom interrupt code needed.
243
+
244
+ When the LLM calls a write tool, the middleware pauses execution and returns the pending
245
+ actions to the frontend. The frontend shows a confirmation card. The user approves or
246
+ rejects. A POST to `/ai/agent/resume` continues the workflow using
247
+ `Command(resume={"decisions": [{"type": "approve"}]})`.
248
+
249
+ If the user refreshes the page during an interrupt, the next chat message auto-rejects
250
+ the stale interrupt before processing the new message.
251
+
252
+ ---
253
+
254
+ ## Structured invocation (workflows)
255
+
256
+ For LangGraph workflow nodes that need structured Pydantic output:
257
+
258
+ ```python
259
+ from django_agentic.service import ai_service
260
+ from pydantic import BaseModel, Field
261
+
262
+ class ProductAnalysis(BaseModel):
263
+ category: str = Field(description="Product category")
264
+ sentiment: float = Field(description="Sentiment score 0-1")
265
+
266
+ result = ai_service.invoke(
267
+ schema=ProductAnalysis,
268
+ system_prompt="Analyze this product review.",
269
+ human_content="Great product, fast shipping!",
270
+ workflow="reviews",
271
+ node="analyze",
272
+ )
273
+ # result is a ProductAnalysis instance
274
+ ```
275
+
276
+ Every `invoke()` call is automatically logged, costed, and credit-deducted.
277
+
278
+ ---
279
+
280
+ ## Running workflows (ai_context)
281
+
282
+ When running a LangGraph workflow that calls `ai_service.invoke()` internally, use
283
+ `ai_context` to handle credit pre-check, model selection, and context var setup:
284
+
285
+ ```python
286
+ from django_agentic.service import ai_context
287
+
288
+ with ai_context(user) as ctx:
289
+ # ctx.model_name, ctx.model, ctx.is_free_tier available
290
+ # context vars (current_ai_user, current_ai_model_name) are set
291
+ my_workflow.invoke({"input": data})
292
+ # context vars auto-reset on exit, even if an exception occurs
293
+ ```
294
+
295
+ This replaces the manual pattern of calling `resolve_model_for_user()`, setting context
296
+ vars, and resetting them in a `finally` block.
297
+
298
+ ---
299
+
300
+ ## API endpoints
301
+
302
+ All endpoints are namespaced under `agentic/`. When you include the URLs with
303
+ `path("api/", include("django_agentic.urls"))`, the full paths become `/api/agentic/...`.
304
+
305
+ | Method | Path | Description |
306
+ |--------|------|-------------|
307
+ | GET | `agentic/credits/` | Credit status, current model, available models |
308
+ | PATCH | `agentic/credits/model-override/` | Set per-user model override |
309
+ | GET | `agentic/credits/usage/` | Usage statistics (daily, by model, by workflow) |
310
+ | POST | `agentic/agent/chat` | Send a chat message |
311
+ | POST | `agentic/agent/resume` | Approve/reject HITL interrupt |
312
+ | GET | `agentic/agent/history` | Load conversation history for an entity |
313
+
314
+ ### Request/Response formats
315
+
316
+ **Chat:**
317
+
318
+ ```json
319
+ POST /api/agentic/agent/chat
320
+ {
321
+ "message": "What is the current price?",
322
+ "context": {"entity_class": "catalog.Product", "entity_id": "123"}
323
+ }
324
+
325
+ // Normal response
326
+ {"success": true, "message": "The price is $29.99.", "usage": {"input_tokens": 150, "output_tokens": 25}}
327
+
328
+ // HITL interrupt response
329
+ {"success": true, "message": "Confirm 1 action", "usage": {...},
330
+ "interrupt": {"message": "Confirm 1 action", "actions": [{"name": "update_price", "args": {"new_price": 39.99}, "description": "Update price to $39.99"}]}}
331
+ ```
332
+
333
+ **Resume:**
334
+
335
+ ```json
336
+ POST /api/agentic/agent/resume
337
+ {"approved": true, "context": {"entity_class": "catalog.Product", "entity_id": "123"}}
338
+ ```
339
+
340
+ **History:**
341
+
342
+ ```
343
+ GET /api/agentic/agent/history?entity_class=catalog.Product&entity_id=123
344
+
345
+ {"history": [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}]}
346
+ ```
347
+
348
+ ---
349
+
350
+ ## Checkpointer (chat history storage)
351
+
352
+ Default: `InMemorySaver` (works out of the box, loses history on server restart).
353
+
354
+ ### PostgreSQL (production)
355
+
356
+ ```bash
357
+ pip install django-agentic[postgres]
358
+ ```
359
+
360
+ ```python
361
+ from langgraph.checkpoint.postgres import PostgresSaver
362
+ DJANGO_AGENTIC = {
363
+ "CHECKPOINTER": PostgresSaver.from_conn_string("postgresql://user:pass@host/db"),
364
+ }
365
+ # Call DJANGO_AGENTIC["CHECKPOINTER"].setup() once to create checkpoint tables.
366
+ ```
367
+
368
+ ### Redis
369
+
370
+ ```bash
371
+ pip install django-agentic[redis]
372
+ ```
373
+
374
+ ```python
375
+ from langgraph.checkpoint.redis import RedisSaver
376
+ DJANGO_AGENTIC = {"CHECKPOINTER": RedisSaver(redis_url="redis://localhost:6379")}
377
+ ```
378
+
379
+ ---
380
+
381
+ ## Credit system
382
+
383
+ Each user gets a configurable free monthly credit allowance (default $2.00). When free
384
+ credits run out, the system falls back to the free-tier model. Users with purchased
385
+ credits get the paid-tier model.
386
+
387
+ - Admins see unlimited credits (staff bypass)
388
+ - Per-user model override in admin
389
+ - Atomic credit deduction with idempotency keys (no double-charging on retries)
390
+
391
+ ```bash
392
+ python manage.py reset_free_credits # run monthly via cron
393
+ ```
394
+
395
+ ---
396
+
397
+ ## Prompt caching (Anthropic)
398
+
399
+ When using an Anthropic model, `AnthropicPromptCachingMiddleware` automatically caches
400
+ the system prompt. On repeated requests within 5 minutes, cache hits cost ~90% less than
401
+ regular input tokens. No configuration needed -- the middleware is applied automatically.
402
+
403
+ ---
404
+
405
+ ## Configuration reference
406
+
407
+ All settings go in the `DJANGO_AGENTIC` dict in your Django settings:
408
+
409
+ | Key | Default | Description |
410
+ |-----|---------|-------------|
411
+ | `DEFAULT_MODEL` | `claude-sonnet-4-20250514` | Fallback LLM model name |
412
+ | `ANTHROPIC_API_KEY` | `""` | Anthropic API key |
413
+ | `OPENAI_API_KEY` | `""` | OpenAI API key |
414
+ | `MAX_RETRIES` | `8` | LLM retry count |
415
+ | `REQUESTS_PER_SECOND` | `0.8` | Rate limiter for LLM calls |
416
+ | `CHECKPOINTER` | `InMemorySaver()` | LangGraph checkpointer instance |
417
+ | `AGENT_MAPPINGS` | `{}` | Maps `"app.Model"` to `"app.agents.AgentClass"` |
418
+ | `ENTITY_MODELS` | `[]` | Model paths for entity resolution by ID |
419
+
420
+ ---
421
+
422
+ ## Requirements
423
+
424
+ - Python 3.11+
425
+ - Django 4.2+
426
+ - Django REST Framework 3.14+
427
+ - LangChain 0.3+
428
+ - LangGraph 1.0+
429
+
430
+ **Optional:**
431
+
432
+ - `langchain-anthropic` -- for Anthropic models + prompt caching middleware
433
+ - `langchain-openai` -- for OpenAI models
434
+ - `langgraph-checkpoint-postgres` -- PostgreSQL checkpointer
435
+ - `langgraph-checkpoint-redis` -- Redis checkpointer
436
+
437
+ ---
438
+
439
+ ## License
440
+
441
+ MIT