django-cfg 1.1.81__py3-none-any.whl → 1.2.0__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_cfg/__init__.py +20 -448
- django_cfg/apps/accounts/README.md +3 -3
- django_cfg/apps/accounts/admin/__init__.py +0 -2
- django_cfg/apps/accounts/admin/activity.py +2 -9
- django_cfg/apps/accounts/admin/filters.py +0 -42
- django_cfg/apps/accounts/admin/inlines.py +8 -8
- django_cfg/apps/accounts/admin/otp.py +5 -5
- django_cfg/apps/accounts/admin/registration_source.py +1 -8
- django_cfg/apps/accounts/admin/user.py +12 -20
- django_cfg/apps/accounts/managers/user_manager.py +2 -129
- django_cfg/apps/accounts/migrations/0006_remove_twilioresponse_otp_secret_and_more.py +46 -0
- django_cfg/apps/accounts/models.py +3 -123
- django_cfg/apps/accounts/serializers/otp.py +40 -44
- django_cfg/apps/accounts/serializers/profile.py +0 -2
- django_cfg/apps/accounts/services/otp_service.py +98 -186
- django_cfg/apps/accounts/signals.py +25 -15
- django_cfg/apps/accounts/utils/auth_email_service.py +84 -0
- django_cfg/apps/accounts/views/otp.py +35 -36
- django_cfg/apps/agents/README.md +129 -0
- django_cfg/apps/agents/__init__.py +68 -0
- django_cfg/apps/agents/admin/__init__.py +17 -0
- django_cfg/apps/agents/admin/execution_admin.py +460 -0
- django_cfg/apps/agents/admin/registry_admin.py +360 -0
- django_cfg/apps/agents/admin/toolsets_admin.py +482 -0
- django_cfg/apps/agents/apps.py +29 -0
- django_cfg/apps/agents/core/__init__.py +20 -0
- django_cfg/apps/agents/core/agent.py +281 -0
- django_cfg/apps/agents/core/dependencies.py +154 -0
- django_cfg/apps/agents/core/exceptions.py +66 -0
- django_cfg/apps/agents/core/models.py +106 -0
- django_cfg/apps/agents/core/orchestrator.py +391 -0
- django_cfg/apps/agents/examples/__init__.py +3 -0
- django_cfg/apps/agents/examples/simple_example.py +161 -0
- django_cfg/apps/agents/integration/__init__.py +14 -0
- django_cfg/apps/agents/integration/middleware.py +80 -0
- django_cfg/apps/agents/integration/registry.py +345 -0
- django_cfg/apps/agents/integration/signals.py +50 -0
- django_cfg/apps/agents/management/__init__.py +3 -0
- django_cfg/apps/agents/management/commands/__init__.py +3 -0
- django_cfg/apps/agents/management/commands/create_agent.py +365 -0
- django_cfg/apps/agents/management/commands/orchestrator_status.py +191 -0
- django_cfg/apps/agents/managers/__init__.py +23 -0
- django_cfg/apps/agents/managers/execution.py +236 -0
- django_cfg/apps/agents/managers/registry.py +254 -0
- django_cfg/apps/agents/managers/toolsets.py +496 -0
- django_cfg/apps/agents/migrations/0001_initial.py +286 -0
- django_cfg/apps/agents/migrations/__init__.py +5 -0
- django_cfg/apps/agents/models/__init__.py +15 -0
- django_cfg/apps/agents/models/execution.py +215 -0
- django_cfg/apps/agents/models/registry.py +220 -0
- django_cfg/apps/agents/models/toolsets.py +305 -0
- django_cfg/apps/agents/patterns/__init__.py +24 -0
- django_cfg/apps/agents/patterns/content_agents.py +234 -0
- django_cfg/apps/agents/toolsets/__init__.py +15 -0
- django_cfg/apps/agents/toolsets/cache_toolset.py +285 -0
- django_cfg/apps/agents/toolsets/django_toolset.py +220 -0
- django_cfg/apps/agents/toolsets/file_toolset.py +324 -0
- django_cfg/apps/agents/toolsets/orm_toolset.py +319 -0
- django_cfg/apps/agents/urls.py +46 -0
- django_cfg/apps/knowbase/README.md +150 -0
- django_cfg/apps/knowbase/__init__.py +27 -0
- django_cfg/apps/knowbase/admin/__init__.py +23 -0
- django_cfg/apps/knowbase/admin/archive_admin.py +857 -0
- django_cfg/apps/knowbase/admin/chat_admin.py +386 -0
- django_cfg/apps/knowbase/admin/document_admin.py +650 -0
- django_cfg/apps/knowbase/admin/external_data_admin.py +685 -0
- django_cfg/apps/knowbase/apps.py +81 -0
- django_cfg/apps/knowbase/config/README.md +176 -0
- django_cfg/apps/knowbase/config/__init__.py +51 -0
- django_cfg/apps/knowbase/config/constance_fields.py +186 -0
- django_cfg/apps/knowbase/config/constance_settings.py +200 -0
- django_cfg/apps/knowbase/config/settings.py +444 -0
- django_cfg/apps/knowbase/examples/__init__.py +3 -0
- django_cfg/apps/knowbase/examples/external_data_usage.py +191 -0
- django_cfg/apps/knowbase/management/__init__.py +0 -0
- django_cfg/apps/knowbase/management/commands/__init__.py +0 -0
- django_cfg/apps/knowbase/management/commands/knowbase_stats.py +158 -0
- django_cfg/apps/knowbase/management/commands/setup_knowbase.py +59 -0
- django_cfg/apps/knowbase/managers/__init__.py +22 -0
- django_cfg/apps/knowbase/managers/archive.py +426 -0
- django_cfg/apps/knowbase/managers/base.py +32 -0
- django_cfg/apps/knowbase/managers/chat.py +141 -0
- django_cfg/apps/knowbase/managers/document.py +203 -0
- django_cfg/apps/knowbase/managers/external_data.py +471 -0
- django_cfg/apps/knowbase/migrations/0001_initial.py +427 -0
- django_cfg/apps/knowbase/migrations/0002_archiveitem_archiveitemchunk_documentarchive_and_more.py +434 -0
- django_cfg/apps/knowbase/migrations/__init__.py +5 -0
- django_cfg/apps/knowbase/mixins/__init__.py +15 -0
- django_cfg/apps/knowbase/mixins/config.py +108 -0
- django_cfg/apps/knowbase/mixins/creator.py +81 -0
- django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +199 -0
- django_cfg/apps/knowbase/mixins/external_data_mixin.py +813 -0
- django_cfg/apps/knowbase/mixins/service.py +362 -0
- django_cfg/apps/knowbase/models/__init__.py +41 -0
- django_cfg/apps/knowbase/models/archive.py +599 -0
- django_cfg/apps/knowbase/models/base.py +58 -0
- django_cfg/apps/knowbase/models/chat.py +157 -0
- django_cfg/apps/knowbase/models/document.py +267 -0
- django_cfg/apps/knowbase/models/external_data.py +376 -0
- django_cfg/apps/knowbase/serializers/__init__.py +68 -0
- django_cfg/apps/knowbase/serializers/archive_serializers.py +386 -0
- django_cfg/apps/knowbase/serializers/chat_serializers.py +137 -0
- django_cfg/apps/knowbase/serializers/document_serializers.py +94 -0
- django_cfg/apps/knowbase/serializers/external_data_serializers.py +256 -0
- django_cfg/apps/knowbase/serializers/public_serializers.py +74 -0
- django_cfg/apps/knowbase/services/__init__.py +40 -0
- django_cfg/apps/knowbase/services/archive/__init__.py +42 -0
- django_cfg/apps/knowbase/services/archive/archive_service.py +541 -0
- django_cfg/apps/knowbase/services/archive/chunking_service.py +791 -0
- django_cfg/apps/knowbase/services/archive/exceptions.py +52 -0
- django_cfg/apps/knowbase/services/archive/extraction_service.py +508 -0
- django_cfg/apps/knowbase/services/archive/vectorization_service.py +362 -0
- django_cfg/apps/knowbase/services/base.py +53 -0
- django_cfg/apps/knowbase/services/chat_service.py +239 -0
- django_cfg/apps/knowbase/services/document_service.py +144 -0
- django_cfg/apps/knowbase/services/embedding/__init__.py +43 -0
- django_cfg/apps/knowbase/services/embedding/async_processor.py +244 -0
- django_cfg/apps/knowbase/services/embedding/batch_processor.py +250 -0
- django_cfg/apps/knowbase/services/embedding/batch_result.py +61 -0
- django_cfg/apps/knowbase/services/embedding/models.py +229 -0
- django_cfg/apps/knowbase/services/embedding/processors.py +148 -0
- django_cfg/apps/knowbase/services/embedding/utils.py +176 -0
- django_cfg/apps/knowbase/services/prompt_builder.py +191 -0
- django_cfg/apps/knowbase/services/search_service.py +293 -0
- django_cfg/apps/knowbase/signals/__init__.py +21 -0
- django_cfg/apps/knowbase/signals/archive_signals.py +211 -0
- django_cfg/apps/knowbase/signals/chat_signals.py +37 -0
- django_cfg/apps/knowbase/signals/document_signals.py +143 -0
- django_cfg/apps/knowbase/signals/external_data_signals.py +157 -0
- django_cfg/apps/knowbase/tasks/__init__.py +39 -0
- django_cfg/apps/knowbase/tasks/archive_tasks.py +316 -0
- django_cfg/apps/knowbase/tasks/document_processing.py +341 -0
- django_cfg/apps/knowbase/tasks/external_data_tasks.py +341 -0
- django_cfg/apps/knowbase/tasks/maintenance.py +195 -0
- django_cfg/apps/knowbase/urls.py +43 -0
- django_cfg/apps/knowbase/utils/__init__.py +12 -0
- django_cfg/apps/knowbase/utils/chunk_settings.py +261 -0
- django_cfg/apps/knowbase/utils/text_processing.py +375 -0
- django_cfg/apps/knowbase/utils/validation.py +99 -0
- django_cfg/apps/knowbase/views/__init__.py +28 -0
- django_cfg/apps/knowbase/views/archive_views.py +469 -0
- django_cfg/apps/knowbase/views/base.py +49 -0
- django_cfg/apps/knowbase/views/chat_views.py +181 -0
- django_cfg/apps/knowbase/views/document_views.py +183 -0
- django_cfg/apps/knowbase/views/public_views.py +129 -0
- django_cfg/apps/leads/admin.py +70 -0
- django_cfg/apps/newsletter/admin.py +234 -0
- django_cfg/apps/newsletter/admin_filters.py +124 -0
- django_cfg/apps/support/admin.py +196 -0
- django_cfg/apps/support/admin_filters.py +71 -0
- django_cfg/apps/support/templates/support/chat/ticket_chat.html +1 -1
- django_cfg/apps/urls.py +5 -4
- django_cfg/cli/README.md +1 -1
- django_cfg/cli/commands/create_project.py +2 -2
- django_cfg/cli/commands/info.py +1 -1
- django_cfg/config.py +44 -0
- django_cfg/core/config.py +29 -82
- django_cfg/core/environment.py +1 -1
- django_cfg/core/generation.py +19 -107
- django_cfg/{integration.py → core/integration.py} +18 -16
- django_cfg/core/validation.py +1 -1
- django_cfg/management/__init__.py +1 -1
- django_cfg/management/commands/__init__.py +1 -1
- django_cfg/management/commands/auto_generate.py +482 -0
- django_cfg/management/commands/migrator.py +19 -101
- django_cfg/management/commands/test_email.py +1 -1
- django_cfg/middleware/README.md +0 -158
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/middleware/user_activity.py +3 -3
- django_cfg/models/api.py +145 -0
- django_cfg/models/base.py +287 -0
- django_cfg/models/cache.py +4 -4
- django_cfg/models/constance.py +25 -88
- django_cfg/models/database.py +9 -9
- django_cfg/models/drf.py +3 -36
- django_cfg/models/email.py +163 -0
- django_cfg/models/environment.py +276 -0
- django_cfg/models/limits.py +1 -1
- django_cfg/models/logging.py +366 -0
- django_cfg/models/revolution.py +41 -2
- django_cfg/models/security.py +125 -0
- django_cfg/models/services.py +1 -1
- django_cfg/modules/__init__.py +2 -56
- django_cfg/modules/base.py +78 -52
- django_cfg/modules/django_currency/service.py +2 -2
- django_cfg/modules/django_email.py +2 -2
- django_cfg/modules/django_health.py +267 -0
- django_cfg/modules/django_llm/llm/client.py +79 -17
- django_cfg/modules/django_llm/translator/translator.py +2 -2
- django_cfg/modules/django_logger.py +2 -2
- django_cfg/modules/django_ngrok.py +2 -2
- django_cfg/modules/django_tasks.py +68 -3
- django_cfg/modules/django_telegram.py +3 -3
- django_cfg/modules/django_twilio/sendgrid_service.py +2 -2
- django_cfg/modules/django_twilio/service.py +2 -2
- django_cfg/modules/django_twilio/simple_service.py +2 -2
- django_cfg/modules/django_twilio/templates/guide.md +266 -0
- django_cfg/modules/django_twilio/twilio_service.py +2 -2
- django_cfg/modules/django_unfold/__init__.py +69 -0
- django_cfg/modules/{unfold → django_unfold}/callbacks.py +23 -22
- django_cfg/modules/django_unfold/dashboard.py +278 -0
- django_cfg/modules/django_unfold/icons/README.md +145 -0
- django_cfg/modules/django_unfold/icons/__init__.py +12 -0
- django_cfg/modules/django_unfold/icons/constants.py +2851 -0
- django_cfg/modules/django_unfold/icons/generate_icons.py +486 -0
- django_cfg/modules/django_unfold/models/__init__.py +42 -0
- django_cfg/modules/django_unfold/models/config.py +601 -0
- django_cfg/modules/django_unfold/models/dashboard.py +206 -0
- django_cfg/modules/django_unfold/models/dropdown.py +40 -0
- django_cfg/modules/django_unfold/models/navigation.py +73 -0
- django_cfg/modules/django_unfold/models/tabs.py +25 -0
- django_cfg/modules/{unfold → django_unfold}/system_monitor.py +2 -2
- django_cfg/modules/django_unfold/utils.py +140 -0
- django_cfg/registry/__init__.py +23 -0
- django_cfg/registry/core.py +61 -0
- django_cfg/registry/exceptions.py +11 -0
- django_cfg/registry/modules.py +12 -0
- django_cfg/registry/services.py +26 -0
- django_cfg/registry/third_party.py +52 -0
- django_cfg/routing/__init__.py +19 -0
- django_cfg/routing/callbacks.py +198 -0
- django_cfg/routing/routers.py +48 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +8 -9
- django_cfg/templatetags/__init__.py +0 -0
- django_cfg/templatetags/django_cfg.py +33 -0
- django_cfg/urls.py +33 -0
- django_cfg/utils/path_resolution.py +1 -1
- django_cfg/utils/smart_defaults.py +7 -61
- django_cfg/utils/toolkit.py +663 -0
- {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/METADATA +83 -86
- django_cfg-1.2.0.dist-info/RECORD +441 -0
- django_cfg/apps/tasks/@docs/README.md +0 -195
- django_cfg/archive/django_sample.zip +0 -0
- django_cfg/models/unfold.py +0 -271
- django_cfg/modules/unfold/__init__.py +0 -29
- django_cfg/modules/unfold/dashboard.py +0 -318
- django_cfg/pyproject.toml +0 -370
- django_cfg/routers.py +0 -83
- django_cfg-1.1.81.dist-info/RECORD +0 -278
- /django_cfg/{exceptions.py → core/exceptions.py} +0 -0
- /django_cfg/modules/{unfold → django_unfold}/models.py +0 -0
- /django_cfg/modules/{unfold → django_unfold}/tailwind.py +0 -0
- /django_cfg/{version_check.py → utils/version_check.py} +0 -0
- {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/WHEEL +0 -0
- {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,281 @@
|
|
1
|
+
"""
|
2
|
+
Django Agent - Wrapper around Pydantic AI Agent with Django integration.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import time
|
6
|
+
import logging
|
7
|
+
from typing import TypeVar, Generic, Type, Any, Optional, Dict, Callable
|
8
|
+
from dataclasses import dataclass
|
9
|
+
|
10
|
+
from pydantic_ai import Agent
|
11
|
+
from pydantic_ai.models import Model
|
12
|
+
|
13
|
+
from .exceptions import ExecutionError, ConfigurationError
|
14
|
+
from .models import ExecutionResult
|
15
|
+
|
16
|
+
# Type variables for generic typing
|
17
|
+
DepsT = TypeVar('DepsT')
|
18
|
+
OutputT = TypeVar('OutputT')
|
19
|
+
|
20
|
+
logger = logging.getLogger(__name__)
|
21
|
+
|
22
|
+
|
23
|
+
class DjangoAgent(Generic[DepsT, OutputT]):
|
24
|
+
"""
|
25
|
+
Django-integrated agent wrapper around Pydantic AI Agent.
|
26
|
+
|
27
|
+
Provides Django-specific functionality like:
|
28
|
+
- Integration with django_llm module
|
29
|
+
- Caching support
|
30
|
+
- Metrics collection
|
31
|
+
- Error handling
|
32
|
+
- Type safety
|
33
|
+
"""
|
34
|
+
|
35
|
+
def __init__(
|
36
|
+
self,
|
37
|
+
name: str,
|
38
|
+
deps_type: Type[DepsT],
|
39
|
+
output_type: Type[OutputT],
|
40
|
+
instructions: str,
|
41
|
+
model: Optional[str] = None,
|
42
|
+
llm_client: Optional[Any] = None,
|
43
|
+
timeout: int = 300,
|
44
|
+
max_retries: int = 3,
|
45
|
+
enable_caching: bool = True
|
46
|
+
):
|
47
|
+
"""
|
48
|
+
Initialize Django Agent.
|
49
|
+
|
50
|
+
Args:
|
51
|
+
name: Unique agent identifier
|
52
|
+
deps_type: Type for dependency injection (must be dataclass)
|
53
|
+
output_type: Pydantic model for output validation
|
54
|
+
instructions: System prompt for the agent
|
55
|
+
model: Override model (uses client default if None)
|
56
|
+
llm_client: Optional LLM client (uses default if None)
|
57
|
+
timeout: Execution timeout in seconds
|
58
|
+
max_retries: Maximum retry attempts
|
59
|
+
enable_caching: Whether to enable result caching
|
60
|
+
"""
|
61
|
+
self.name = name
|
62
|
+
self.deps_type = deps_type
|
63
|
+
self.output_type = output_type
|
64
|
+
self.instructions = instructions
|
65
|
+
self.timeout = timeout
|
66
|
+
self.max_retries = max_retries
|
67
|
+
self.enable_caching = enable_caching
|
68
|
+
|
69
|
+
# Get LLM client
|
70
|
+
self.llm_client = llm_client or self._get_default_llm_client()
|
71
|
+
|
72
|
+
# Determine model to use
|
73
|
+
model_name = model or self._get_default_model()
|
74
|
+
|
75
|
+
# Create Pydantic AI agent
|
76
|
+
self.agent = Agent[DepsT, OutputT](
|
77
|
+
model=model_name,
|
78
|
+
deps_type=deps_type,
|
79
|
+
output_type=output_type,
|
80
|
+
instructions=instructions,
|
81
|
+
retries=max_retries
|
82
|
+
)
|
83
|
+
|
84
|
+
# Initialize metrics
|
85
|
+
self._execution_count = 0
|
86
|
+
self._total_execution_time = 0.0
|
87
|
+
self._error_count = 0
|
88
|
+
self._cache_hits = 0
|
89
|
+
|
90
|
+
logger.info(f"Initialized DjangoAgent '{name}' with model '{model_name}'")
|
91
|
+
|
92
|
+
async def run(self, prompt: str, deps: DepsT, **kwargs) -> ExecutionResult:
|
93
|
+
"""
|
94
|
+
Run agent with Django-specific enhancements.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
prompt: User prompt/instruction
|
98
|
+
deps: Dependencies instance
|
99
|
+
**kwargs: Additional Pydantic AI parameters
|
100
|
+
|
101
|
+
Returns:
|
102
|
+
ExecutionResult with output, usage, and metadata
|
103
|
+
"""
|
104
|
+
start_time = time.time()
|
105
|
+
self._execution_count += 1
|
106
|
+
|
107
|
+
# Check cache if enabled
|
108
|
+
if self.enable_caching:
|
109
|
+
cached_result = await self._check_cache(prompt, deps)
|
110
|
+
if cached_result:
|
111
|
+
self._cache_hits += 1
|
112
|
+
logger.debug(f"Cache hit for agent '{self.name}'")
|
113
|
+
return ExecutionResult(
|
114
|
+
agent_name=self.name,
|
115
|
+
output=cached_result,
|
116
|
+
execution_time=0.0,
|
117
|
+
cached=True
|
118
|
+
)
|
119
|
+
|
120
|
+
try:
|
121
|
+
# Execute agent
|
122
|
+
logger.debug(f"Executing agent '{self.name}' with prompt: {prompt[:100]}...")
|
123
|
+
|
124
|
+
result = await self.agent.run(prompt, deps=deps, **kwargs)
|
125
|
+
|
126
|
+
execution_time = time.time() - start_time
|
127
|
+
self._total_execution_time += execution_time
|
128
|
+
|
129
|
+
# Extract metrics
|
130
|
+
tokens_used = 0
|
131
|
+
cost = 0.0
|
132
|
+
if hasattr(result, 'usage') and result.usage:
|
133
|
+
tokens_used = getattr(result.usage, 'total_tokens', 0)
|
134
|
+
# Calculate cost based on model and tokens (implement based on your pricing)
|
135
|
+
cost = self._calculate_cost(tokens_used)
|
136
|
+
|
137
|
+
execution_result = ExecutionResult(
|
138
|
+
agent_name=self.name,
|
139
|
+
output=result.output,
|
140
|
+
execution_time=execution_time,
|
141
|
+
tokens_used=tokens_used,
|
142
|
+
cost=cost
|
143
|
+
)
|
144
|
+
|
145
|
+
# Cache result if enabled
|
146
|
+
if self.enable_caching:
|
147
|
+
await self._cache_result(prompt, deps, result.output)
|
148
|
+
|
149
|
+
logger.info(
|
150
|
+
f"Agent '{self.name}' executed successfully in {execution_time:.2f}s "
|
151
|
+
f"(tokens: {tokens_used}, cost: ${cost:.4f})"
|
152
|
+
)
|
153
|
+
|
154
|
+
return execution_result
|
155
|
+
|
156
|
+
except Exception as e:
|
157
|
+
self._error_count += 1
|
158
|
+
execution_time = time.time() - start_time
|
159
|
+
|
160
|
+
logger.error(f"Agent '{self.name}' execution failed: {e}")
|
161
|
+
|
162
|
+
raise ExecutionError(
|
163
|
+
f"Agent '{self.name}' execution failed: {str(e)}",
|
164
|
+
agent_name=self.name,
|
165
|
+
original_error=e
|
166
|
+
)
|
167
|
+
|
168
|
+
def tool(self, func: Callable) -> Callable:
|
169
|
+
"""
|
170
|
+
Register tool with agent.
|
171
|
+
|
172
|
+
Args:
|
173
|
+
func: Function to register as agent tool
|
174
|
+
|
175
|
+
Returns:
|
176
|
+
Decorated function
|
177
|
+
"""
|
178
|
+
return self.agent.tool(func)
|
179
|
+
|
180
|
+
def get_metrics(self) -> Dict[str, Any]:
|
181
|
+
"""Get agent execution metrics."""
|
182
|
+
return {
|
183
|
+
'name': self.name,
|
184
|
+
'execution_count': self._execution_count,
|
185
|
+
'total_execution_time': self._total_execution_time,
|
186
|
+
'average_execution_time': (
|
187
|
+
self._total_execution_time / self._execution_count
|
188
|
+
if self._execution_count > 0 else 0
|
189
|
+
),
|
190
|
+
'error_count': self._error_count,
|
191
|
+
'success_rate': (
|
192
|
+
(self._execution_count - self._error_count) / self._execution_count
|
193
|
+
if self._execution_count > 0 else 0
|
194
|
+
),
|
195
|
+
'cache_hits': self._cache_hits,
|
196
|
+
'cache_hit_rate': (
|
197
|
+
self._cache_hits / self._execution_count
|
198
|
+
if self._execution_count > 0 else 0
|
199
|
+
)
|
200
|
+
}
|
201
|
+
|
202
|
+
def reset_metrics(self):
|
203
|
+
"""Reset agent metrics."""
|
204
|
+
self._execution_count = 0
|
205
|
+
self._total_execution_time = 0.0
|
206
|
+
self._error_count = 0
|
207
|
+
self._cache_hits = 0
|
208
|
+
|
209
|
+
def _get_default_llm_client(self):
|
210
|
+
"""Get default LLM client from django_llm module."""
|
211
|
+
try:
|
212
|
+
from django_cfg.modules.django_llm import DjangoLLM
|
213
|
+
llm_service = DjangoLLM()
|
214
|
+
if llm_service.is_configured:
|
215
|
+
return llm_service.client
|
216
|
+
except ImportError:
|
217
|
+
logger.warning("django_llm module not available, using default client")
|
218
|
+
|
219
|
+
return None
|
220
|
+
|
221
|
+
def _get_default_model(self) -> str:
|
222
|
+
"""Get default model name."""
|
223
|
+
if self.llm_client and hasattr(self.llm_client, 'primary_provider'):
|
224
|
+
if self.llm_client.primary_provider == 'openrouter':
|
225
|
+
return 'openai:gpt-4o-mini'
|
226
|
+
elif self.llm_client.primary_provider == 'openai':
|
227
|
+
return 'openai:gpt-4o-mini'
|
228
|
+
|
229
|
+
return 'openai:gpt-4o-mini' # Default fallback
|
230
|
+
|
231
|
+
async def _check_cache(self, prompt: str, deps: DepsT) -> Optional[Any]:
|
232
|
+
"""Check cache for existing result."""
|
233
|
+
if not self.llm_client or not hasattr(self.llm_client, 'cache'):
|
234
|
+
return None
|
235
|
+
|
236
|
+
try:
|
237
|
+
cache_key = self._generate_cache_key(prompt, deps)
|
238
|
+
return self.llm_client.cache.get(cache_key)
|
239
|
+
except Exception as e:
|
240
|
+
logger.warning(f"Cache check failed: {e}")
|
241
|
+
return None
|
242
|
+
|
243
|
+
async def _cache_result(self, prompt: str, deps: DepsT, result: Any):
|
244
|
+
"""Cache execution result."""
|
245
|
+
if not self.llm_client or not hasattr(self.llm_client, 'cache'):
|
246
|
+
return
|
247
|
+
|
248
|
+
try:
|
249
|
+
cache_key = self._generate_cache_key(prompt, deps)
|
250
|
+
self.llm_client.cache.set(cache_key, result)
|
251
|
+
except Exception as e:
|
252
|
+
logger.warning(f"Cache storage failed: {e}")
|
253
|
+
|
254
|
+
def _generate_cache_key(self, prompt: str, deps: DepsT) -> str:
|
255
|
+
"""Generate cache key for prompt and dependencies."""
|
256
|
+
# Create a simple hash-based key
|
257
|
+
import hashlib
|
258
|
+
|
259
|
+
# Include agent name, prompt, and relevant deps data
|
260
|
+
key_data = f"{self.name}:{prompt}"
|
261
|
+
|
262
|
+
# Add user info if available
|
263
|
+
if hasattr(deps, 'user') and deps.user:
|
264
|
+
key_data += f":user_{deps.user.id}"
|
265
|
+
|
266
|
+
# Add other relevant dependency data
|
267
|
+
if hasattr(deps, 'to_dict'):
|
268
|
+
deps_str = str(sorted(deps.to_dict().items()))
|
269
|
+
key_data += f":deps_{deps_str}"
|
270
|
+
|
271
|
+
return hashlib.md5(key_data.encode()).hexdigest()
|
272
|
+
|
273
|
+
def _calculate_cost(self, tokens_used: int) -> float:
|
274
|
+
"""Calculate cost based on tokens used."""
|
275
|
+
# Implement based on your pricing model
|
276
|
+
# This is a simple example
|
277
|
+
cost_per_1k_tokens = 0.002 # Example: $0.002 per 1K tokens
|
278
|
+
return (tokens_used / 1000) * cost_per_1k_tokens
|
279
|
+
|
280
|
+
def __repr__(self) -> str:
|
281
|
+
return f"DjangoAgent(name='{self.name}', deps_type={self.deps_type.__name__}, output_type={self.output_type.__name__})"
|
@@ -0,0 +1,154 @@
|
|
1
|
+
"""
|
2
|
+
Dependency injection classes for Django Orchestrator.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from dataclasses import dataclass, field
|
6
|
+
from typing import Dict, Any, Optional, TYPE_CHECKING
|
7
|
+
from django.http import HttpRequest
|
8
|
+
|
9
|
+
# Re-export RunContext from pydantic_ai for convenience
|
10
|
+
from pydantic_ai import RunContext
|
11
|
+
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from django.contrib.auth.models import AbstractUser
|
14
|
+
|
15
|
+
__all__ = ["DjangoDeps", "ContentDeps", "RunContext"]
|
16
|
+
|
17
|
+
|
18
|
+
@dataclass
|
19
|
+
class DjangoDeps:
|
20
|
+
"""Standard Django dependencies for agent execution."""
|
21
|
+
|
22
|
+
user: "AbstractUser"
|
23
|
+
request: Optional[HttpRequest] = None
|
24
|
+
session_data: Dict[str, Any] = field(default_factory=dict)
|
25
|
+
|
26
|
+
@classmethod
|
27
|
+
async def from_request(cls, request: HttpRequest) -> 'DjangoDeps':
|
28
|
+
"""Create dependencies from Django request."""
|
29
|
+
return cls(
|
30
|
+
user=request.user,
|
31
|
+
request=request,
|
32
|
+
session_data=dict(request.session) if request.session else {}
|
33
|
+
)
|
34
|
+
|
35
|
+
@classmethod
|
36
|
+
async def from_user_id(cls, user_id: int) -> 'DjangoDeps':
|
37
|
+
"""Create dependencies from user ID."""
|
38
|
+
from django.contrib.auth import get_user_model
|
39
|
+
User = get_user_model()
|
40
|
+
user = await User.objects.aget(id=user_id)
|
41
|
+
return cls(user=user)
|
42
|
+
|
43
|
+
@classmethod
|
44
|
+
async def from_user(cls, user: "AbstractUser") -> 'DjangoDeps':
|
45
|
+
"""Create dependencies from user instance."""
|
46
|
+
return cls(user=user)
|
47
|
+
|
48
|
+
def to_dict(self) -> Dict[str, Any]:
|
49
|
+
"""Convert to dictionary for serialization."""
|
50
|
+
return {
|
51
|
+
'user_id': self.user.id,
|
52
|
+
'username': self.user.username,
|
53
|
+
'session_data': self.session_data,
|
54
|
+
'has_request': self.request is not None
|
55
|
+
}
|
56
|
+
|
57
|
+
|
58
|
+
@dataclass
|
59
|
+
class ContentDeps(DjangoDeps):
|
60
|
+
"""Content-specific dependencies."""
|
61
|
+
|
62
|
+
content_id: Optional[int] = None
|
63
|
+
content_type: str = "article"
|
64
|
+
target_audience: str = "general"
|
65
|
+
|
66
|
+
async def get_content(self):
|
67
|
+
"""Get content object if content_id is provided."""
|
68
|
+
if not self.content_id:
|
69
|
+
return None
|
70
|
+
|
71
|
+
# Import here to avoid circular imports
|
72
|
+
from django.apps import apps
|
73
|
+
|
74
|
+
try:
|
75
|
+
Content = apps.get_model('your_app', 'Content') # Adjust app name
|
76
|
+
return await Content.objects.aget(id=self.content_id)
|
77
|
+
except Exception:
|
78
|
+
return None
|
79
|
+
|
80
|
+
|
81
|
+
@dataclass
|
82
|
+
class DataProcessingDeps(DjangoDeps):
|
83
|
+
"""Dependencies for data processing agents."""
|
84
|
+
|
85
|
+
dataset_id: Optional[int] = None
|
86
|
+
processing_options: Dict[str, Any] = field(default_factory=dict)
|
87
|
+
batch_size: int = 100
|
88
|
+
|
89
|
+
async def get_dataset(self):
|
90
|
+
"""Get dataset object if dataset_id is provided."""
|
91
|
+
if not self.dataset_id:
|
92
|
+
return None
|
93
|
+
|
94
|
+
# Import here to avoid circular imports
|
95
|
+
from django.apps import apps
|
96
|
+
|
97
|
+
try:
|
98
|
+
Dataset = apps.get_model('your_app', 'Dataset') # Adjust app name
|
99
|
+
return await Dataset.objects.aget(id=self.dataset_id)
|
100
|
+
except Exception:
|
101
|
+
return None
|
102
|
+
|
103
|
+
|
104
|
+
@dataclass
|
105
|
+
class BusinessLogicDeps(DjangoDeps):
|
106
|
+
"""Dependencies for business logic agents."""
|
107
|
+
|
108
|
+
business_context: Dict[str, Any] = field(default_factory=dict)
|
109
|
+
rules_version: str = "latest"
|
110
|
+
organization_id: Optional[int] = None
|
111
|
+
|
112
|
+
async def get_organization(self):
|
113
|
+
"""Get organization object if organization_id is provided."""
|
114
|
+
if not self.organization_id:
|
115
|
+
return None
|
116
|
+
|
117
|
+
# Import here to avoid circular imports
|
118
|
+
from django.apps import apps
|
119
|
+
|
120
|
+
try:
|
121
|
+
Organization = apps.get_model('your_app', 'Organization') # Adjust app name
|
122
|
+
return await Organization.objects.aget(id=self.organization_id)
|
123
|
+
except Exception:
|
124
|
+
return None
|
125
|
+
|
126
|
+
|
127
|
+
@dataclass
|
128
|
+
class IntegrationDeps(DjangoDeps):
|
129
|
+
"""Dependencies for external integration agents."""
|
130
|
+
|
131
|
+
api_credentials: Dict[str, str] = field(default_factory=dict)
|
132
|
+
service_config: Dict[str, Any] = field(default_factory=dict)
|
133
|
+
rate_limit_key: Optional[str] = None
|
134
|
+
|
135
|
+
def get_api_key(self, service_name: str) -> Optional[str]:
|
136
|
+
"""Get API key for specific service."""
|
137
|
+
return self.api_credentials.get(f"{service_name}_api_key")
|
138
|
+
|
139
|
+
def get_service_config(self, service_name: str) -> Dict[str, Any]:
|
140
|
+
"""Get configuration for specific service."""
|
141
|
+
return self.service_config.get(service_name, {})
|
142
|
+
|
143
|
+
|
144
|
+
@dataclass
|
145
|
+
class TestDeps:
|
146
|
+
"""Simple dependencies for testing."""
|
147
|
+
|
148
|
+
user_id: int
|
149
|
+
test_data: Dict[str, Any] = field(default_factory=dict)
|
150
|
+
mock_responses: Dict[str, Any] = field(default_factory=dict)
|
151
|
+
|
152
|
+
def get_mock_response(self, key: str) -> Any:
|
153
|
+
"""Get mock response for testing."""
|
154
|
+
return self.mock_responses.get(key)
|
@@ -0,0 +1,66 @@
|
|
1
|
+
"""
|
2
|
+
Exception classes for Django Agents.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Optional, Any
|
6
|
+
|
7
|
+
|
8
|
+
class AgentError(Exception):
|
9
|
+
"""Base exception for Django Agents."""
|
10
|
+
|
11
|
+
def __init__(self, message: str, details: Optional[dict] = None):
|
12
|
+
super().__init__(message)
|
13
|
+
self.message = message
|
14
|
+
self.details = details or {}
|
15
|
+
|
16
|
+
|
17
|
+
class AgentNotFoundError(AgentError):
|
18
|
+
"""Raised when requested agent is not found in registry."""
|
19
|
+
|
20
|
+
def __init__(self, agent_name: str):
|
21
|
+
message = f"Agent '{agent_name}' not found in registry"
|
22
|
+
super().__init__(message)
|
23
|
+
self.agent_name = agent_name
|
24
|
+
|
25
|
+
|
26
|
+
class ExecutionError(AgentError):
|
27
|
+
"""Raised when agent execution fails."""
|
28
|
+
|
29
|
+
def __init__(
|
30
|
+
self,
|
31
|
+
message: str,
|
32
|
+
agent_name: Optional[str] = None,
|
33
|
+
original_error: Optional[Exception] = None
|
34
|
+
):
|
35
|
+
super().__init__(message)
|
36
|
+
self.agent_name = agent_name
|
37
|
+
self.original_error = original_error
|
38
|
+
|
39
|
+
|
40
|
+
class ValidationError(AgentError):
|
41
|
+
"""Raised when input/output validation fails."""
|
42
|
+
|
43
|
+
def __init__(self, message: str, validation_errors: Optional[list] = None):
|
44
|
+
super().__init__(message)
|
45
|
+
self.validation_errors = validation_errors or []
|
46
|
+
|
47
|
+
|
48
|
+
class ConfigurationError(AgentError):
|
49
|
+
"""Raised when agent configuration is invalid."""
|
50
|
+
pass
|
51
|
+
|
52
|
+
|
53
|
+
class PermissionError(AgentError):
|
54
|
+
"""Raised when user lacks required permissions."""
|
55
|
+
|
56
|
+
def __init__(self, message: str, required_permission: Optional[str] = None):
|
57
|
+
super().__init__(message)
|
58
|
+
self.required_permission = required_permission
|
59
|
+
|
60
|
+
|
61
|
+
class TimeoutError(AgentError):
|
62
|
+
"""Raised when agent execution times out."""
|
63
|
+
|
64
|
+
def __init__(self, message: str, timeout_seconds: Optional[float] = None):
|
65
|
+
super().__init__(message)
|
66
|
+
self.timeout_seconds = timeout_seconds
|
@@ -0,0 +1,106 @@
|
|
1
|
+
"""
|
2
|
+
Data models for Django Orchestrator.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from dataclasses import dataclass, field
|
6
|
+
from datetime import datetime
|
7
|
+
from typing import Any, Dict, List, Optional, Literal
|
8
|
+
from pydantic import BaseModel, Field, ConfigDict
|
9
|
+
|
10
|
+
|
11
|
+
@dataclass
|
12
|
+
class ExecutionResult:
|
13
|
+
"""Result from agent execution with metadata."""
|
14
|
+
|
15
|
+
agent_name: str
|
16
|
+
output: Any
|
17
|
+
execution_time: float
|
18
|
+
tokens_used: int = 0
|
19
|
+
cost: float = 0.0
|
20
|
+
cached: bool = False
|
21
|
+
error: Optional[str] = None
|
22
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
23
|
+
|
24
|
+
@property
|
25
|
+
def success(self) -> bool:
|
26
|
+
"""Check if execution was successful."""
|
27
|
+
return self.error is None
|
28
|
+
|
29
|
+
|
30
|
+
class WorkflowConfig(BaseModel):
|
31
|
+
"""Configuration for workflow execution."""
|
32
|
+
|
33
|
+
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
34
|
+
|
35
|
+
name: str = Field(..., description="Workflow identifier")
|
36
|
+
timeout: int = Field(default=300, ge=1, le=3600, description="Timeout in seconds")
|
37
|
+
max_retries: int = Field(default=3, ge=0, le=10, description="Maximum retry attempts")
|
38
|
+
execution_strategy: Literal["sequential", "parallel", "conditional"] = Field(
|
39
|
+
default="sequential", description="Execution strategy"
|
40
|
+
)
|
41
|
+
priority: int = Field(default=1, ge=1, le=10, description="Execution priority")
|
42
|
+
max_concurrent: int = Field(default=5, ge=1, le=20, description="Max concurrent executions")
|
43
|
+
|
44
|
+
|
45
|
+
class AgentDefinition(BaseModel):
|
46
|
+
"""Agent definition for registry storage."""
|
47
|
+
|
48
|
+
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
49
|
+
|
50
|
+
name: str = Field(..., description="Agent identifier")
|
51
|
+
instructions: str = Field(..., description="Agent instructions")
|
52
|
+
deps_type: str = Field(..., description="Dependencies type name")
|
53
|
+
output_type: str = Field(..., description="Output type name")
|
54
|
+
model: str = Field(default="openai:gpt-4o-mini", description="LLM model to use")
|
55
|
+
timeout: int = Field(default=300, ge=1, le=3600, description="Execution timeout")
|
56
|
+
max_retries: int = Field(default=3, ge=0, le=10, description="Maximum retries")
|
57
|
+
is_active: bool = Field(default=True, description="Whether agent is active")
|
58
|
+
created_at: datetime = Field(default_factory=datetime.now)
|
59
|
+
updated_at: datetime = Field(default_factory=datetime.now)
|
60
|
+
|
61
|
+
|
62
|
+
class ProcessResult(BaseModel):
|
63
|
+
"""Standard processing result model."""
|
64
|
+
|
65
|
+
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
66
|
+
|
67
|
+
success: bool = Field(..., description="Whether processing was successful")
|
68
|
+
message: str = Field(..., description="Result message")
|
69
|
+
data: Dict[str, Any] = Field(default_factory=dict, description="Additional data")
|
70
|
+
timestamp: datetime = Field(default_factory=datetime.now)
|
71
|
+
|
72
|
+
|
73
|
+
class AnalysisResult(BaseModel):
|
74
|
+
"""Standard analysis result model."""
|
75
|
+
|
76
|
+
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
77
|
+
|
78
|
+
sentiment: str = Field(..., description="Sentiment analysis result")
|
79
|
+
confidence: float = Field(..., ge=0.0, le=1.0, description="Confidence score")
|
80
|
+
topics: List[str] = Field(default_factory=list, description="Extracted topics")
|
81
|
+
keywords: List[str] = Field(default_factory=list, description="Key terms")
|
82
|
+
summary: str = Field(default="", description="Content summary")
|
83
|
+
|
84
|
+
|
85
|
+
class ValidationResult(BaseModel):
|
86
|
+
"""Standard validation result model."""
|
87
|
+
|
88
|
+
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
89
|
+
|
90
|
+
is_valid: bool = Field(..., description="Whether input is valid")
|
91
|
+
errors: List[str] = Field(default_factory=list, description="Validation errors")
|
92
|
+
warnings: List[str] = Field(default_factory=list, description="Validation warnings")
|
93
|
+
suggestions: List[str] = Field(default_factory=list, description="Improvement suggestions")
|
94
|
+
|
95
|
+
|
96
|
+
@dataclass
|
97
|
+
class ErrorContext:
|
98
|
+
"""Error context information."""
|
99
|
+
|
100
|
+
agent_name: str
|
101
|
+
prompt: str
|
102
|
+
error_type: str
|
103
|
+
error_message: str
|
104
|
+
timestamp: datetime
|
105
|
+
retry_count: int = 0
|
106
|
+
stack_trace: Optional[str] = None
|