dacp 0.3.1__py3-none-any.whl → 0.3.2__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/llm.py CHANGED
@@ -9,13 +9,13 @@ from .intelligence import invoke_intelligence
9
9
 
10
10
  def call_llm(prompt: str, model: str = "gpt-4") -> str:
11
11
  """
12
- Legacy function for calling LLMs.
12
+ Legacy function for calling LLMs.
13
13
  Maintained for backward compatibility.
14
-
14
+
15
15
  Args:
16
16
  prompt: The input prompt
17
17
  model: The model to use (defaults to gpt-4)
18
-
18
+
19
19
  Returns:
20
20
  Response from the LLM
21
21
  """
@@ -26,7 +26,14 @@ def call_llm(prompt: str, model: str = "gpt-4") -> str:
26
26
  "api_key": os.getenv("OPENAI_API_KEY"),
27
27
  "endpoint": "https://api.openai.com/v1",
28
28
  "temperature": 0.7,
29
- "max_tokens": 150
29
+ "max_tokens": 150,
30
30
  }
31
-
32
- return invoke_intelligence(prompt, config)
31
+
32
+ result = invoke_intelligence(prompt, config)
33
+
34
+ # Ensure we return a string for backward compatibility
35
+ if isinstance(result, str):
36
+ return result
37
+ else:
38
+ # If it's a dict (error response), convert to string
39
+ return str(result.get("error", "Unknown error occurred"))
dacp/logging_config.py CHANGED
@@ -13,11 +13,11 @@ def setup_dacp_logging(
13
13
  level: str = "INFO",
14
14
  format_style: str = "detailed",
15
15
  include_timestamp: bool = True,
16
- log_file: Optional[str] = None
16
+ log_file: Optional[str] = None,
17
17
  ) -> None:
18
18
  """
19
19
  Set up logging for DACP components.
20
-
20
+
21
21
  Args:
22
22
  level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
23
23
  format_style: Log format style ('simple', 'detailed', 'emoji')
@@ -32,7 +32,9 @@ def setup_dacp_logging(
32
32
  log_format = "%(name)s - %(levelname)s - %(message)s"
33
33
  elif format_style == "detailed":
34
34
  if include_timestamp:
35
- log_format = "%(asctime)s - %(name)s:%(lineno)d - %(levelname)s - %(message)s"
35
+ log_format = (
36
+ "%(asctime)s - %(name)s:%(lineno)d - %(levelname)s - %(message)s"
37
+ )
36
38
  else:
37
39
  log_format = "%(name)s:%(lineno)d - %(levelname)s - %(message)s"
38
40
  elif format_style == "emoji":
@@ -43,42 +45,41 @@ def setup_dacp_logging(
43
45
  log_format = "%(message)s"
44
46
  else:
45
47
  raise ValueError(f"Unknown format_style: {format_style}")
46
-
48
+
47
49
  # Configure root logger for DACP components
48
50
  logger = logging.getLogger("dacp")
49
51
  logger.setLevel(getattr(logging, level.upper()))
50
-
52
+
51
53
  # Remove existing handlers to avoid duplicates
52
54
  for handler in logger.handlers[:]:
53
55
  logger.removeHandler(handler)
54
-
56
+
55
57
  # Create formatter
56
58
  formatter = logging.Formatter(
57
- log_format,
58
- datefmt="%Y-%m-%d %H:%M:%S" if include_timestamp else None
59
+ log_format, datefmt="%Y-%m-%d %H:%M:%S" if include_timestamp else None
59
60
  )
60
-
61
+
61
62
  # Console handler
62
63
  console_handler = logging.StreamHandler(sys.stdout)
63
64
  console_handler.setFormatter(formatter)
64
65
  logger.addHandler(console_handler)
65
-
66
+
66
67
  # Optional file handler
67
68
  if log_file:
68
69
  file_handler = logging.FileHandler(log_file)
69
70
  file_handler.setFormatter(formatter)
70
71
  logger.addHandler(file_handler)
71
-
72
+
72
73
  # Prevent propagation to root logger to avoid duplicate messages
73
74
  logger.propagate = False
74
-
75
+
75
76
  logger.info(f"🚀 DACP logging configured: level={level}, style={format_style}")
76
77
 
77
78
 
78
79
  def set_dacp_log_level(level: str) -> None:
79
80
  """
80
81
  Set the log level for all DACP components.
81
-
82
+
82
83
  Args:
83
84
  level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
84
85
  """
@@ -102,10 +103,10 @@ def enable_dacp_logging() -> None:
102
103
  def get_dacp_logger(name: str) -> logging.Logger:
103
104
  """
104
105
  Get a logger for a DACP component.
105
-
106
+
106
107
  Args:
107
108
  name: Logger name (usually __name__)
