django-agent-runtime 0.3.6__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.
Files changed (55) hide show
  1. django_agent_runtime/__init__.py +25 -0
  2. django_agent_runtime/admin.py +155 -0
  3. django_agent_runtime/api/__init__.py +26 -0
  4. django_agent_runtime/api/permissions.py +109 -0
  5. django_agent_runtime/api/serializers.py +114 -0
  6. django_agent_runtime/api/views.py +472 -0
  7. django_agent_runtime/apps.py +26 -0
  8. django_agent_runtime/conf.py +241 -0
  9. django_agent_runtime/examples/__init__.py +10 -0
  10. django_agent_runtime/examples/langgraph_adapter.py +164 -0
  11. django_agent_runtime/examples/langgraph_tools.py +179 -0
  12. django_agent_runtime/examples/simple_chat.py +69 -0
  13. django_agent_runtime/examples/tool_agent.py +157 -0
  14. django_agent_runtime/management/__init__.py +2 -0
  15. django_agent_runtime/management/commands/__init__.py +2 -0
  16. django_agent_runtime/management/commands/runagent.py +419 -0
  17. django_agent_runtime/migrations/0001_initial.py +117 -0
  18. django_agent_runtime/migrations/0002_persistence_models.py +129 -0
  19. django_agent_runtime/migrations/0003_persistenceconversation_active_branch_id_and_more.py +212 -0
  20. django_agent_runtime/migrations/0004_add_anonymous_session_id.py +18 -0
  21. django_agent_runtime/migrations/__init__.py +2 -0
  22. django_agent_runtime/models/__init__.py +54 -0
  23. django_agent_runtime/models/base.py +450 -0
  24. django_agent_runtime/models/concrete.py +146 -0
  25. django_agent_runtime/persistence/__init__.py +60 -0
  26. django_agent_runtime/persistence/helpers.py +148 -0
  27. django_agent_runtime/persistence/models.py +506 -0
  28. django_agent_runtime/persistence/stores.py +1191 -0
  29. django_agent_runtime/runtime/__init__.py +23 -0
  30. django_agent_runtime/runtime/events/__init__.py +65 -0
  31. django_agent_runtime/runtime/events/base.py +135 -0
  32. django_agent_runtime/runtime/events/db.py +129 -0
  33. django_agent_runtime/runtime/events/redis.py +228 -0
  34. django_agent_runtime/runtime/events/sync.py +140 -0
  35. django_agent_runtime/runtime/interfaces.py +475 -0
  36. django_agent_runtime/runtime/llm/__init__.py +91 -0
  37. django_agent_runtime/runtime/llm/anthropic.py +249 -0
  38. django_agent_runtime/runtime/llm/litellm_adapter.py +173 -0
  39. django_agent_runtime/runtime/llm/openai.py +230 -0
  40. django_agent_runtime/runtime/queue/__init__.py +75 -0
  41. django_agent_runtime/runtime/queue/base.py +158 -0
  42. django_agent_runtime/runtime/queue/postgres.py +248 -0
  43. django_agent_runtime/runtime/queue/redis_streams.py +336 -0
  44. django_agent_runtime/runtime/queue/sync.py +277 -0
  45. django_agent_runtime/runtime/registry.py +186 -0
  46. django_agent_runtime/runtime/runner.py +540 -0
  47. django_agent_runtime/runtime/tracing/__init__.py +48 -0
  48. django_agent_runtime/runtime/tracing/langfuse.py +117 -0
  49. django_agent_runtime/runtime/tracing/noop.py +36 -0
  50. django_agent_runtime/urls.py +39 -0
  51. django_agent_runtime-0.3.6.dist-info/METADATA +723 -0
  52. django_agent_runtime-0.3.6.dist-info/RECORD +55 -0
  53. django_agent_runtime-0.3.6.dist-info/WHEEL +5 -0
  54. django_agent_runtime-0.3.6.dist-info/licenses/LICENSE +22 -0
  55. django_agent_runtime-0.3.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,25 @@
