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.
- django_agentic-0.3.0/LICENSE +21 -0
- django_agentic-0.3.0/PKG-INFO +441 -0
- django_agentic-0.3.0/README.md +397 -0
- django_agentic-0.3.0/django_agentic/__init__.py +44 -0
- django_agentic-0.3.0/django_agentic/admin.py +163 -0
- django_agentic-0.3.0/django_agentic/agent.py +101 -0
- django_agentic-0.3.0/django_agentic/apps.py +13 -0
- django_agentic-0.3.0/django_agentic/context.py +5 -0
- django_agentic-0.3.0/django_agentic/credits.py +89 -0
- django_agentic-0.3.0/django_agentic/management/__init__.py +1 -0
- django_agentic-0.3.0/django_agentic/management/commands/__init__.py +1 -0
- django_agentic-0.3.0/django_agentic/management/commands/reset_free_credits.py +16 -0
- django_agentic-0.3.0/django_agentic/migrations/0001_initial.py +116 -0
- django_agentic-0.3.0/django_agentic/migrations/0002_add_model_type.py +18 -0
- django_agentic-0.3.0/django_agentic/migrations/0003_remove_db_table_overrides.py +44 -0
- django_agentic-0.3.0/django_agentic/migrations/0004_seed_default_models.py +57 -0
- django_agentic-0.3.0/django_agentic/migrations/__init__.py +1 -0
- django_agentic-0.3.0/django_agentic/models.py +204 -0
- django_agentic-0.3.0/django_agentic/py.typed +0 -0
- django_agentic-0.3.0/django_agentic/serializers.py +28 -0
- django_agentic-0.3.0/django_agentic/service.py +677 -0
- django_agentic-0.3.0/django_agentic/tests/__init__.py +1 -0
- django_agentic-0.3.0/django_agentic/tests/test_credits.py +124 -0
- django_agentic-0.3.0/django_agentic/tests/test_models.py +133 -0
- django_agentic-0.3.0/django_agentic/tests/test_service.py +149 -0
- django_agentic-0.3.0/django_agentic/tests/test_views.py +149 -0
- django_agentic-0.3.0/django_agentic/tests/urls.py +6 -0
- django_agentic-0.3.0/django_agentic/urls.py +13 -0
- django_agentic-0.3.0/django_agentic/views.py +237 -0
- django_agentic-0.3.0/django_agentic.egg-info/PKG-INFO +441 -0
- django_agentic-0.3.0/django_agentic.egg-info/SOURCES.txt +34 -0
- django_agentic-0.3.0/django_agentic.egg-info/dependency_links.txt +1 -0
- django_agentic-0.3.0/django_agentic.egg-info/requires.txt +21 -0
- django_agentic-0.3.0/django_agentic.egg-info/top_level.txt +1 -0
- django_agentic-0.3.0/pyproject.toml +53 -0
- 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
|