108
-
109
+
109
110
  Returns:
110
111
  Configured logger
111
112
  """
@@ -125,4 +126,4 @@ def enable_info_logging(log_file: Optional[str] = None) -> None:
125
126
 
126
127
  def enable_quiet_logging() -> None:
127
128
  """Enable only error and critical logging."""
128
- setup_dacp_logging(level="ERROR", format_style="simple", include_timestamp=False)
129
+ setup_dacp_logging(level="ERROR", format_style="simple", include_timestamp=False)
dacp/main.py CHANGED
@@ -1,15 +1,9 @@
1
- from dacp.orchestrator import Orchestrator
1
+ #!/usr/bin/env python3
2
+ """
3
+ DACP Main Entry Point
2
4
 
3
- def main():
4
- orchestrator = Orchestrator()
5
+ This module provides examples and testing functionality for DACP.
6
+ """
5
7
 
6
- # Agent registers itself with the orchestrator
7
- hello_agent = HelloWorldAgent("hello_agent", orchestrator)
8
-
9
- # Orchestrator sends a message to the agent and prints the response
10
- input_message = {"name": "Alice"}
11
- response = orchestrator.call_agent("hello_agent", input_message)
12
- print("Orchestrator received:", response)
13
-
14
- if __name__ == "__main__":
15
- main()
8
+ print("DACP - Declarative Agent Communication Protocol")
9
+ print("For examples, see the examples/ directory")
dacp/orchestrator.py CHANGED
@@ -1,248 +1,284 @@
1
1
  """
2
- DACP Orchestrator - Manages agent registration and message routing.
2
+ DACP Orchestrator - Agent management and message routing.
3
+
4
+ This module provides the core orchestrator functionality for managing agents
5
+ and routing messages between them.
3
6
  """
4
7
 
5
8
  import logging
6
9
  import time
7
10
  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
11
+
12
+ from .tools import execute_tool
13
+
19
14
  logger = logging.getLogger("dacp.orchestrator")
20
15
 
21
16
 
22
17
  class Agent:
23
- """Base class for DACP agents."""
24
-
18
+ """
19
+ Base agent class that all DACP agents should inherit from.
20
+
21
+ This provides the standard interface for agent communication.
22
+ """
23
+
25
24
  def handle_message(self, message: Dict[str, Any]) -> Dict[str, Any]:
26
- """Handle an incoming message. Subclasses should override this method."""
25
+ """
26
+ Handle incoming messages.
27
+
28
+ Args:
29
+ message: Message dictionary containing task and parameters
30
+
31
+ Returns:
32
+ Response dictionary with either 'response', 'tool_request', or 'error'
33
+ """
27
34
  raise NotImplementedError("Agents must implement handle_message method")
28
35
 
29
36
 
30
37
  class Orchestrator:
31
38
  """
32
39
  Central orchestrator for managing agents and routing messages.
40
+
41
+ The orchestrator handles agent registration, message routing, tool execution,
42
+ and conversation history management.
33
43
  """
34
-
35
- def __init__(self):
36
- """Initialize the orchestrator."""
37
- self.agents: Dict[str, Any] = {}
44
+
45
+ def __init__(self, session_id: Optional[str] = None):
46
+ """Initialize orchestrator with optional session ID."""
47
+ self.agents: Dict[str, Agent] = {}
48
+ self.session_id = session_id or f"session_{int(time.time())}"
38
49
  self.conversation_history: List[Dict[str, Any]] = []
39
- self.session_id = f"session_{int(time.time())}"
50
+
40
51
  logger.info(f"🎭 Orchestrator initialized with session ID: {self.session_id}")
41
-
42
- def register_agent(self, agent_id: str, agent: Any) -> None:
52
+
53
+ def register_agent(self, name: str, agent: Agent) -> None:
43
54
  """
44
55
  Register an agent with the orchestrator.
45
-
56
+
46
57
  Args:
47
- agent_id: Unique identifier for the agent
48
- agent: Agent instance that implements handle_message method
58
+ name: Unique name for the agent
59
+ agent: Agent instance implementing the Agent interface
49
60
  """
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__})")
61
+ if not isinstance(agent, Agent):
62
+ raise ValueError("Agent must inherit from dacp.Agent base class")
63
+
64
+ self.agents[name] = agent
65
+ logger.info(
66
+ f"✅ Agent '{name}' registered successfully "
67
+ f"(type: {type(agent).__name__})"
68
+ )
56
69
  logger.debug(f"📊 Total registered agents: {len(self.agents)}")
57
-
58
- def unregister_agent(self, agent_id: str) -> bool:
70
+
71
+ def unregister_agent(self, name: str) -> bool:
59
72
  """
60
73
  Unregister an agent from the orchestrator.
