django-cfg 1.1.82__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/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.82.dist-info → django_cfg-1.2.0.dist-info}/METADATA +83 -86
- django_cfg-1.2.0.dist-info/RECORD +441 -0
- 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.82.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.82.dist-info → django_cfg-1.2.0.dist-info}/WHEEL +0 -0
- {django_cfg-1.1.82.dist-info → django_cfg-1.2.0.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.1.82.dist-info → django_cfg-1.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,391 @@
|
|
1
|
+
"""
|
2
|
+
Simple Orchestrator - Main coordination class for multi-agent workflows.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import asyncio
|
6
|
+
import time
|
7
|
+
import logging
|
8
|
+
from typing import Dict, List, Any, Optional, Callable
|
9
|
+
from dataclasses import dataclass
|
10
|
+
|
11
|
+
from .agent import DjangoAgent
|
12
|
+
from .models import ExecutionResult, WorkflowConfig
|
13
|
+
from .exceptions import AgentNotFoundError, ExecutionError, ConfigurationError
|
14
|
+
|
15
|
+
logger = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
|
18
|
+
@dataclass
|
19
|
+
class ExecutionPattern:
|
20
|
+
"""Base class for execution patterns."""
|
21
|
+
|
22
|
+
name: str
|
23
|
+
description: str
|
24
|
+
|
25
|
+
async def execute(
|
26
|
+
self,
|
27
|
+
agents: List[DjangoAgent],
|
28
|
+
prompt: str,
|
29
|
+
deps: Any,
|
30
|
+
**kwargs
|
31
|
+
) -> List[ExecutionResult]:
|
32
|
+
"""Execute agents according to pattern."""
|
33
|
+
raise NotImplementedError
|
34
|
+
|
35
|
+
|
36
|
+
class SequentialPattern(ExecutionPattern):
|
37
|
+
"""Sequential execution pattern - agents run one after another."""
|
38
|
+
|
39
|
+
def __init__(self):
|
40
|
+
super().__init__(
|
41
|
+
name="sequential",
|
42
|
+
description="Execute agents one after another, passing results forward"
|
43
|
+
)
|
44
|
+
|
45
|
+
async def execute(
|
46
|
+
self,
|
47
|
+
agents: List[DjangoAgent],
|
48
|
+
prompt: str,
|
49
|
+
deps: Any,
|
50
|
+
**kwargs
|
51
|
+
) -> List[ExecutionResult]:
|
52
|
+
"""Execute agents sequentially."""
|
53
|
+
results = []
|
54
|
+
current_prompt = prompt
|
55
|
+
|
56
|
+
for i, agent in enumerate(agents):
|
57
|
+
logger.debug(f"Executing agent {i+1}/{len(agents)}: {agent.name}")
|
58
|
+
|
59
|
+
try:
|
60
|
+
result = await agent.run(current_prompt, deps)
|
61
|
+
results.append(result)
|
62
|
+
|
63
|
+
# Pass result to next agent (if not the last one)
|
64
|
+
if i < len(agents) - 1:
|
65
|
+
current_prompt = f"Previous result: {result.output}\nNew task: {prompt}"
|
66
|
+
|
67
|
+
except Exception as e:
|
68
|
+
logger.error(f"Sequential execution failed at agent '{agent.name}': {e}")
|
69
|
+
# Add failed result
|
70
|
+
results.append(ExecutionResult(
|
71
|
+
agent_name=agent.name,
|
72
|
+
output=None,
|
73
|
+
execution_time=0.0,
|
74
|
+
error=str(e)
|
75
|
+
))
|
76
|
+
break # Stop execution on error
|
77
|
+
|
78
|
+
return results
|
79
|
+
|
80
|
+
|
81
|
+
class ParallelPattern(ExecutionPattern):
|
82
|
+
"""Parallel execution pattern - agents run concurrently."""
|
83
|
+
|
84
|
+
def __init__(self):
|
85
|
+
super().__init__(
|
86
|
+
name="parallel",
|
87
|
+
description="Execute agents concurrently with optional concurrency limits"
|
88
|
+
)
|
89
|
+
|
90
|
+
async def execute(
|
91
|
+
self,
|
92
|
+
agents: List[DjangoAgent],
|
93
|
+
prompt: str,
|
94
|
+
deps: Any,
|
95
|
+
**kwargs
|
96
|
+
) -> List[ExecutionResult]:
|
97
|
+
"""Execute agents in parallel."""
|
98
|
+
max_concurrent = kwargs.get('max_concurrent', 5)
|
99
|
+
semaphore = asyncio.Semaphore(max_concurrent)
|
100
|
+
|
101
|
+
async def run_agent_with_semaphore(agent: DjangoAgent) -> ExecutionResult:
|
102
|
+
async with semaphore:
|
103
|
+
try:
|
104
|
+
return await agent.run(prompt, deps)
|
105
|
+
except Exception as e:
|
106
|
+
logger.error(f"Parallel execution failed for agent '{agent.name}': {e}")
|
107
|
+
return ExecutionResult(
|
108
|
+
agent_name=agent.name,
|
109
|
+
output=None,
|
110
|
+
execution_time=0.0,
|
111
|
+
error=str(e)
|
112
|
+
)
|
113
|
+
|
114
|
+
logger.debug(f"Executing {len(agents)} agents in parallel (max_concurrent={max_concurrent})")
|
115
|
+
|
116
|
+
# Execute all agents concurrently
|
117
|
+
tasks = [run_agent_with_semaphore(agent) for agent in agents]
|
118
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
119
|
+
|
120
|
+
# Handle any exceptions that weren't caught
|
121
|
+
final_results = []
|
122
|
+
for i, result in enumerate(results):
|
123
|
+
if isinstance(result, Exception):
|
124
|
+
final_results.append(ExecutionResult(
|
125
|
+
agent_name=agents[i].name,
|
126
|
+
output=None,
|
127
|
+
execution_time=0.0,
|
128
|
+
error=str(result)
|
129
|
+
))
|
130
|
+
else:
|
131
|
+
final_results.append(result)
|
132
|
+
|
133
|
+
return final_results
|
134
|
+
|
135
|
+
|
136
|
+
class ConditionalPattern(ExecutionPattern):
|
137
|
+
"""Conditional execution pattern - execute agents based on conditions."""
|
138
|
+
|
139
|
+
def __init__(self):
|
140
|
+
super().__init__(
|
141
|
+
name="conditional",
|
142
|
+
description="Execute agents based on condition evaluation"
|
143
|
+
)
|
144
|
+
|
145
|
+
async def execute(
|
146
|
+
self,
|
147
|
+
agents: List[DjangoAgent],
|
148
|
+
prompt: str,
|
149
|
+
deps: Any,
|
150
|
+
**kwargs
|
151
|
+
) -> List[ExecutionResult]:
|
152
|
+
"""Execute agents conditionally."""
|
153
|
+
if len(agents) < 2:
|
154
|
+
raise ConfigurationError("Conditional pattern requires at least 2 agents")
|
155
|
+
|
156
|
+
# First agent determines condition
|
157
|
+
condition_agent = agents[0]
|
158
|
+
execution_agents = agents[1:]
|
159
|
+
|
160
|
+
logger.debug(f"Using '{condition_agent.name}' as condition agent")
|
161
|
+
|
162
|
+
# Get condition
|
163
|
+
condition_result = await condition_agent.run(prompt, deps)
|
164
|
+
results = [condition_result]
|
165
|
+
|
166
|
+
# Check if we should proceed
|
167
|
+
should_proceed = self._evaluate_condition(condition_result.output)
|
168
|
+
|
169
|
+
if should_proceed:
|
170
|
+
logger.debug(f"Condition met, executing {len(execution_agents)} agents")
|
171
|
+
|
172
|
+
# Execute remaining agents sequentially
|
173
|
+
sequential = SequentialPattern()
|
174
|
+
execution_results = await sequential.execute(execution_agents, prompt, deps, **kwargs)
|
175
|
+
results.extend(execution_results)
|
176
|
+
else:
|
177
|
+
logger.debug("Condition not met, skipping execution agents")
|
178
|
+
|
179
|
+
return results
|
180
|
+
|
181
|
+
def _evaluate_condition(self, output: Any) -> bool:
|
182
|
+
"""Evaluate condition from agent output."""
|
183
|
+
# Simple condition evaluation
|
184
|
+
if isinstance(output, dict):
|
185
|
+
return output.get('proceed', False) or output.get('success', False)
|
186
|
+
elif isinstance(output, bool):
|
187
|
+
return output
|
188
|
+
elif hasattr(output, 'success'):
|
189
|
+
return output.success
|
190
|
+
else:
|
191
|
+
# Default to True if we can't determine condition
|
192
|
+
return True
|
193
|
+
|
194
|
+
|
195
|
+
class SimpleOrchestrator:
|
196
|
+
"""
|
197
|
+
Main orchestrator for agent coordination.
|
198
|
+
|
199
|
+
Provides simple, clean interface for multi-agent workflows
|
200
|
+
following KISS principles.
|
201
|
+
"""
|
202
|
+
|
203
|
+
def __init__(self):
|
204
|
+
"""Initialize orchestrator."""
|
205
|
+
self.agents: Dict[str, DjangoAgent] = {}
|
206
|
+
self.patterns: Dict[str, ExecutionPattern] = {
|
207
|
+
'sequential': SequentialPattern(),
|
208
|
+
'parallel': ParallelPattern(),
|
209
|
+
'conditional': ConditionalPattern()
|
210
|
+
}
|
211
|
+
|
212
|
+
# Metrics
|
213
|
+
self._total_executions = 0
|
214
|
+
self._total_execution_time = 0.0
|
215
|
+
self._pattern_usage = {}
|
216
|
+
|
217
|
+
logger.info("Initialized SimpleOrchestrator")
|
218
|
+
|
219
|
+
def register_agent(self, agent: DjangoAgent):
|
220
|
+
"""
|
221
|
+
Register agent with orchestrator.
|
222
|
+
|
223
|
+
Args:
|
224
|
+
agent: DjangoAgent instance to register
|
225
|
+
|
226
|
+
Raises:
|
227
|
+
ConfigurationError: If agent name already exists
|
228
|
+
"""
|
229
|
+
if agent.name in self.agents:
|
230
|
+
raise ConfigurationError(f"Agent '{agent.name}' already registered")
|
231
|
+
|
232
|
+
self.agents[agent.name] = agent
|
233
|
+
logger.info(f"Registered agent '{agent.name}'")
|
234
|
+
|
235
|
+
def unregister_agent(self, agent_name: str):
|
236
|
+
"""
|
237
|
+
Unregister agent from orchestrator.
|
238
|
+
|
239
|
+
Args:
|
240
|
+
agent_name: Name of agent to unregister
|
241
|
+
"""
|
242
|
+
if agent_name in self.agents:
|
243
|
+
del self.agents[agent_name]
|
244
|
+
logger.info(f"Unregistered agent '{agent_name}'")
|
245
|
+
|
246
|
+
def get_agent(self, name: str) -> DjangoAgent:
|
247
|
+
"""
|
248
|
+
Get registered agent by name.
|
249
|
+
|
250
|
+
Args:
|
251
|
+
name: Agent name
|
252
|
+
|
253
|
+
Returns:
|
254
|
+
DjangoAgent instance
|
255
|
+
|
256
|
+
Raises:
|
257
|
+
AgentNotFoundError: If agent not found
|
258
|
+
"""
|
259
|
+
if name not in self.agents:
|
260
|
+
raise AgentNotFoundError(name)
|
261
|
+
|
262
|
+
return self.agents[name]
|
263
|
+
|
264
|
+
def list_agents(self) -> List[str]:
|
265
|
+
"""Get list of registered agent names."""
|
266
|
+
return list(self.agents.keys())
|
267
|
+
|
268
|
+
def add_pattern(self, pattern: ExecutionPattern):
|
269
|
+
"""
|
270
|
+
Add custom execution pattern.
|
271
|
+
|
272
|
+
Args:
|
273
|
+
pattern: ExecutionPattern instance
|
274
|
+
"""
|
275
|
+
self.patterns[pattern.name] = pattern
|
276
|
+
logger.info(f"Added execution pattern '{pattern.name}'")
|
277
|
+
|
278
|
+
async def execute(
|
279
|
+
self,
|
280
|
+
pattern: str,
|
281
|
+
agents: List[str],
|
282
|
+
prompt: str,
|
283
|
+
deps: Any,
|
284
|
+
config: Optional[WorkflowConfig] = None,
|
285
|
+
**kwargs
|
286
|
+
) -> List[ExecutionResult]:
|
287
|
+
"""
|
288
|
+
Execute agents using specified pattern.
|
289
|
+
|
290
|
+
Args:
|
291
|
+
pattern: Execution pattern name
|
292
|
+
agents: List of agent names to execute
|
293
|
+
prompt: Input prompt for agents
|
294
|
+
deps: Dependencies for execution
|
295
|
+
config: Optional workflow configuration
|
296
|
+
**kwargs: Pattern-specific options
|
297
|
+
|
298
|
+
Returns:
|
299
|
+
List of execution results
|
300
|
+
|
301
|
+
Raises:
|
302
|
+
AgentNotFoundError: If any agent not found
|
303
|
+
ConfigurationError: If pattern not found
|
304
|
+
ExecutionError: If execution fails
|
305
|
+
"""
|
306
|
+
# Validate pattern
|
307
|
+
if pattern not in self.patterns:
|
308
|
+
available_patterns = list(self.patterns.keys())
|
309
|
+
raise ConfigurationError(
|
310
|
+
f"Unknown pattern '{pattern}'. Available patterns: {available_patterns}"
|
311
|
+
)
|
312
|
+
|
313
|
+
# Validate agents
|
314
|
+
agent_instances = []
|
315
|
+
for agent_name in agents:
|
316
|
+
if agent_name not in self.agents:
|
317
|
+
raise AgentNotFoundError(agent_name)
|
318
|
+
agent_instances.append(self.agents[agent_name])
|
319
|
+
|
320
|
+
# Apply configuration
|
321
|
+
if config:
|
322
|
+
kwargs.setdefault('max_concurrent', config.max_concurrent)
|
323
|
+
kwargs.setdefault('timeout', config.timeout)
|
324
|
+
|
325
|
+
# Execute pattern
|
326
|
+
start_time = time.time()
|
327
|
+
execution_id = f"exec_{int(time.time() * 1000)}"
|
328
|
+
|
329
|
+
logger.info(
|
330
|
+
f"Starting execution {execution_id}: pattern='{pattern}', "
|
331
|
+
f"agents={agents}, prompt='{prompt[:50]}...'"
|
332
|
+
)
|
333
|
+
|
334
|
+
try:
|
335
|
+
pattern_executor = self.patterns[pattern]
|
336
|
+
results = await pattern_executor.execute(agent_instances, prompt, deps, **kwargs)
|
337
|
+
|
338
|
+
execution_time = time.time() - start_time
|
339
|
+
|
340
|
+
# Update metrics
|
341
|
+
self._total_executions += 1
|
342
|
+
self._total_execution_time += execution_time
|
343
|
+
self._pattern_usage[pattern] = self._pattern_usage.get(pattern, 0) + 1
|
344
|
+
|
345
|
+
# Log results
|
346
|
+
successful_agents = sum(1 for r in results if r.success)
|
347
|
+
failed_agents = len(results) - successful_agents
|
348
|
+
|
349
|
+
logger.info(
|
350
|
+
f"Completed execution {execution_id} in {execution_time:.2f}s: "
|
351
|
+
f"{successful_agents} successful, {failed_agents} failed"
|
352
|
+
)
|
353
|
+
|
354
|
+
return results
|
355
|
+
|
356
|
+
except Exception as e:
|
357
|
+
execution_time = time.time() - start_time
|
358
|
+
logger.error(f"Execution {execution_id} failed after {execution_time:.2f}s: {e}")
|
359
|
+
|
360
|
+
raise ExecutionError(
|
361
|
+
f"Execution failed: {str(e)}",
|
362
|
+
original_error=e
|
363
|
+
)
|
364
|
+
|
365
|
+
def get_metrics(self) -> Dict[str, Any]:
|
366
|
+
"""Get orchestrator metrics."""
|
367
|
+
return {
|
368
|
+
'total_executions': self._total_executions,
|
369
|
+
'total_execution_time': self._total_execution_time,
|
370
|
+
'average_execution_time': (
|
371
|
+
self._total_execution_time / self._total_executions
|
372
|
+
if self._total_executions > 0 else 0
|
373
|
+
),
|
374
|
+
'pattern_usage': self._pattern_usage.copy(),
|
375
|
+
'registered_agents': len(self.agents),
|
376
|
+
'available_patterns': list(self.patterns.keys())
|
377
|
+
}
|
378
|
+
|
379
|
+
def reset_metrics(self):
|
380
|
+
"""Reset orchestrator metrics."""
|
381
|
+
self._total_executions = 0
|
382
|
+
self._total_execution_time = 0.0
|
383
|
+
self._pattern_usage = {}
|
384
|
+
|
385
|
+
@property
|
386
|
+
def registered_agents(self) -> Dict[str, DjangoAgent]:
|
387
|
+
"""Get copy of registered agents."""
|
388
|
+
return self.agents.copy()
|
389
|
+
|
390
|
+
def __repr__(self) -> str:
|
391
|
+
return f"SimpleOrchestrator(agents={len(self.agents)}, patterns={len(self.patterns)})"
|
@@ -0,0 +1,161 @@
|
|
1
|
+
"""
|
2
|
+
Simple example demonstrating Django Orchestrator usage.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import asyncio
|
6
|
+
from dataclasses import dataclass
|
7
|
+
from pydantic import BaseModel
|
8
|
+
from django.contrib.auth.models import User
|
9
|
+
|
10
|
+
from django_cfg.modules.django_orchestrator import DjangoAgent, SimpleOrchestrator, DjangoDeps, RunContext
|
11
|
+
|
12
|
+
|
13
|
+
# Define output model
|
14
|
+
class GreetingResult(BaseModel):
|
15
|
+
"""Result from greeting agent."""
|
16
|
+
greeting: str
|
17
|
+
personalized: bool
|
18
|
+
user_info: str
|
19
|
+
|
20
|
+
|
21
|
+
# Create agent
|
22
|
+
greeting_agent = DjangoAgent[DjangoDeps, GreetingResult](
|
23
|
+
name="greeting_agent",
|
24
|
+
deps_type=DjangoDeps,
|
25
|
+
output_type=GreetingResult,
|
26
|
+
instructions="Generate personalized greetings for users"
|
27
|
+
)
|
28
|
+
|
29
|
+
|
30
|
+
# Add tools to agent
|
31
|
+
@greeting_agent.tool
|
32
|
+
async def get_user_info(ctx: RunContext[DjangoDeps]) -> str:
|
33
|
+
"""Get user information for personalization."""
|
34
|
+
user = ctx.deps.user
|
35
|
+
return f"User: {user.username}, Email: {user.email}, Joined: {user.date_joined}"
|
36
|
+
|
37
|
+
|
38
|
+
@greeting_agent.tool
|
39
|
+
async def get_time_of_day(ctx: RunContext[DjangoDeps]) -> str:
|
40
|
+
"""Get current time of day for greeting."""
|
41
|
+
from datetime import datetime
|
42
|
+
hour = datetime.now().hour
|
43
|
+
|
44
|
+
if hour < 12:
|
45
|
+
return "morning"
|
46
|
+
elif hour < 17:
|
47
|
+
return "afternoon"
|
48
|
+
else:
|
49
|
+
return "evening"
|
50
|
+
|
51
|
+
|
52
|
+
async def simple_example():
|
53
|
+
"""Run simple orchestrator example."""
|
54
|
+
print("🤖 Django Orchestrator Simple Example")
|
55
|
+
print("=" * 50)
|
56
|
+
|
57
|
+
# Create test user (in real app, get from request)
|
58
|
+
try:
|
59
|
+
user = await User.objects.aget(username='testuser')
|
60
|
+
except User.DoesNotExist:
|
61
|
+
user = await User.objects.acreate_user(
|
62
|
+
username='testuser',
|
63
|
+
email='test@example.com',
|
64
|
+
first_name='Test',
|
65
|
+
last_name='User'
|
66
|
+
)
|
67
|
+
|
68
|
+
# Create dependencies
|
69
|
+
deps = DjangoDeps(user=user)
|
70
|
+
|
71
|
+
# Create orchestrator
|
72
|
+
orchestrator = SimpleOrchestrator()
|
73
|
+
orchestrator.register_agent(greeting_agent)
|
74
|
+
|
75
|
+
# Execute single agent
|
76
|
+
print("\n1. Single Agent Execution:")
|
77
|
+
results = await orchestrator.execute(
|
78
|
+
pattern="sequential",
|
79
|
+
agents=["greeting_agent"],
|
80
|
+
prompt="Create a personalized greeting",
|
81
|
+
deps=deps
|
82
|
+
)
|
83
|
+
|
84
|
+
result = results[0]
|
85
|
+
print(f"✅ Agent: {result.agent_name}")
|
86
|
+
print(f"✅ Output: {result.output}")
|
87
|
+
print(f"✅ Execution Time: {result.execution_time:.2f}s")
|
88
|
+
print(f"✅ Tokens Used: {result.tokens_used}")
|
89
|
+
|
90
|
+
# Get metrics
|
91
|
+
print("\n2. Agent Metrics:")
|
92
|
+
metrics = greeting_agent.get_metrics()
|
93
|
+
for key, value in metrics.items():
|
94
|
+
print(f"📊 {key}: {value}")
|
95
|
+
|
96
|
+
# Get orchestrator metrics
|
97
|
+
print("\n3. Orchestrator Metrics:")
|
98
|
+
orch_metrics = orchestrator.get_metrics()
|
99
|
+
for key, value in orch_metrics.items():
|
100
|
+
print(f"📈 {key}: {value}")
|
101
|
+
|
102
|
+
|
103
|
+
async def multi_agent_example():
|
104
|
+
"""Run multi-agent orchestrator example."""
|
105
|
+
print("\n🤖 Multi-Agent Example")
|
106
|
+
print("=" * 30)
|
107
|
+
|
108
|
+
# Create additional agents
|
109
|
+
analyzer_agent = DjangoAgent[DjangoDeps, BaseModel](
|
110
|
+
name="content_analyzer",
|
111
|
+
deps_type=DjangoDeps,
|
112
|
+
output_type=BaseModel,
|
113
|
+
instructions="Analyze content sentiment and topics"
|
114
|
+
)
|
115
|
+
|
116
|
+
formatter_agent = DjangoAgent[DjangoDeps, BaseModel](
|
117
|
+
name="content_formatter",
|
118
|
+
deps_type=DjangoDeps,
|
119
|
+
output_type=BaseModel,
|
120
|
+
instructions="Format content for display"
|
121
|
+
)
|
122
|
+
|
123
|
+
# Create orchestrator
|
124
|
+
orchestrator = SimpleOrchestrator()
|
125
|
+
orchestrator.register_agent(analyzer_agent)
|
126
|
+
orchestrator.register_agent(formatter_agent)
|
127
|
+
|
128
|
+
# Get user
|
129
|
+
user = await User.objects.aget(username='testuser')
|
130
|
+
deps = DjangoDeps(user=user)
|
131
|
+
|
132
|
+
# Execute sequential workflow
|
133
|
+
print("\n1. Sequential Workflow:")
|
134
|
+
results = await orchestrator.execute(
|
135
|
+
pattern="sequential",
|
136
|
+
agents=["content_analyzer", "content_formatter"],
|
137
|
+
prompt="Analyze and format this content: Hello world!",
|
138
|
+
deps=deps
|
139
|
+
)
|
140
|
+
|
141
|
+
for i, result in enumerate(results, 1):
|
142
|
+
print(f"Step {i} - {result.agent_name}: {result.success}")
|
143
|
+
|
144
|
+
# Execute parallel workflow
|
145
|
+
print("\n2. Parallel Workflow:")
|
146
|
+
results = await orchestrator.execute(
|
147
|
+
pattern="parallel",
|
148
|
+
agents=["content_analyzer", "content_formatter"],
|
149
|
+
prompt="Process content in parallel",
|
150
|
+
deps=deps,
|
151
|
+
max_concurrent=2
|
152
|
+
)
|
153
|
+
|
154
|
+
successful = sum(1 for r in results if r.success)
|
155
|
+
print(f"✅ {successful}/{len(results)} agents completed successfully")
|
156
|
+
|
157
|
+
|
158
|
+
if __name__ == "__main__":
|
159
|
+
# Run examples
|
160
|
+
asyncio.run(simple_example())
|
161
|
+
asyncio.run(multi_agent_example())
|
@@ -0,0 +1,14 @@
|
|
1
|
+
"""
|
2
|
+
Django integration components for orchestrator.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from .registry import AgentRegistry, initialize_registry
|
6
|
+
from .signals import setup_signals
|
7
|
+
from .middleware import OrchestratorMiddleware
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"AgentRegistry",
|
11
|
+
"initialize_registry",
|
12
|
+
"setup_signals",
|
13
|
+
"OrchestratorMiddleware",
|
14
|
+
]
|
@@ -0,0 +1,80 @@
|
|
1
|
+
"""
|
2
|
+
Django middleware for orchestrator integration.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import time
|
6
|
+
import logging
|
7
|
+
from typing import Callable
|
8
|
+
from django.http import HttpRequest, HttpResponse
|
9
|
+
from django.utils.deprecation import MiddlewareMixin
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
class OrchestratorMiddleware(MiddlewareMixin):
|
15
|
+
"""
|
16
|
+
Middleware for Django Orchestrator integration.
|
17
|
+
|
18
|
+
Provides:
|
19
|
+
- Request context for agents
|
20
|
+
- Performance monitoring
|
21
|
+
- Error tracking
|
22
|
+
- User session integration
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __init__(self, get_response: Callable):
|
26
|
+
"""Initialize middleware."""
|
27
|
+
self.get_response = get_response
|
28
|
+
super().__init__(get_response)
|
29
|
+
|
30
|
+
def process_request(self, request: HttpRequest):
|
31
|
+
"""Process incoming request."""
|
32
|
+
# Add orchestrator context to request
|
33
|
+
request.orchestrator_start_time = time.time()
|
34
|
+
request.orchestrator_context = {
|
35
|
+
'user_agent': request.META.get('HTTP_USER_AGENT', ''),
|
36
|
+
'ip_address': self._get_client_ip(request),
|
37
|
+
'session_key': request.session.session_key if hasattr(request, 'session') else None,
|
38
|
+
}
|
39
|
+
|
40
|
+
logger.debug(f"Orchestrator middleware: Processing request {request.path}")
|
41
|
+
|
42
|
+
def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse:
|
43
|
+
"""Process outgoing response."""
|
44
|
+
if hasattr(request, 'orchestrator_start_time'):
|
45
|
+
duration = time.time() - request.orchestrator_start_time
|
46
|
+
|
47
|
+
# Add performance header
|
48
|
+
response['X-Orchestrator-Duration'] = f"{duration:.3f}s"
|
49
|
+
|
50
|
+
# Log slow requests
|
51
|
+
if duration > 5.0: # 5 seconds threshold
|
52
|
+
logger.warning(
|
53
|
+
f"Slow request detected: {request.path} took {duration:.3f}s"
|
54
|
+
)
|
55
|
+
|
56
|
+
return response
|
57
|
+
|
58
|
+
def process_exception(self, request: HttpRequest, exception: Exception):
|
59
|
+
"""Process exceptions."""
|
60
|
+
if hasattr(request, 'orchestrator_context'):
|
61
|
+
logger.error(
|
62
|
+
f"Exception in orchestrator context: {exception}",
|
63
|
+
extra={
|
64
|
+
'request_path': request.path,
|
65
|
+
'user_id': request.user.id if hasattr(request, 'user') and request.user.is_authenticated else None,
|
66
|
+
'orchestrator_context': request.orchestrator_context,
|
67
|
+
}
|
68
|
+
)
|
69
|
+
|
70
|
+
# Don't handle the exception, just log it
|
71
|
+
return None
|
72
|
+
|
73
|
+
def _get_client_ip(self, request: HttpRequest) -> str:
|
74
|
+
"""Get client IP address."""
|
75
|
+
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
76
|
+
if x_forwarded_for:
|
77
|
+
ip = x_forwarded_for.split(',')[0]
|
78
|
+
else:
|
79
|
+
ip = request.META.get('REMOTE_ADDR')
|
80
|
+
return ip or 'unknown'
|