1
+ """
2
+ Django Agent Runtime - Production-grade AI agent execution for Django.
3
+
4
+ Framework-agnostic • Model-agnostic • Production-grade concurrency
5
+
6
+ This package provides:
7
+ - AgentRun model for tracking agent executions
8
+ - Queue adapters (Postgres, Redis Streams) for job distribution
9
+ - Event bus for real-time streaming to UI
10
+ - Plugin system for custom agent runtimes
11
+ - LLM client abstraction (provider-agnostic)
12
+ - Persistence layer (memory, conversations, tasks, preferences)
13
+ - Optional integrations (LiteLLM, Langfuse)
14
+
15
+ Usage:
16
+ 1. Add 'django_agent_runtime' to INSTALLED_APPS
17
+ 2. Configure DJANGO_AGENT_RUNTIME settings
18
+ 3. Run migrations
19
+ 4. Start workers: ./manage.py runagent
20
+ """
21
+
22
+ __version__ = "0.3.0"
23
+
24
+ default_app_config = "django_agent_runtime.apps.DjangoAgentRuntimeConfig"
25
+
@@ -0,0 +1,155 @@
1
+ """
2
+ Django admin configuration for agent runtime models.
3
+ """
4
+
5
+ from django.contrib import admin
6
+ from django.utils.html import format_html
7
+
8
+ from django_agent_runtime.models import (
9
+ AgentConversation,
10
+ AgentRun,
11
+ AgentEvent,
12
+ AgentCheckpoint,
13
+ )
14
+
15
+
16
+ @admin.register(AgentConversation)
17
+ class AgentConversationAdmin(admin.ModelAdmin):
18
+ """Admin for AgentConversation."""
19
+
20
+ list_display = ["id", "agent_key", "user", "title", "created_at"]
21
+ list_filter = ["agent_key", "created_at"]
22
+ search_fields = ["id", "title", "user__email"]
23
+ readonly_fields = ["id", "created_at", "updated_at"]
24
+ raw_id_fields = ["user"]
25
+
26
+
27
+ class AgentEventInline(admin.TabularInline):
28
+ """Inline for viewing events on a run."""
29
+
30
+ model = AgentEvent
31
+ extra = 0
32
+ readonly_fields = ["seq", "event_type", "payload", "timestamp"]
33
+ can_delete = False
34
+
35
+ def has_add_permission(self, request, obj=None):
36
+ return False
37
+
38
+
39
+ class AgentCheckpointInline(admin.TabularInline):
40
+ """Inline for viewing checkpoints on a run."""
41
+
42
+ model = AgentCheckpoint
43
+ extra = 0
44
+ readonly_fields = ["seq", "state", "created_at"]
45
+ can_delete = False
46
+
47
+ def has_add_permission(self, request, obj=None):
48
+ return False
49
+
50
+
51
+ @admin.register(AgentRun)
52
+ class AgentRunAdmin(admin.ModelAdmin):
53
+ """Admin for AgentRun."""
54
+
55
+ list_display = [
56
+ "id",
57
+ "agent_key",
58
+ "status_badge",
59
+ "attempt",
60
+ "conversation",
61
+ "created_at",
62
+ "duration",
63
+ ]
64
+ list_filter = ["status", "agent_key", "created_at"]
65
+ search_fields = ["id", "agent_key", "idempotency_key"]
66
+ readonly_fields = [
67
+ "id",
68
+ "status",
69
+ "attempt",
70
+ "lease_owner",
71
+ "lease_expires_at",
72
+ "created_at",
73
+ "started_at",
74
+ "finished_at",
75
+ "cancel_requested_at",
76
+ ]
77
+ raw_id_fields = ["conversation"]
78
+ inlines = [AgentEventInline, AgentCheckpointInline]
79
+
80
+ fieldsets = (
81
+ (None, {
82
+ "fields": ("id", "agent_key", "conversation", "status")
83
+ }),
84
+ ("Input/Output", {
85
+ "fields": ("input", "output", "error"),
86
+ "classes": ("collapse",),
87
+ }),
88
+ ("Execution", {
89
+ "fields": (
90
+ "attempt",
91
+ "max_attempts",
92
+ "lease_owner",
93
+ "lease_expires_at",
94
+ "cancel_requested_at",
95
+ ),
96
+ }),
97
+ ("Timestamps", {
98
+ "fields": ("created_at", "started_at", "finished_at"),
99
+ }),
100
+ ("Metadata", {
101
+ "fields": ("idempotency_key", "metadata"),
102
+ "classes": ("collapse",),
103
+ }),
104
+ )
105
+
106
+ def status_badge(self, obj):
107
+ """Display status as a colored badge."""
108
+ colors = {
109
+ "queued": "#6c757d",
110
+ "running": "#007bff",
111
+ "succeeded": "#28a745",
112
+ "failed": "#dc3545",
113
+ "cancelled": "#ffc107",
114
+ "timed_out": "#fd7e14",
115
+ }
116
+ color = colors.get(obj.status, "#6c757d")
117
+ return format_html(
118
+ '<span style="background-color: {}; color: white; padding: 2px 8px; '
119
+ 'border-radius: 4px; font-size: 11px;">{}</span>',
120
+ color,
121
+ obj.status.upper(),
122
+ )
123
+ status_badge.short_description = "Status"
124
+
125
+ def duration(self, obj):
126
+ """Calculate run duration."""
127
+ if obj.started_at and obj.finished_at:
128
+ delta = obj.finished_at - obj.started_at
129
+ return f"{delta.total_seconds():.1f}s"
130
+ elif obj.started_at:
131
+ return "Running..."
132
+ return "-"
133
+ duration.short_description = "Duration"
134
+
135
+
136
+ @admin.register(AgentEvent)
137
+ class AgentEventAdmin(admin.ModelAdmin):
138
+ """Admin for AgentEvent."""
139
+
140
+ list_display = ["id", "run", "seq", "event_type", "timestamp"]
141
+ list_filter = ["event_type", "timestamp"]
142
+ search_fields = ["run__id", "event_type"]
143
+ readonly_fields = ["id", "run", "seq", "event_type", "payload", "timestamp"]
144
+ raw_id_fields = ["run"]
145
+
146
+
147
+ @admin.register(AgentCheckpoint)
148
+ class AgentCheckpointAdmin(admin.ModelAdmin):
149
+ """Admin for AgentCheckpoint."""
150
+
151
+ list_display = ["id", "run", "seq", "created_at"]
152
+ search_fields = ["run__id"]
153
+ readonly_fields = ["id", "run", "seq", "state", "created_at"]
154
+ raw_id_fields = ["run"]
155
+
@@ -0,0 +1,26 @@
1
+ """
2
+ API module for django_agent_runtime.
3
+
4
+ Provides base ViewSets for agent runtime API. Inherit from these
5
+ in your project and set your own permission_classes.
6
+
7
+ Example:
8
+ from django_agent_runtime.api.views import BaseAgentRunViewSet
9
+
10
+ class AgentRunViewSet(BaseAgentRunViewSet):
11
+ permission_classes = [IsAuthenticated]
12
+ """
13
+
14
+ from django_agent_runtime.api.views import (
15
+ BaseAgentRunViewSet,
16
+ BaseAgentConversationViewSet,
17
+ sync_event_stream,
18
+ async_event_stream,
19
+ )
20
+
21
+ __all__ = [
22
+ "BaseAgentRunViewSet",
23
+ "BaseAgentConversationViewSet",
24
+ "sync_event_stream",
25
+ "async_event_stream",
26
+ ]
@@ -0,0 +1,109 @@
1
+ """
2
+ Custom permissions and authentication for agent runtime API.
3
+
4
+ Supports both authenticated users and anonymous sessions via X-Anonymous-Token header.
5
+ """
6
+
7
+ from rest_framework import permissions
8
+ from rest_framework.authentication import BaseAuthentication
9
+
10
+ from django_agent_runtime.conf import runtime_settings
11
+
12
+
13
+ def _get_anonymous_session_model():
14
+ """
15
+ Get the anonymous session model if configured.
16
+
17
+ Returns None if anonymous sessions are not configured.
18
+ """
19
+ settings = runtime_settings()
20
+ model_path = settings.ANONYMOUS_SESSION_MODEL
21
+
22
+ if not model_path:
23
+ return None
24
+
25
+ try:
26
+ from django.apps import apps
27
+ app_label, model_name = model_path.rsplit('.', 1)
28
+ return apps.get_model(app_label, model_name)
29
+ except Exception:
30
+ return None
31
+
32
+
33
+ class AnonymousSessionAuthentication(BaseAuthentication):
34
+ """
35
+ DRF Authentication class that authenticates via X-Anonymous-Token header.
36
+
37
+ This allows anonymous users to access the agent runtime API by providing
38
+ a valid anonymous session token.
39
+
40
+ To enable, set AGENT_RUNTIME["ANONYMOUS_SESSION_MODEL"] to your session model path,
41
+ e.g., "accounts.AnonymousSession". The model must have:
42
+ - A `token` field
43
+ - An `is_expired` property
44
+ """
45
+
46
+ def authenticate(self, request):
47
+ """
48
+ Authenticate the request using X-Anonymous-Token header.
49
+
50
+ Returns a tuple of (user, auth) where user is None for anonymous sessions
51
+ and auth is the AnonymousSession object.
52
+ """
53
+ token = request.headers.get('X-Anonymous-Token')
54
+ if not token:
55
+ token = request.query_params.get('anonymous_token')
56
+
57
+ if not token:
58
+ return None
59
+
60
+ AnonymousSession = _get_anonymous_session_model()
61
+ if not AnonymousSession:
62
+ return None
63
+
64
+ try:
65
+ session = AnonymousSession.objects.get(token=token)
66
+ if hasattr(session, 'is_expired') and session.is_expired:
67
+ return None
68
+
69
+ # Store the session on the request for later use
70
+ request.anonymous_session = session
71
+
72
+ # Return (None, session) - None user means anonymous
73
+ # The session is the "auth" object
74
+ return (None, session)
75
+ except Exception:
76
+ return None
77
+
78
+ def authenticate_header(self, request):
79
+ """Return a string to be used as the value of the WWW-Authenticate header."""
80
+ return 'X-Anonymous-Token'
81
+
82
+
83
+ class IsAuthenticatedOrAnonymousSession(permissions.BasePermission):
84
+ """
85
+ Permission class that allows access if:
86
+ 1. User is authenticated (via Token auth), OR
87
+ 2. Request has a valid X-Anonymous-Token header
88
+ """
89
+
90
+ def has_permission(self, request, view):
91
+ # Check if user is authenticated
92
+ if request.user and request.user.is_authenticated:
93
+ return True
94
+
95
+ # Check if we have an anonymous session (set by AnonymousSessionAuthentication)
96
+ if hasattr(request, 'anonymous_session') and request.anonymous_session:
97
+ return True
98
+
99
+ return False
100
+
101
+
102
+ def get_anonymous_session(request):
103
+ """
104
+ Helper function to get the anonymous session from a request.
105
+
106
+ Returns the AnonymousSession object if present, None otherwise.
107
+ """
108
+ return getattr(request, 'anonymous_session', None)
109
+
@@ -0,0 +1,114 @@
1
+ """
2
+ DRF serializers for agent runtime API.
3
+ """
4
+
5
+ from rest_framework import serializers
6
+
7
+ from django_agent_runtime.models import AgentRun, AgentConversation, AgentEvent
8
+
9
+
10
+ class AgentConversationSerializer(serializers.ModelSerializer):
11
+ """Serializer for AgentConversation."""
12
+
13
+ class Meta:
14
+ model = AgentConversation
15
+ fields = [
16
+ "id",
17
+ "agent_key",
18
+ "title",
19
+ "metadata",
20
+ "created_at",
21
+ "updated_at",
22
+ ]
23
+ read_only_fields = ["id", "created_at", "updated_at"]
24
+
25
+
26
+ class AgentRunSerializer(serializers.ModelSerializer):
27
+ """Serializer for AgentRun."""
28
+
29
+ class Meta:
30
+ model = AgentRun
31
+ fields = [
32
+ "id",
33
+ "conversation_id",
34
+ "agent_key",
35
+ "status",
36
+ "input",
37
+ "output",
38
+ "error",
39
+ "attempt",
40
+ "max_attempts",
41
+ "idempotency_key",
42
+ "created_at",
43
+ "started_at",
44
+ "finished_at",
45
+ "metadata",
46
+ ]
47
+ read_only_fields = [
48
+ "id",
49
+ "status",
50
+ "output",
51
+ "error",
52
+ "attempt",
53
+ "created_at",
54
+ "started_at",
55
+ "finished_at",
56
+ ]
57
+
58
+
59
+ class AgentRunCreateSerializer(serializers.Serializer):
60
+ """Serializer for creating a new agent run."""
61
+
62
+ agent_key = serializers.CharField(max_length=100)
63
+ conversation_id = serializers.UUIDField(required=False, allow_null=True)
64
+ messages = serializers.ListField(
65
+ child=serializers.DictField(),
66
+ required=True,
67
+ help_text="List of messages in the conversation",
68
+ )
69
+ params = serializers.DictField(
70
+ required=False,
71
+ default=dict,
72
+ help_text="Additional parameters for the agent",
73
+ )
74
+ max_attempts = serializers.IntegerField(
75
+ required=False,
76
+ default=3,
77
+ min_value=1,
78
+ max_value=10,
79
+ )
80
+ idempotency_key = serializers.CharField(
81
+ required=False,
82
+ allow_null=True,
83
+ max_length=255,
84
+ )
85
+ metadata = serializers.DictField(
86
+ required=False,
87
+ default=dict,
88
+ )
89
+
90
+
91
+ class AgentEventSerializer(serializers.ModelSerializer):
92
+ """Serializer for AgentEvent."""
93
+
94
+ class Meta:
95
+ model = AgentEvent
96
+ fields = [
97
+ "id",
98
+ "run_id",
99
+ "seq",
100
+ "event_type",
101
+ "payload",
102
+ "timestamp",
103
+ ]
104
+ read_only_fields = fields
105
+
106
+
107
+ class AgentRunDetailSerializer(AgentRunSerializer):
108
+ """Detailed serializer for AgentRun with events."""
109
+
110
+ events = AgentEventSerializer(many=True, read_only=True)
111
+
112
+ class Meta(AgentRunSerializer.Meta):
113
+ fields = AgentRunSerializer.Meta.fields + ["events"]
114
+