61
-
74
+
62
75
  Args:
63
- agent_id: Unique identifier for the agent
64
-
76
+ name: Name of the agent to unregister
77
+
65
78
  Returns:
66
- True if agent was successfully unregistered, False if not found
79
+ True if agent was unregistered, False if not found
67
80
  """
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)}")
81
+ if name in self.agents:
82
+ del self.agents[name]
83
+ logger.info(f"🗑️ Agent '{name}' unregistered successfully")
84
+ logger.debug(f"📊 Remaining agents: {len(self.agents)}")
73
85
  return True
74
86
  else:
75
- logger.warning(f"⚠️ Attempted to unregister unknown agent: '{agent_id}'")
87
+ logger.warning(f"⚠️ Agent '{name}' not found for unregistration")
76
88
  return False
77
-
78
- def send_message(self, agent_id: str, message: Dict[str, Any]) -> Dict[str, Any]:
89
+
90
+ def list_agents(self) -> List[str]:
91
+ """Get list of registered agent names."""
92
+ return list(self.agents.keys())
93
+
94
+ def send_message(self, agent_name: str, message: Dict[str, Any]) -> Dict[str, Any]:
79
95
  """
80
96
  Send a message to a specific agent.
81
-
97
+
82
98
  Args:
83
- agent_id: Target agent identifier
84
- message: Message to send
85
-
99
+ agent_name: Name of the target agent
100
+ message: Message dictionary to send
101
+
86
102
  Returns:
87
- Response from the agent (or error if agent not found)
103
+ Response from the agent after processing
88
104
  """
89
- logger.info(f"📨 Sending message to agent '{agent_id}'")
105
+ start_time = time.time()
106
+
107
+ logger.info(f"📨 Sending message to agent '{agent_name}'")
90
108
  logger.debug(f"📋 Message content: {message}")
91
-
92
- if agent_id not in self.agents:
93
- error_msg = f"Agent '{agent_id}' not found"
109
+
110
+ if agent_name not in self.agents:
111
+ error_msg = f"Agent '{agent_name}' not found"
94
112
  logger.error(f"❌ {error_msg}")
95
- logger.debug(f"📊 Available agents: {list(self.agents.keys())}")
96
113
  return {"error": error_msg}
97
-
98
- agent = self.agents[agent_id]
99
-
114
+
115
+ agent = self.agents[agent_name]
116
+
100
117
  try:
101
- start_time = time.time()
102
- logger.debug(f"🔄 Calling handle_message on agent '{agent_id}'")
103
-
118
+ logger.debug(f"🔄 Calling handle_message on agent '{agent_name}'")
119
+
120
+ # Call the agent's message handler
104
121
  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")
122
+
123
+ duration = time.time() - start_time
124
+ logger.info(f"✅ Agent '{agent_name}' responded in {duration:.3f}s")
108
125
  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)
126
+
127
+ # Check if agent requested tool execution
128
+ if isinstance(response, dict) and "tool_request" in response:
129
+ logger.info(f"🔧 Agent '{agent_name}' requested tool execution")
130
+ response = self._handle_tool_request(
131
+ agent_name, response["tool_request"]
132
+ )
133
+
134
+ # Log the conversation
135
+ self._log_conversation(agent_name, message, response, duration)
136
+
148
137
  return response
149
-
138
+
150
139
  except Exception as e:
151
- error_msg = f"Agent '{agent_id}' error: {str(e)}"
140
+ duration = time.time() - start_time
141
+ error_msg = f"Error in agent '{agent_name}': {type(e).__name__}: {e}"
152
142
  logger.error(f"❌ {error_msg}")
153
- logger.debug(f"🐛 Exception details: {type(e).__name__}: {e}")
143
+ logger.debug("💥 Exception details", exc_info=True)
144
+
154
145
  error_response = {"error": error_msg}
155
- self._log_conversation(agent_id, message, error_response)
146
+ self._log_conversation(agent_name, message, error_response, duration)
147
+
156
148
  return error_response
157
-
158
- def broadcast_message(self, message: Dict[str, Any], exclude_agents: Optional[List[str]] = None) -> Dict[str, Dict[str, Any]]:
149
+
150
+ def broadcast_message(self, message: Dict[str, Any]) -> Dict[str, Any]:
159
151
  """
160
- Send a message to all registered agents (optionally excluding some).
161
-
152
+ Send a message to all registered agents.
153
+
162
154
  Args:
163
- message: Message to broadcast
164
- exclude_agents: List of agent IDs to exclude from broadcast
165
-
155
+ message: Message dictionary to broadcast
156
+
166
157
  Returns:
