dacp 0.1.0__py3-none-any.whl → 0.3.1__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.
- dacp/__init__.py +40 -12
- dacp/intelligence.py +378 -0
- dacp/llm.py +28 -15
- dacp/logging_config.py +128 -0
- dacp/main.py +15 -0
- dacp/orchestrator.py +248 -0
- dacp/tools.py +66 -10
- dacp-0.3.1.dist-info/METADATA +464 -0
- dacp-0.3.1.dist-info/RECORD +15 -0
- dacp-0.1.0.dist-info/METADATA +0 -114
- dacp-0.1.0.dist-info/RECORD +0 -11
- {dacp-0.1.0.dist-info → dacp-0.3.1.dist-info}/WHEEL +0 -0
- {dacp-0.1.0.dist-info → dacp-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {dacp-0.1.0.dist-info → dacp-0.3.1.dist-info}/top_level.txt +0 -0
dacp/orchestrator.py
ADDED
@@ -0,0 +1,248 @@
|
|
1
|
+
"""
|
2
|
+
DACP Orchestrator - Manages agent registration and message routing.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
import time
|
7
|
+
from typing import Dict, Any, List, Optional
|
8
|
+
from .tools import run_tool, TOOL_REGISTRY
|
9
|
+
from .protocol import (
|
10
|
+
parse_agent_response,
|
11
|
+
is_tool_request,
|
12
|
+
get_tool_request,
|
13
|
+
wrap_tool_result,
|
14
|
+
is_final_response,
|
15
|
+
get_final_response,
|
16
|
+
)
|
17
|
+
|
18
|
+
# Set up logger for this module
|
19
|
+
logger = logging.getLogger("dacp.orchestrator")
|
20
|
+
|
21
|
+
|
22
|
+
class Agent:
|
23
|
+
"""Base class for DACP agents."""
|
24
|
+
|
25
|
+
def handle_message(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
26
|
+
"""Handle an incoming message. Subclasses should override this method."""
|
27
|
+
raise NotImplementedError("Agents must implement handle_message method")
|
28
|
+
|
29
|
+
|
30
|
+
class Orchestrator:
|
31
|
+
"""
|
32
|
+
Central orchestrator for managing agents and routing messages.
|
33
|
+
"""
|
34
|
+
|
35
|
+
def __init__(self):
|
36
|
+
"""Initialize the orchestrator."""
|
37
|
+
self.agents: Dict[str, Any] = {}
|
38
|
+
self.conversation_history: List[Dict[str, Any]] = []
|
39
|
+
self.session_id = f"session_{int(time.time())}"
|
40
|
+
logger.info(f"🎭 Orchestrator initialized with session ID: {self.session_id}")
|
41
|
+
|
42
|
+
def register_agent(self, agent_id: str, agent: Any) -> None:
|
43
|
+
"""
|
44
|
+
Register an agent with the orchestrator.
|
45
|
+
|
46
|
+
Args:
|
47
|
+
agent_id: Unique identifier for the agent
|
48
|
+
agent: Agent instance that implements handle_message method
|
49
|
+
"""
|
50
|
+
if not hasattr(agent, 'handle_message'):
|
51
|
+
logger.error(f"❌ Agent '{agent_id}' does not implement handle_message method")
|
52
|
+
raise ValueError(f"Agent must implement handle_message method")
|
53
|
+
|
54
|
+
self.agents[agent_id] = agent
|
55
|
+
logger.info(f"✅ Agent '{agent_id}' registered successfully (type: {type(agent).__name__})")
|
56
|
+
logger.debug(f"📊 Total registered agents: {len(self.agents)}")
|
57
|
+
|
58
|
+
def unregister_agent(self, agent_id: str) -> bool:
|
59
|
+
"""
|
60
|
+
Unregister an agent from the orchestrator.
|
61
|
+
|
62
|
+
Args:
|
63
|
+
agent_id: Unique identifier for the agent
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
True if agent was successfully unregistered, False if not found
|
67
|
+
"""
|
68
|
+
if agent_id in self.agents:
|
69
|
+
agent_type = type(self.agents[agent_id]).__name__
|
70
|
+
del self.agents[agent_id]
|
71
|
+
logger.info(f"🗑️ Agent '{agent_id}' unregistered successfully (was type: {agent_type})")
|
72
|
+
logger.debug(f"📊 Remaining registered agents: {len(self.agents)}")
|
73
|
+
return True
|
74
|
+
else:
|
75
|
+
logger.warning(f"⚠️ Attempted to unregister unknown agent: '{agent_id}'")
|
76
|
+
return False
|
77
|
+
|
78
|
+
def send_message(self, agent_id: str, message: Dict[str, Any]) -> Dict[str, Any]:
|
79
|
+
"""
|
80
|
+
Send a message to a specific agent.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
agent_id: Target agent identifier
|
84
|
+
message: Message to send
|
85
|
+
|
86
|
+
Returns:
|
87
|
+
Response from the agent (or error if agent not found)
|
88
|
+
"""
|
89
|
+
logger.info(f"📨 Sending message to agent '{agent_id}'")
|
90
|
+
logger.debug(f"📋 Message content: {message}")
|
91
|
+
|
92
|
+
if agent_id not in self.agents:
|
93
|
+
error_msg = f"Agent '{agent_id}' not found"
|
94
|
+
logger.error(f"❌ {error_msg}")
|
95
|
+
logger.debug(f"📊 Available agents: {list(self.agents.keys())}")
|
96
|
+
return {"error": error_msg}
|
97
|
+
|
98
|
+
agent = self.agents[agent_id]
|
99
|
+
|
100
|
+
try:
|
101
|
+
start_time = time.time()
|
102
|
+
logger.debug(f"🔄 Calling handle_message on agent '{agent_id}'")
|
103
|
+
|
104
|
+
response = agent.handle_message(message)
|
105
|
+
|
106
|
+
processing_time = time.time() - start_time
|
107
|
+
logger.info(f"✅ Agent '{agent_id}' responded in {processing_time:.3f}s")
|
108
|
+
logger.debug(f"📤 Agent response: {response}")
|
109
|
+
|
110
|
+
# Check if agent requested a tool
|
111
|
+
if is_tool_request(response):
|
112
|
+
logger.info(f"🔧 Agent '{agent_id}' requested tool execution")
|
113
|
+
tool_name, tool_args = get_tool_request(response)
|
114
|
+
logger.info(f"🛠️ Executing tool: '{tool_name}' with args: {tool_args}")
|
115
|
+
|
116
|
+
if tool_name in TOOL_REGISTRY:
|
117
|
+
try:
|
118
|
+
tool_start_time = time.time()
|
119
|
+
tool_result = run_tool(tool_name, tool_args)
|
120
|
+
tool_execution_time = time.time() - tool_start_time
|
121
|
+
|
122
|
+
logger.info(f"✅ Tool '{tool_name}' executed successfully in {tool_execution_time:.3f}s")
|
123
|
+
logger.debug(f"🔧 Tool result: {tool_result}")
|
124
|
+
|
125
|
+
wrapped_result = wrap_tool_result(tool_name, tool_result)
|
126
|
+
|
127
|
+
# Log the conversation
|
128
|
+
self._log_conversation(agent_id, message, wrapped_result, tool_used=tool_name)
|
129
|
+
|
130
|
+
return wrapped_result
|
131
|
+
|
132
|
+
except Exception as e:
|
133
|
+
error_msg = f"Tool '{tool_name}' execution failed: {str(e)}"
|
134
|
+
logger.error(f"❌ {error_msg}")
|
135
|
+
error_response = {"error": error_msg}
|
136
|
+
self._log_conversation(agent_id, message, error_response, tool_used=tool_name)
|
137
|
+
return error_response
|
138
|
+
else:
|
139
|
+
error_msg = f"Unknown tool requested: '{tool_name}'"
|
140
|
+
logger.error(f"❌ {error_msg}")
|
141
|
+
logger.debug(f"📊 Available tools: {list(TOOL_REGISTRY.keys())}")
|
142
|
+
error_response = {"error": error_msg}
|
143
|
+
self._log_conversation(agent_id, message, error_response)
|
144
|
+
return error_response
|
145
|
+
|
146
|
+
# Log successful conversation
|
147
|
+
self._log_conversation(agent_id, message, response)
|
148
|
+
return response
|
149
|
+
|
150
|
+
except Exception as e:
|
151
|
+
error_msg = f"Agent '{agent_id}' error: {str(e)}"
|
152
|
+
logger.error(f"❌ {error_msg}")
|
153
|
+
logger.debug(f"🐛 Exception details: {type(e).__name__}: {e}")
|
154
|
+
error_response = {"error": error_msg}
|
155
|
+
self._log_conversation(agent_id, message, error_response)
|
156
|
+
return error_response
|
157
|
+
|
158
|
+
def broadcast_message(self, message: Dict[str, Any], exclude_agents: Optional[List[str]] = None) -> Dict[str, Dict[str, Any]]:
|
159
|
+
"""
|
160
|
+
Send a message to all registered agents (optionally excluding some).
|
161
|
+
|
162
|
+
Args:
|
163
|
+
message: Message to broadcast
|
164
|
+
exclude_agents: List of agent IDs to exclude from broadcast
|
165
|
+
|
166
|
+
Returns:
|
167
|
+
Dict mapping agent IDs to their responses
|
168
|
+
"""
|
169
|
+
exclude_agents = exclude_agents or []
|
170
|
+
target_agents = [aid for aid in self.agents.keys() if aid not in exclude_agents]
|
171
|
+
|
172
|
+
logger.info(f"📡 Broadcasting message to {len(target_agents)} agents")
|
173
|
+
logger.debug(f"🎯 Target agents: {target_agents}")
|
174
|
+
if exclude_agents:
|
175
|
+
logger.debug(f"🚫 Excluded agents: {exclude_agents}")
|
176
|
+
|
177
|
+
responses = {}
|
178
|
+
start_time = time.time()
|
179
|
+
|
180
|
+
for agent_id in target_agents:
|
181
|
+
logger.debug(f"📨 Broadcasting to agent '{agent_id}'")
|
182
|
+
responses[agent_id] = self.send_message(agent_id, message)
|
183
|
+
|
184
|
+
broadcast_time = time.time() - start_time
|
185
|
+
logger.info(f"✅ Broadcast completed in {broadcast_time:.3f}s")
|
186
|
+
|
187
|
+
return responses
|
188
|
+
|
189
|
+
def get_conversation_history(self, agent_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
190
|
+
"""
|
191
|
+
Get conversation history, optionally filtered by agent.
|
192
|
+
|
193
|
+
Args:
|
194
|
+
agent_id: Optional agent ID to filter by
|
195
|
+
|
196
|
+
Returns:
|
197
|
+
List of conversation entries
|
198
|
+
"""
|
199
|
+
if agent_id is None:
|
200
|
+
logger.debug(f"📚 Retrieving full conversation history ({len(self.conversation_history)} entries)")
|
201
|
+
return self.conversation_history.copy()
|
202
|
+
else:
|
203
|
+
filtered_history = [
|
204
|
+
entry for entry in self.conversation_history
|
205
|
+
if entry.get("agent_id") == agent_id
|
206
|
+
]
|
207
|
+
logger.debug(f"📚 Retrieving conversation history for '{agent_id}' ({len(filtered_history)} entries)")
|
208
|
+
return filtered_history
|
209
|
+
|
210
|
+
def clear_history(self) -> None:
|
211
|
+
"""Clear the conversation history."""
|
212
|
+
old_count = len(self.conversation_history)
|
213
|
+
self.conversation_history.clear()
|
214
|
+
logger.info(f"🗑️ Conversation history cleared ({old_count} entries removed)")
|
215
|
+
|
216
|
+
def get_session_info(self) -> Dict[str, Any]:
|
217
|
+
"""
|
218
|
+
Get current session information.
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
Dict containing session metadata
|
222
|
+
"""
|
223
|
+
info = {
|
224
|
+
"session_id": self.session_id,
|
225
|
+
"registered_agents": list(self.agents.keys()),
|
226
|
+
"conversation_count": len(self.conversation_history),
|
227
|
+
"available_tools": list(TOOL_REGISTRY.keys())
|
228
|
+
}
|
229
|
+
logger.debug(f"📊 Session info requested: {info}")
|
230
|
+
return info
|
231
|
+
|
232
|
+
def _log_conversation(self, agent_id: str, message: Dict[str, Any], response: Dict[str, Any], tool_used: Optional[str] = None) -> None:
|
233
|
+
"""Log a conversation entry."""
|
234
|
+
entry = {
|
235
|
+
"timestamp": time.time(),
|
236
|
+
"agent_id": agent_id,
|
237
|
+
"message": message,
|
238
|
+
"response": response,
|
239
|
+
"session_id": self.session_id
|
240
|
+
}
|
241
|
+
|
242
|
+
if tool_used:
|
243
|
+
entry["tool_used"] = tool_used
|
244
|
+
logger.debug(f"💾 Logging conversation with tool usage: {tool_used}")
|
245
|
+
else:
|
246
|
+
logger.debug(f"💾 Logging conversation entry")
|
247
|
+
|
248
|
+
self.conversation_history.append(entry)
|
dacp/tools.py
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
+
import logging
|
1
2
|
from typing import Dict, Any, Callable
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
# Set up logger for this module
|
6
|
+
logger = logging.getLogger("dacp.tools")
|
2
7
|
|
3
8
|
TOOL_REGISTRY: Dict[str, Callable[..., Dict[str, Any]]] = {}
|
4
9
|
|
@@ -6,25 +11,76 @@ TOOL_REGISTRY: Dict[str, Callable[..., Dict[str, Any]]] = {}
|
|
6
11
|
def register_tool(tool_id: str, func: Callable[..., Dict[str, Any]]) -> None:
|
7
12
|
"""Register a tool function."""
|
8
13
|
TOOL_REGISTRY[tool_id] = func
|
14
|
+
logger.info(f"🔧 Tool '{tool_id}' registered successfully (function: {func.__name__})")
|
15
|
+
logger.debug(f"📊 Total registered tools: {len(TOOL_REGISTRY)}")
|
9
16
|
|
10
17
|
|
11
18
|
def run_tool(tool_id: str, args: Dict[str, Any]) -> Dict[str, Any]:
|
12
19
|
"""Run a registered tool with the given arguments."""
|
13
20
|
if tool_id not in TOOL_REGISTRY:
|
21
|
+
logger.error(f"❌ Unknown tool requested: '{tool_id}'")
|
22
|
+
logger.debug(f"📊 Available tools: {list(TOOL_REGISTRY.keys())}")
|
14
23
|
raise ValueError(f"Unknown tool: {tool_id}")
|
24
|
+
|
15
25
|
tool_func = TOOL_REGISTRY[tool_id]
|
16
|
-
|
26
|
+
logger.debug(f"🛠️ Executing tool '{tool_id}' with args: {args}")
|
27
|
+
|
28
|
+
import time
|
29
|
+
start_time = time.time()
|
30
|
+
|
31
|
+
try:
|
32
|
+
result = tool_func(**args)
|
33
|
+
execution_time = time.time() - start_time
|
34
|
+
logger.info(f"✅ Tool '{tool_id}' executed successfully in {execution_time:.3f}s")
|
35
|
+
logger.debug(f"🔧 Tool result: {result}")
|
36
|
+
return result
|
37
|
+
except Exception as e:
|
38
|
+
execution_time = time.time() - start_time
|
39
|
+
logger.error(f"❌ Tool '{tool_id}' failed after {execution_time:.3f}s: {type(e).__name__}: {e}")
|
40
|
+
raise
|
41
|
+
|
42
|
+
|
43
|
+
def file_writer(path: str, content: str) -> Dict[str, Any]:
|
44
|
+
"""
|
45
|
+
Write content to a file, creating parent directories if they don't exist.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
path: File path to write to
|
49
|
+
content: Content to write to the file
|
50
|
+
|
51
|
+
Returns:
|
52
|
+
Dict with success status and file path
|
53
|
+
"""
|
54
|
+
logger.debug(f"📝 Writing to file: {path} ({len(content)} characters)")
|
55
|
+
|
56
|
+
try:
|
57
|
+
# Create parent directories if they don't exist
|
58
|
+
parent_dir = Path(path).parent
|
59
|
+
if not parent_dir.exists():
|
60
|
+
logger.debug(f"📁 Creating parent directories: {parent_dir}")
|
61
|
+
parent_dir.mkdir(parents=True, exist_ok=True)
|
17
62
|
|
63
|
+
# Write the content to the file
|
64
|
+
with open(path, "w", encoding="utf-8") as f:
|
65
|
+
f.write(content)
|
18
66
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
f
|
27
|
-
|
67
|
+
logger.info(f"✅ File written successfully: {path}")
|
68
|
+
return {
|
69
|
+
"success": True,
|
70
|
+
"path": path,
|
71
|
+
"message": f"Successfully wrote {len(content)} characters to {path}",
|
72
|
+
}
|
73
|
+
except Exception as e:
|
74
|
+
logger.error(f"❌ Failed to write file {path}: {type(e).__name__}: {e}")
|
75
|
+
return {
|
76
|
+
"success": False,
|
77
|
+
"path": path,
|
78
|
+
"error": str(e),
|
79
|
+
"message": f"Failed to write to {path}: {e}",
|
80
|
+
}
|
28
81
|
|
29
82
|
|
83
|
+
# Register the built-in file_writer tool
|
84
|
+
logger.debug("🏗️ Registering built-in tools...")
|
30
85
|
register_tool("file_writer", file_writer)
|
86
|
+
logger.debug("✅ Built-in tools registration complete")
|