167
- Dict mapping agent IDs to their responses
158
+ Dictionary mapping agent names to their responses
168
159
  """
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
-
160
+ logger.info(f"📢 Broadcasting message to {len(self.agents)} agents")
161
+ logger.debug(f"📋 Broadcast message: {message}")
162
+
177
163
  responses = {}
178
164
  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
-
165
+
166
+ for agent_name in self.agents:
167
+ logger.debug(f"📨 Broadcasting to agent '{agent_name}'")
168
+ responses[agent_name] = self.send_message(agent_name, message)
169
+
170
+ duration = time.time() - start_time
171
+ logger.info(
172
+ f"✅ Broadcast completed in {duration:.3f}s "
173
+ f"({len(responses)} responses)"
174
+ )
175
+
187
176
  return responses
188
-
189
- def get_conversation_history(self, agent_id: Optional[str] = None) -> List[Dict[str, Any]]:
177
+
178
+ def _handle_tool_request(
179
+ self, agent_name: str, tool_request: Dict[str, Any]
180
+ ) -> Dict[str, Any]:
190
181
  """
191
- Get conversation history, optionally filtered by agent.
192
-
182
+ Handle tool execution request from an agent.
183
+
193
184
  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
-
185
+ agent_name: Name of the requesting agent
186
+ tool_request: Tool request dictionary with 'name' and 'args'
187
+
220
188
  Returns:
221
- Dict containing session metadata
189
+ Tool execution result
222
190
  """
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."""
191
+ tool_name = tool_request.get("name")
192
+ tool_args = tool_request.get("args", {})
193
+
194
+ if not tool_name:
195
+ return {"error": "Tool name is required"}
196
+
197
+ logger.info(f"🛠️ Executing tool: '{tool_name}' with args: {tool_args}")
198
+
199
+ start_time = time.time()
200
+
201
+ try:
202
+ result = execute_tool(tool_name, tool_args)
203
+ duration = time.time() - start_time
204
+
205
+ logger.info(
206
+ f"✅ Tool '{tool_name}' executed successfully in {duration:.3f}s"
207
+ )
208
+ logger.debug(f"🔧 Tool result: {result}")
209
+
210
+ return {"tool_result": {"name": tool_name, "result": result}}
211
+
212
+ except Exception as e:
213
+ duration = time.time() - start_time
214
+ error_msg = f"Tool '{tool_name}' failed: {type(e).__name__}: {e}"
215
+ logger.error(f"❌ {error_msg}")
216
+
217
+ return {"error": error_msg}
218
+
219
+ def _log_conversation(
220
+ self,
221
+ agent_name: str,
222
+ message: Dict[str, Any],
223
+ response: Dict[str, Any],
224
+ duration: float,
225
+ ) -> None:
226
+ """Log conversation entry to history."""
227
+ logger.debug("💾 Logging conversation entry")
228
+
234
229
  entry = {
235
230
  "timestamp": time.time(),
236
- "agent_id": agent_id,
231
+ "session_id": self.session_id,
232
+ "agent_name": agent_name,
237
233
  "message": message,
238
234
  "response": response,
239
- "session_id": self.session_id
235
+ "duration": duration,
240
236
  }
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
-
237
+
248
238
  self.conversation_history.append(entry)
239
+
240
+ # Keep history manageable (last 1000 entries)
241
+ if len(self.conversation_history) > 1000:
242
+ self.conversation_history = self.conversation_history[-1000:]
243
+ logger.debug("🗂️ Conversation history trimmed to 1000 entries")
244
+
245
+ def get_conversation_history(
246
+ self, limit: Optional[int] = None
247
+ ) -> List[Dict[str, Any]]:
248
+ """
249
+ Get conversation history.
250
+
251
+ Args:
252
+ limit: Maximum number of entries to return (None for all)
253
+
254
+ Returns:
255
+ List of conversation entries
256
+ """
257
+ if limit is None:
258
+ return self.conversation_history.copy()
259
+ else:
260
+ return self.conversation_history[-limit:].copy()
261
+
262
+ def clear_conversation_history(self) -> None:
263
+ """Clear conversation history."""
264
+ self.conversation_history.clear()
265
+ logger.info("🗑️ Conversation history cleared")
266
+
267
+ def get_session_metadata(self) -> Dict[str, Any]:
268
+ """Get session metadata and statistics."""
269
+ return {
270
+ "session_id": self.session_id,
271
+ "registered_agents": len(self.agents),
272
+ "agent_names": list(self.agents.keys()),
273
+ "conversation_entries": len(self.conversation_history),
274
+ "start_time": (
275
+ self.conversation_history[0]["timestamp"]
276
+ if self.conversation_history
277
+ else None
278
+ ),
279
+ "last_activity": (
280
+ self.conversation_history[-1]["timestamp"]
281
+ if self.conversation_history
282
+ else None
283
+ ),
284
+ }