dacp 0.3.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 +22 -1
- dacp/intelligence.py +128 -22
- dacp/logging_config.py +128 -0
- dacp/orchestrator.py +146 -158
- dacp/tools.py +33 -2
- {dacp-0.3.0.dist-info → dacp-0.3.1.dist-info}/METADATA +96 -1
- dacp-0.3.1.dist-info/RECORD +15 -0
- dacp-0.3.0.dist-info/RECORD +0 -14
- {dacp-0.3.0.dist-info → dacp-0.3.1.dist-info}/WHEEL +0 -0
- {dacp-0.3.0.dist-info → dacp-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {dacp-0.3.0.dist-info → dacp-0.3.1.dist-info}/top_level.txt +0 -0
dacp/__init__.py
CHANGED
@@ -17,22 +17,43 @@ from .tools import (
|
|
17
17
|
from .llm import call_llm
|
18
18
|
from .intelligence import invoke_intelligence
|
19
19
|
from .orchestrator import Orchestrator, Agent
|
20
|
+
from .logging_config import (
|
21
|
+
setup_dacp_logging,
|
22
|
+
enable_debug_logging,
|
23
|
+
enable_info_logging,
|
24
|
+
enable_quiet_logging,
|
25
|
+
set_dacp_log_level,
|
26
|
+
disable_dacp_logging,
|
27
|
+
enable_dacp_logging,
|
28
|
+
)
|
20
29
|
|
21
30
|
__version__ = "0.3.0"
|
22
31
|
|
23
32
|
__all__ = [
|
33
|
+
# Protocol functions
|
24
34
|
"parse_agent_response",
|
25
|
-
"is_tool_request",
|
35
|
+
"is_tool_request",
|
26
36
|
"get_tool_request",
|
27
37
|
"wrap_tool_result",
|
28
38
|
"is_final_response",
|
29
39
|
"get_final_response",
|
40
|
+
# Tool functions
|
30
41
|
"register_tool",
|
31
42
|
"run_tool",
|
32
43
|
"TOOL_REGISTRY",
|
33
44
|
"file_writer",
|
45
|
+
# LLM functions
|
34
46
|
"call_llm",
|
35
47
|
"invoke_intelligence",
|
48
|
+
# Agent orchestration
|
36
49
|
"Orchestrator",
|
37
50
|
"Agent",
|
51
|
+
# Logging configuration
|
52
|
+
"setup_dacp_logging",
|
53
|
+
"enable_debug_logging",
|
54
|
+
"enable_info_logging",
|
55
|
+
"enable_quiet_logging",
|
56
|
+
"set_dacp_log_level",
|
57
|
+
"disable_dacp_logging",
|
58
|
+
"enable_dacp_logging",
|
38
59
|
]
|
dacp/intelligence.py
CHANGED
@@ -6,7 +6,8 @@ import os
|
|
6
6
|
import logging
|
7
7
|
from typing import Dict, Any, Optional
|
8
8
|
|
9
|
-
|
9
|
+
# Set up logger for this module
|
10
|
+
logger = logging.getLogger("dacp.intelligence")
|
10
11
|
|
11
12
|
|
12
13
|
class IntelligenceError(Exception):
|
@@ -42,27 +43,58 @@ def invoke_intelligence(prompt: str, config: Dict[str, Any]) -> str:
|
|
42
43
|
"""
|
43
44
|
engine = config.get("engine")
|
44
45
|
if not engine:
|
46
|
+
logger.error("❌ Missing 'engine' in intelligence configuration")
|
45
47
|
raise ConfigurationError("Missing 'engine' in intelligence configuration")
|
46
48
|
|
47
49
|
engine = engine.lower()
|
50
|
+
model = config.get("model", "default")
|
48
51
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
52
|
+
logger.info(f"🧠 Invoking intelligence: engine='{engine}', model='{model}'")
|
53
|
+
logger.debug(f"📋 Prompt length: {len(prompt)} characters")
|
54
|
+
logger.debug(f"⚙️ Full config: {_sanitize_config_for_logging(config)}")
|
55
|
+
|
56
|
+
import time
|
57
|
+
start_time = time.time()
|
58
|
+
|
59
|
+
try:
|
60
|
+
if engine == "openai":
|
61
|
+
result = _invoke_openai(prompt, config)
|
62
|
+
elif engine == "anthropic":
|
63
|
+
result = _invoke_anthropic(prompt, config)
|
64
|
+
elif engine == "azure":
|
65
|
+
result = _invoke_azure_openai(prompt, config)
|
66
|
+
elif engine == "local":
|
67
|
+
result = _invoke_local(prompt, config)
|
68
|
+
else:
|
69
|
+
logger.error(f"❌ Unsupported intelligence engine: {engine}")
|
70
|
+
raise UnsupportedProviderError(f"Unsupported intelligence engine: {engine}")
|
71
|
+
|
72
|
+
execution_time = time.time() - start_time
|
73
|
+
logger.info(f"✅ Intelligence response received in {execution_time:.3f}s (length: {len(result)} chars)")
|
74
|
+
logger.debug(f"📤 Response preview: {result[:100]}{'...' if len(result) > 100 else ''}")
|
75
|
+
|
76
|
+
return result
|
77
|
+
|
78
|
+
except (IntelligenceError, UnsupportedProviderError, ConfigurationError):
|
79
|
+
# Re-raise our own exceptions without modification
|
80
|
+
execution_time = time.time() - start_time
|
81
|
+
logger.error(f"❌ Intelligence call failed after {execution_time:.3f}s")
|
82
|
+
raise
|
83
|
+
except Exception as e:
|
84
|
+
execution_time = time.time() - start_time
|
85
|
+
logger.error(f"❌ Unexpected intelligence error after {execution_time:.3f}s: {type(e).__name__}: {e}")
|
86
|
+
raise IntelligenceError(f"Unexpected error: {e}")
|
59
87
|
|
60
88
|
|
61
89
|
def _invoke_openai(prompt: str, config: Dict[str, Any]) -> str:
|
62
90
|
"""Invoke OpenAI provider."""
|
91
|
+
logger.debug("🔵 Initializing OpenAI provider")
|
92
|
+
|
63
93
|
try:
|
64
94
|
import openai
|
95
|
+
logger.debug("✅ OpenAI package imported successfully")
|
65
96
|
except ImportError:
|
97
|
+
logger.error("❌ OpenAI package not installed")
|
66
98
|
raise IntelligenceError("OpenAI package not installed. Run: pip install openai")
|
67
99
|
|
68
100
|
model = config.get("model", "gpt-4")
|
@@ -71,11 +103,17 @@ def _invoke_openai(prompt: str, config: Dict[str, Any]) -> str:
|
|
71
103
|
temperature = config.get("temperature", 0.7)
|
72
104
|
max_tokens = config.get("max_tokens", 150)
|
73
105
|
|
106
|
+
logger.debug(f"🔧 OpenAI config: model={model}, base_url={base_url}, temp={temperature}, max_tokens={max_tokens}")
|
107
|
+
|
74
108
|
if not api_key:
|
109
|
+
logger.error("❌ OpenAI API key not found")
|
75
110
|
raise ConfigurationError("OpenAI API key not found in config or OPENAI_API_KEY environment variable")
|
76
111
|
|
77
112
|
try:
|
113
|
+
logger.debug("🔗 Creating OpenAI client")
|
78
114
|
client = openai.OpenAI(api_key=api_key, base_url=base_url)
|
115
|
+
|
116
|
+
logger.debug("📡 Sending request to OpenAI API")
|
79
117
|
response = client.chat.completions.create(
|
80
118
|
model=model,
|
81
119
|
messages=[{"role": "user", "content": prompt}],
|
@@ -85,19 +123,26 @@ def _invoke_openai(prompt: str, config: Dict[str, Any]) -> str:
|
|
85
123
|
|
86
124
|
content = response.choices[0].message.content
|
87
125
|
if content is None:
|
126
|
+
logger.error("❌ OpenAI returned empty response")
|
88
127
|
raise IntelligenceError("OpenAI returned empty response")
|
128
|
+
|
129
|
+
logger.debug(f"✅ OpenAI API call successful")
|
89
130
|
return content
|
90
131
|
|
91
132
|
except Exception as e:
|
92
|
-
|
133
|
+
logger.error(f"❌ OpenAI API error: {type(e).__name__}: {e}")
|
93
134
|
raise IntelligenceError(f"OpenAI API error: {e}")
|
94
135
|
|
95
136
|
|
96
137
|
def _invoke_anthropic(prompt: str, config: Dict[str, Any]) -> str:
|
97
138
|
"""Invoke Anthropic (Claude) provider."""
|
139
|
+
logger.debug("🟣 Initializing Anthropic provider")
|
140
|
+
|
98
141
|
try:
|
99
142
|
import anthropic
|
143
|
+
logger.debug("✅ Anthropic package imported successfully")
|
100
144
|
except ImportError:
|
145
|
+
logger.error("❌ Anthropic package not installed")
|
101
146
|
raise IntelligenceError("Anthropic package not installed. Run: pip install anthropic")
|
102
147
|
|
103
148
|
model = config.get("model", "claude-3-haiku-20240307")
|
@@ -106,11 +151,17 @@ def _invoke_anthropic(prompt: str, config: Dict[str, Any]) -> str:
|
|
106
151
|
max_tokens = config.get("max_tokens", 150)
|
107
152
|
temperature = config.get("temperature", 0.7)
|
108
153
|
|
154
|
+
logger.debug(f"🔧 Anthropic config: model={model}, base_url={base_url}, temp={temperature}, max_tokens={max_tokens}")
|
155
|
+
|
109
156
|
if not api_key:
|
157
|
+
logger.error("❌ Anthropic API key not found")
|
110
158
|
raise ConfigurationError("Anthropic API key not found in config or ANTHROPIC_API_KEY environment variable")
|
111
159
|
|
112
160
|
try:
|
161
|
+
logger.debug("🔗 Creating Anthropic client")
|
113
162
|
client = anthropic.Anthropic(api_key=api_key, base_url=base_url)
|
163
|
+
|
164
|
+
logger.debug("📡 Sending request to Anthropic API")
|
114
165
|
response = client.messages.create(
|
115
166
|
model=model,
|
116
167
|
max_tokens=max_tokens,
|
@@ -119,21 +170,28 @@ def _invoke_anthropic(prompt: str, config: Dict[str, Any]) -> str:
|
|
119
170
|
)
|
120
171
|
|
121
172
|
if not response.content or len(response.content) == 0:
|
173
|
+
logger.error("❌ Anthropic returned empty response")
|
122
174
|
raise IntelligenceError("Anthropic returned empty response")
|
123
175
|
|
124
176
|
# Anthropic returns a list of content blocks
|
125
|
-
|
177
|
+
result = response.content[0].text
|
178
|
+
logger.debug(f"✅ Anthropic API call successful")
|
179
|
+
return result
|
126
180
|
|
127
181
|
except Exception as e:
|
128
|
-
|
182
|
+
logger.error(f"❌ Anthropic API error: {type(e).__name__}: {e}")
|
129
183
|
raise IntelligenceError(f"Anthropic API error: {e}")
|
130
184
|
|
131
185
|
|
132
186
|
def _invoke_azure_openai(prompt: str, config: Dict[str, Any]) -> str:
|
133
187
|
"""Invoke Azure OpenAI provider."""
|
188
|
+
logger.debug("🔷 Initializing Azure OpenAI provider")
|
189
|
+
|
134
190
|
try:
|
135
191
|
import openai
|
192
|
+
logger.debug("✅ OpenAI package imported successfully")
|
136
193
|
except ImportError:
|
194
|
+
logger.error("❌ OpenAI package not installed")
|
137
195
|
raise IntelligenceError("OpenAI package not installed. Run: pip install openai")
|
138
196
|
|
139
197
|
model = config.get("model", "gpt-4")
|
@@ -143,19 +201,25 @@ def _invoke_azure_openai(prompt: str, config: Dict[str, Any]) -> str:
|
|
143
201
|
temperature = config.get("temperature", 0.7)
|
144
202
|
max_tokens = config.get("max_tokens", 150)
|
145
203
|
|
204
|
+
logger.debug(f"🔧 Azure config: model={model}, endpoint={endpoint}, api_version={api_version}, temp={temperature}, max_tokens={max_tokens}")
|
205
|
+
|
146
206
|
if not api_key:
|
207
|
+
logger.error("❌ Azure OpenAI API key not found")
|
147
208
|
raise ConfigurationError("Azure OpenAI API key not found in config or AZURE_OPENAI_API_KEY environment variable")
|
148
209
|
|
149
210
|
if not endpoint:
|
211
|
+
logger.error("❌ Azure OpenAI endpoint not found")
|
150
212
|
raise ConfigurationError("Azure OpenAI endpoint not found in config or AZURE_OPENAI_ENDPOINT environment variable")
|
151
213
|
|
152
214
|
try:
|
215
|
+
logger.debug("🔗 Creating Azure OpenAI client")
|
153
216
|
client = openai.AzureOpenAI(
|
154
217
|
api_key=api_key,
|
155
218
|
azure_endpoint=endpoint,
|
156
219
|
api_version=api_version
|
157
220
|
)
|
158
221
|
|
222
|
+
logger.debug("📡 Sending request to Azure OpenAI API")
|
159
223
|
response = client.chat.completions.create(
|
160
224
|
model=model,
|
161
225
|
messages=[{"role": "user", "content": prompt}],
|
@@ -165,11 +229,14 @@ def _invoke_azure_openai(prompt: str, config: Dict[str, Any]) -> str:
|
|
165
229
|
|
166
230
|
content = response.choices[0].message.content
|
167
231
|
if content is None:
|
232
|
+
logger.error("❌ Azure OpenAI returned empty response")
|
168
233
|
raise IntelligenceError("Azure OpenAI returned empty response")
|
234
|
+
|
235
|
+
logger.debug(f"✅ Azure OpenAI API call successful")
|
169
236
|
return content
|
170
237
|
|
171
238
|
except Exception as e:
|
172
|
-
|
239
|
+
logger.error(f"❌ Azure OpenAI API error: {type(e).__name__}: {e}")
|
173
240
|
raise IntelligenceError(f"Azure OpenAI API error: {e}")
|
174
241
|
|
175
242
|
|
@@ -182,9 +249,13 @@ def _invoke_local(prompt: str, config: Dict[str, Any]) -> str:
|
|
182
249
|
temperature = config.get("temperature", 0.7)
|
183
250
|
max_tokens = config.get("max_tokens", 150)
|
184
251
|
|
252
|
+
logger.debug(f"🟢 Initializing local provider")
|
253
|
+
logger.debug(f"🔧 Local config: model={model}, endpoint={endpoint}, temp={temperature}, max_tokens={max_tokens}")
|
254
|
+
|
185
255
|
try:
|
186
256
|
# Format for Ollama API
|
187
257
|
if "ollama" in endpoint or ":11434" in endpoint:
|
258
|
+
logger.debug("📦 Using Ollama API format")
|
188
259
|
payload = {
|
189
260
|
"model": model,
|
190
261
|
"prompt": prompt,
|
@@ -195,7 +266,7 @@ def _invoke_local(prompt: str, config: Dict[str, Any]) -> str:
|
|
195
266
|
}
|
196
267
|
}
|
197
268
|
else:
|
198
|
-
|
269
|
+
logger.debug("📦 Using generic local API format")
|
199
270
|
payload = {
|
200
271
|
"model": model,
|
201
272
|
"prompt": prompt,
|
@@ -203,6 +274,7 @@ def _invoke_local(prompt: str, config: Dict[str, Any]) -> str:
|
|
203
274
|
"max_tokens": max_tokens
|
204
275
|
}
|
205
276
|
|
277
|
+
logger.debug(f"📡 Sending request to local endpoint: {endpoint}")
|
206
278
|
response = requests.post(endpoint, json=payload, timeout=30)
|
207
279
|
response.raise_for_status()
|
208
280
|
|
@@ -210,19 +282,25 @@ def _invoke_local(prompt: str, config: Dict[str, Any]) -> str:
|
|
210
282
|
|
211
283
|
# Handle different response formats
|
212
284
|
if "response" in result:
|
213
|
-
|
285
|
+
response_text = result["response"] # Ollama format
|
286
|
+
logger.debug("✅ Local API call successful (Ollama format)")
|
214
287
|
elif "text" in result:
|
215
|
-
|
288
|
+
response_text = result["text"] # Generic format
|
289
|
+
logger.debug("✅ Local API call successful (generic format)")
|
216
290
|
elif "choices" in result and len(result["choices"]) > 0:
|
217
|
-
|
291
|
+
response_text = result["choices"][0].get("text", "") # OpenAI-compatible format
|
292
|
+
logger.debug("✅ Local API call successful (OpenAI-compatible format)")
|
218
293
|
else:
|
294
|
+
logger.error(f"❌ Unexpected response format from local provider: {result}")
|
219
295
|
raise IntelligenceError(f"Unexpected response format from local provider: {result}")
|
296
|
+
|
297
|
+
return response_text
|
220
298
|
|
221
299
|
except requests.RequestException as e:
|
222
|
-
|
300
|
+
logger.error(f"❌ Local provider request error: {type(e).__name__}: {e}")
|
223
301
|
raise IntelligenceError(f"Local provider request error: {e}")
|
224
302
|
except Exception as e:
|
225
|
-
|
303
|
+
logger.error(f"❌ Local provider error: {type(e).__name__}: {e}")
|
226
304
|
raise IntelligenceError(f"Local provider error: {e}")
|
227
305
|
|
228
306
|
|
@@ -244,29 +322,57 @@ def validate_config(config: Dict[str, Any]) -> bool:
|
|
244
322
|
Raises:
|
245
323
|
ConfigurationError: If configuration is invalid
|
246
324
|
"""
|
325
|
+
logger.debug(f"🔍 Validating intelligence configuration")
|
326
|
+
|
247
327
|
if not isinstance(config, dict):
|
328
|
+
logger.error("❌ Configuration must be a dictionary")
|
248
329
|
raise ConfigurationError("Configuration must be a dictionary")
|
249
330
|
|
250
331
|
engine = config.get("engine")
|
251
332
|
if not engine:
|
333
|
+
logger.error("❌ Missing 'engine' in configuration")
|
252
334
|
raise ConfigurationError("Missing 'engine' in configuration")
|
253
335
|
|
254
336
|
if engine.lower() not in get_supported_engines():
|
337
|
+
logger.error(f"❌ Unsupported engine: {engine}")
|
255
338
|
raise ConfigurationError(f"Unsupported engine: {engine}. Supported engines: {get_supported_engines()}")
|
256
339
|
|
257
340
|
# Engine-specific validation
|
258
341
|
engine = engine.lower()
|
342
|
+
logger.debug(f"🔧 Validating {engine} specific configuration")
|
259
343
|
|
260
344
|
if engine in ["openai", "azure"]:
|
261
345
|
if not config.get("api_key") and not os.getenv("OPENAI_API_KEY") and not os.getenv("AZURE_OPENAI_API_KEY"):
|
346
|
+
logger.error(f"❌ API key required for {engine} engine")
|
262
347
|
raise ConfigurationError(f"API key required for {engine} engine")
|
263
348
|
|
264
349
|
elif engine == "anthropic":
|
265
350
|
if not config.get("api_key") and not os.getenv("ANTHROPIC_API_KEY"):
|
351
|
+
logger.error("❌ API key required for Anthropic engine")
|
266
352
|
raise ConfigurationError("API key required for Anthropic engine")
|
267
353
|
|
268
354
|
elif engine == "local":
|
269
355
|
if not config.get("endpoint"):
|
270
356
|
config["endpoint"] = "http://localhost:11434/api/generate" # Default to Ollama
|
357
|
+
logger.debug("🔧 Set default endpoint for local engine")
|
358
|
+
|
359
|
+
logger.debug(f"✅ Configuration validation successful for {engine}")
|
360
|
+
return True
|
361
|
+
|
362
|
+
|
363
|
+
def _sanitize_config_for_logging(config: Dict[str, Any]) -> Dict[str, Any]:
|
364
|
+
"""Sanitize config for logging by masking sensitive data."""
|
365
|
+
sanitized = config.copy()
|
366
|
+
|
367
|
+
# Mask sensitive fields
|
368
|
+
sensitive_fields = ['api_key', 'password', 'token', 'secret']
|
369
|
+
for field in sensitive_fields:
|
370
|
+
if field in sanitized and sanitized[field]:
|
371
|
+
# Show first 4 and last 4 characters, mask the rest
|
372
|
+
value = str(sanitized[field])
|
373
|
+
if len(value) > 8:
|
374
|
+
sanitized[field] = f"{value[:4]}...{value[-4:]}"
|
375
|
+
else:
|
376
|
+
sanitized[field] = "***"
|
271
377
|
|
272
|
-
return
|
378
|
+
return sanitized
|
dacp/logging_config.py
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
"""
|
2
|
+
DACP Logging Configuration
|
3
|
+
|
4
|
+
Utilities for configuring logging for DACP components.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import logging
|
8
|
+
import sys
|
9
|
+
from typing import Optional
|
10
|
+
|
11
|
+
|
12
|
+
def setup_dacp_logging(
|
13
|
+
level: str = "INFO",
|
14
|
+
format_style: str = "detailed",
|
15
|
+
include_timestamp: bool = True,
|
16
|
+
log_file: Optional[str] = None
|
17
|
+
) -> None:
|
18
|
+
"""
|
19
|
+
Set up logging for DACP components.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
23
|
+
format_style: Log format style ('simple', 'detailed', 'emoji')
|
24
|
+
include_timestamp: Whether to include timestamps in logs
|
25
|
+
log_file: Optional file path to also log to a file
|
26
|
+
"""
|
27
|
+
# Define format styles
|
28
|
+
if format_style == "simple":
|
29
|
+
if include_timestamp:
|
30
|
+
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
31
|
+
else:
|
32
|
+
log_format = "%(name)s - %(levelname)s - %(message)s"
|
33
|
+
elif format_style == "detailed":
|
34
|
+
if include_timestamp:
|
35
|
+
log_format = "%(asctime)s - %(name)s:%(lineno)d - %(levelname)s - %(message)s"
|
36
|
+
else:
|
37
|
+
log_format = "%(name)s:%(lineno)d - %(levelname)s - %(message)s"
|
38
|
+
elif format_style == "emoji":
|
39
|
+
# Emoji format doesn't include logger name since emojis provide context
|
40
|
+
if include_timestamp:
|
41
|
+
log_format = "%(asctime)s - %(message)s"
|
42
|
+
else:
|
43
|
+
log_format = "%(message)s"
|
44
|
+
else:
|
45
|
+
raise ValueError(f"Unknown format_style: {format_style}")
|
46
|
+
|
47
|
+
# Configure root logger for DACP components
|
48
|
+
logger = logging.getLogger("dacp")
|
49
|
+
logger.setLevel(getattr(logging, level.upper()))
|
50
|
+
|
51
|
+
# Remove existing handlers to avoid duplicates
|
52
|
+
for handler in logger.handlers[:]:
|
53
|
+
logger.removeHandler(handler)
|
54
|
+
|
55
|
+
# Create formatter
|
56
|
+
formatter = logging.Formatter(
|
57
|
+
log_format,
|
58
|
+
datefmt="%Y-%m-%d %H:%M:%S" if include_timestamp else None
|
59
|
+
)
|
60
|
+
|
61
|
+
# Console handler
|
62
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
63
|
+
console_handler.setFormatter(formatter)
|
64
|
+
logger.addHandler(console_handler)
|
65
|
+
|
66
|
+
# Optional file handler
|
67
|
+
if log_file:
|
68
|
+
file_handler = logging.FileHandler(log_file)
|
69
|
+
file_handler.setFormatter(formatter)
|
70
|
+
logger.addHandler(file_handler)
|
71
|
+
|
72
|
+
# Prevent propagation to root logger to avoid duplicate messages
|
73
|
+
logger.propagate = False
|
74
|
+
|
75
|
+
logger.info(f"🚀 DACP logging configured: level={level}, style={format_style}")
|
76
|
+
|
77
|
+
|
78
|
+
def set_dacp_log_level(level: str) -> None:
|
79
|
+
"""
|
80
|
+
Set the log level for all DACP components.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
84
|
+
"""
|
85
|
+
logger = logging.getLogger("dacp")
|
86
|
+
logger.setLevel(getattr(logging, level.upper()))
|
87
|
+
logger.info(f"📊 DACP log level changed to {level}")
|
88
|
+
|
89
|
+
|
90
|
+
def disable_dacp_logging() -> None:
|
91
|
+
"""Disable all DACP logging."""
|
92
|
+
logger = logging.getLogger("dacp")
|
93
|
+
logger.disabled = True
|
94
|
+
|
95
|
+
|
96
|
+
def enable_dacp_logging() -> None:
|
97
|
+
"""Re-enable DACP logging."""
|
98
|
+
logger = logging.getLogger("dacp")
|
99
|
+
logger.disabled = False
|
100
|
+
|
101
|
+
|
102
|
+
def get_dacp_logger(name: str) -> logging.Logger:
|
103
|
+
"""
|
104
|
+
Get a logger for a DACP component.
|
105
|
+
|
106
|
+
Args:
|
107
|
+
name: Logger name (usually __name__)
|
108
|
+
|
109
|
+
Returns:
|
110
|
+
Configured logger
|
111
|
+
"""
|
112
|
+
return logging.getLogger(f"dacp.{name}")
|
113
|
+
|
114
|
+
|
115
|
+
# Convenience functions for quick setup
|
116
|
+
def enable_debug_logging(log_file: Optional[str] = None) -> None:
|
117
|
+
"""Enable debug logging with detailed format."""
|
118
|
+
setup_dacp_logging(level="DEBUG", format_style="detailed", log_file=log_file)
|
119
|
+
|
120
|
+
|
121
|
+
def enable_info_logging(log_file: Optional[str] = None) -> None:
|
122
|
+
"""Enable info logging with emoji format."""
|
123
|
+
setup_dacp_logging(level="INFO", format_style="emoji", log_file=log_file)
|
124
|
+
|
125
|
+
|
126
|
+
def enable_quiet_logging() -> None:
|
127
|
+
"""Enable only error and critical logging."""
|
128
|
+
setup_dacp_logging(level="ERROR", format_style="simple", include_timestamp=False)
|
dacp/orchestrator.py
CHANGED
@@ -3,9 +3,9 @@ DACP Orchestrator - Manages agent registration and message routing.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import logging
|
6
|
-
|
7
|
-
import
|
8
|
-
import
|
6
|
+
import time
|
7
|
+
from typing import Dict, Any, List, Optional
|
8
|
+
from .tools import run_tool, TOOL_REGISTRY
|
9
9
|
from .protocol import (
|
10
10
|
parse_agent_response,
|
11
11
|
is_tool_request,
|
@@ -14,30 +14,31 @@ from .protocol import (
|
|
14
14
|
is_final_response,
|
15
15
|
get_final_response,
|
16
16
|
)
|
17
|
-
from .tools import run_tool
|
18
17
|
|
19
|
-
|
18
|
+
# Set up logger for this module
|
19
|
+
logger = logging.getLogger("dacp.orchestrator")
|
20
20
|
|
21
21
|
|
22
22
|
class Agent:
|
23
|
-
"""Base
|
23
|
+
"""Base class for DACP agents."""
|
24
24
|
|
25
25
|
def handle_message(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
26
|
-
"""Handle incoming
|
26
|
+
"""Handle an incoming message. Subclasses should override this method."""
|
27
27
|
raise NotImplementedError("Agents must implement handle_message method")
|
28
28
|
|
29
29
|
|
30
30
|
class Orchestrator:
|
31
31
|
"""
|
32
32
|
Central orchestrator for managing agents and routing messages.
|
33
|
-
Handles agent registration, message routing, and tool execution.
|
34
33
|
"""
|
35
34
|
|
36
35
|
def __init__(self):
|
36
|
+
"""Initialize the orchestrator."""
|
37
37
|
self.agents: Dict[str, Any] = {}
|
38
38
|
self.conversation_history: List[Dict[str, Any]] = []
|
39
|
-
self.session_id =
|
40
|
-
|
39
|
+
self.session_id = f"session_{int(time.time())}"
|
40
|
+
logger.info(f"🎭 Orchestrator initialized with session ID: {self.session_id}")
|
41
|
+
|
41
42
|
def register_agent(self, agent_id: str, agent: Any) -> None:
|
42
43
|
"""
|
43
44
|
Register an agent with the orchestrator.
|
@@ -47,183 +48,145 @@ class Orchestrator:
|
|
47
48
|
agent: Agent instance that implements handle_message method
|
48
49
|
"""
|
49
50
|
if not hasattr(agent, 'handle_message'):
|
50
|
-
|
51
|
+
logger.error(f"❌ Agent '{agent_id}' does not implement handle_message method")
|
52
|
+
raise ValueError(f"Agent must implement handle_message method")
|
51
53
|
|
52
54
|
self.agents[agent_id] = agent
|
53
|
-
|
54
|
-
|
55
|
+
logger.info(f"✅ Agent '{agent_id}' registered successfully (type: {type(agent).__name__})")
|
56
|
+
logger.debug(f"📊 Total registered agents: {len(self.agents)}")
|
57
|
+
|
55
58
|
def unregister_agent(self, agent_id: str) -> bool:
|
56
59
|
"""
|
57
60
|
Unregister an agent from the orchestrator.
|
58
61
|
|
59
62
|
Args:
|
60
|
-
agent_id:
|
63
|
+
agent_id: Unique identifier for the agent
|
61
64
|
|
62
65
|
Returns:
|
63
|
-
True if agent was
|
66
|
+
True if agent was successfully unregistered, False if not found
|
64
67
|
"""
|
65
68
|
if agent_id in self.agents:
|
69
|
+
agent_type = type(self.agents[agent_id]).__name__
|
66
70
|
del self.agents[agent_id]
|
67
|
-
|
71
|
+
logger.info(f"🗑️ Agent '{agent_id}' unregistered successfully (was type: {agent_type})")
|
72
|
+
logger.debug(f"📊 Remaining registered agents: {len(self.agents)}")
|
68
73
|
return True
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
return self.agents.get(agent_id)
|
74
|
-
|
75
|
-
def list_agents(self) -> List[str]:
|
76
|
-
"""Get list of registered agent IDs."""
|
77
|
-
return list(self.agents.keys())
|
78
|
-
|
74
|
+
else:
|
75
|
+
logger.warning(f"⚠️ Attempted to unregister unknown agent: '{agent_id}'")
|
76
|
+
return False
|
77
|
+
|
79
78
|
def send_message(self, agent_id: str, message: Dict[str, Any]) -> Dict[str, Any]:
|
80
79
|
"""
|
81
80
|
Send a message to a specific agent.
|
82
81
|
|
83
82
|
Args:
|
84
|
-
agent_id:
|
83
|
+
agent_id: Target agent identifier
|
85
84
|
message: Message to send
|
86
85
|
|
87
86
|
Returns:
|
88
|
-
Response from the agent
|
89
|
-
|
90
|
-
Raises:
|
91
|
-
ValueError: If agent_id is not found
|
87
|
+
Response from the agent (or error if agent not found)
|
92
88
|
"""
|
89
|
+
logger.info(f"📨 Sending message to agent '{agent_id}'")
|
90
|
+
logger.debug(f"📋 Message content: {message}")
|
91
|
+
|
93
92
|
if agent_id not in self.agents:
|
94
|
-
|
95
|
-
|
96
|
-
|
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
97
|
|
98
|
-
|
99
|
-
enriched_message = {
|
100
|
-
"session_id": self.session_id,
|
101
|
-
"timestamp": self._get_timestamp(),
|
102
|
-
**message
|
103
|
-
}
|
98
|
+
agent = self.agents[agent_id]
|
104
99
|
|
105
100
|
try:
|
106
|
-
|
107
|
-
|
101
|
+
start_time = time.time()
|
102
|
+
logger.debug(f"🔄 Calling handle_message on agent '{agent_id}'")
|
108
103
|
|
109
|
-
|
110
|
-
self.conversation_history.append({
|
111
|
-
"type": "message",
|
112
|
-
"agent_id": agent_id,
|
113
|
-
"message": enriched_message,
|
114
|
-
"response": response,
|
115
|
-
"timestamp": self._get_timestamp()
|
116
|
-
})
|
104
|
+
response = agent.handle_message(message)
|
117
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)
|
118
148
|
return response
|
119
149
|
|
120
150
|
except Exception as e:
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
}
|
125
|
-
|
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)
|
126
156
|
return error_response
|
127
|
-
|
128
|
-
def broadcast_message(self, message: Dict[str, Any], exclude_agents: List[str] = None) -> Dict[str, Dict[str, Any]]:
|
157
|
+
|
158
|
+
def broadcast_message(self, message: Dict[str, Any], exclude_agents: Optional[List[str]] = None) -> Dict[str, Dict[str, Any]]:
|
129
159
|
"""
|
130
|
-
|
160
|
+
Send a message to all registered agents (optionally excluding some).
|
131
161
|
|
132
162
|
Args:
|
133
163
|
message: Message to broadcast
|
134
164
|
exclude_agents: List of agent IDs to exclude from broadcast
|
135
165
|
|
136
166
|
Returns:
|
137
|
-
|
167
|
+
Dict mapping agent IDs to their responses
|
138
168
|
"""
|
139
169
|
exclude_agents = exclude_agents or []
|
140
|
-
|
170
|
+
target_agents = [aid for aid in self.agents.keys() if aid not in exclude_agents]
|
141
171
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
except Exception as e:
|
147
|
-
responses[agent_id] = {"error": str(e)}
|
148
|
-
|
149
|
-
return responses
|
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}")
|
150
176
|
|
151
|
-
|
152
|
-
|
153
|
-
Handle a tool execution request.
|
177
|
+
responses = {}
|
178
|
+
start_time = time.time()
|
154
179
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
Returns:
|
160
|
-
Tool execution result wrapped in protocol format
|
161
|
-
"""
|
162
|
-
try:
|
163
|
-
result = run_tool(tool_name, args)
|
164
|
-
return wrap_tool_result(tool_name, result)
|
165
|
-
except Exception as e:
|
166
|
-
error_result = {
|
167
|
-
"success": False,
|
168
|
-
"error": str(e),
|
169
|
-
"tool_name": tool_name,
|
170
|
-
"args": args
|
171
|
-
}
|
172
|
-
return wrap_tool_result(tool_name, error_result)
|
173
|
-
|
174
|
-
def process_agent_response(self, agent_id: str, response: Any) -> Dict[str, Any]:
|
175
|
-
"""
|
176
|
-
Process an agent response, handling tool requests and final responses.
|
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)
|
177
183
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
"""
|
185
|
-
try:
|
186
|
-
# Parse the agent response
|
187
|
-
parsed_response = parse_agent_response(response)
|
188
|
-
|
189
|
-
# Check if it's a tool request
|
190
|
-
if is_tool_request(parsed_response):
|
191
|
-
tool_name, args = get_tool_request(parsed_response)
|
192
|
-
log.info(f"Agent {agent_id} requested tool: {tool_name}")
|
193
|
-
|
194
|
-
# Execute the tool
|
195
|
-
tool_result = self.handle_tool_request(tool_name, args)
|
196
|
-
|
197
|
-
# Log tool execution
|
198
|
-
self.conversation_history.append({
|
199
|
-
"type": "tool_execution",
|
200
|
-
"agent_id": agent_id,
|
201
|
-
"tool_name": tool_name,
|
202
|
-
"args": args,
|
203
|
-
"result": tool_result,
|
204
|
-
"timestamp": self._get_timestamp()
|
205
|
-
})
|
206
|
-
|
207
|
-
return tool_result
|
208
|
-
|
209
|
-
# Check if it's a final response
|
210
|
-
elif is_final_response(parsed_response):
|
211
|
-
final_response = get_final_response(parsed_response)
|
212
|
-
log.info(f"Agent {agent_id} sent final response")
|
213
|
-
return final_response
|
214
|
-
|
215
|
-
else:
|
216
|
-
# Return the parsed response as-is
|
217
|
-
return parsed_response
|
218
|
-
|
219
|
-
except Exception as e:
|
220
|
-
log.error(f"Error processing agent response from {agent_id}: {e}")
|
221
|
-
return {
|
222
|
-
"error": f"Failed to process response: {str(e)}",
|
223
|
-
"original_response": response
|
224
|
-
}
|
225
|
-
|
226
|
-
def get_conversation_history(self, agent_id: str = None) -> List[Dict[str, Any]]:
|
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]]:
|
227
190
|
"""
|
228
191
|
Get conversation history, optionally filtered by agent.
|
229
192
|
|
@@ -233,28 +196,53 @@ class Orchestrator:
|
|
233
196
|
Returns:
|
234
197
|
List of conversation entries
|
235
198
|
"""
|
236
|
-
if agent_id:
|
237
|
-
|
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 = [
|
238
204
|
entry for entry in self.conversation_history
|
239
205
|
if entry.get("agent_id") == agent_id
|
240
206
|
]
|
241
|
-
|
242
|
-
|
207
|
+
logger.debug(f"📚 Retrieving conversation history for '{agent_id}' ({len(filtered_history)} entries)")
|
208
|
+
return filtered_history
|
209
|
+
|
243
210
|
def clear_history(self) -> None:
|
244
|
-
"""Clear conversation history."""
|
211
|
+
"""Clear the conversation history."""
|
212
|
+
old_count = len(self.conversation_history)
|
245
213
|
self.conversation_history.clear()
|
246
|
-
|
247
|
-
|
214
|
+
logger.info(f"🗑️ Conversation history cleared ({old_count} entries removed)")
|
215
|
+
|
248
216
|
def get_session_info(self) -> Dict[str, Any]:
|
249
|
-
"""
|
250
|
-
|
217
|
+
"""
|
218
|
+
Get current session information.
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
Dict containing session metadata
|
222
|
+
"""
|
223
|
+
info = {
|
251
224
|
"session_id": self.session_id,
|
252
|
-
"registered_agents": self.
|
253
|
-
"
|
254
|
-
"
|
225
|
+
"registered_agents": list(self.agents.keys()),
|
226
|
+
"conversation_count": len(self.conversation_history),
|
227
|
+
"available_tools": list(TOOL_REGISTRY.keys())
|
255
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")
|
256
247
|
|
257
|
-
|
258
|
-
"""Get current timestamp in ISO format."""
|
259
|
-
from datetime import datetime
|
260
|
-
return datetime.utcnow().isoformat() + "Z"
|
248
|
+
self.conversation_history.append(entry)
|
dacp/tools.py
CHANGED
@@ -1,21 +1,43 @@
|
|
1
|
+
import logging
|
1
2
|
from typing import Dict, Any, Callable
|
2
3
|
from pathlib import Path
|
3
4
|
|
5
|
+
# Set up logger for this module
|
6
|
+
logger = logging.getLogger("dacp.tools")
|
7
|
+
|
4
8
|
TOOL_REGISTRY: Dict[str, Callable[..., Dict[str, Any]]] = {}
|
5
9
|
|
6
10
|
|
7
11
|
def register_tool(tool_id: str, func: Callable[..., Dict[str, Any]]) -> None:
|
8
12
|
"""Register a tool function."""
|
9
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)}")
|
10
16
|
|
11
17
|
|
12
18
|
def run_tool(tool_id: str, args: Dict[str, Any]) -> Dict[str, Any]:
|
13
19
|
"""Run a registered tool with the given arguments."""
|
14
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())}")
|
15
23
|
raise ValueError(f"Unknown tool: {tool_id}")
|
16
24
|
|
17
25
|
tool_func = TOOL_REGISTRY[tool_id]
|
18
|
-
|
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
|
19
41
|
|
20
42
|
|
21
43
|
def file_writer(path: str, content: str) -> Dict[str, Any]:
|
@@ -29,20 +51,27 @@ def file_writer(path: str, content: str) -> Dict[str, Any]:
|
|
29
51
|
Returns:
|
30
52
|
Dict with success status and file path
|
31
53
|
"""
|
54
|
+
logger.debug(f"📝 Writing to file: {path} ({len(content)} characters)")
|
55
|
+
|
32
56
|
try:
|
33
57
|
# Create parent directories if they don't exist
|
34
|
-
Path(path).parent
|
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)
|
35
62
|
|
36
63
|
# Write the content to the file
|
37
64
|
with open(path, "w", encoding="utf-8") as f:
|
38
65
|
f.write(content)
|
39
66
|
|
67
|
+
logger.info(f"✅ File written successfully: {path}")
|
40
68
|
return {
|
41
69
|
"success": True,
|
42
70
|
"path": path,
|
43
71
|
"message": f"Successfully wrote {len(content)} characters to {path}",
|
44
72
|
}
|
45
73
|
except Exception as e:
|
74
|
+
logger.error(f"❌ Failed to write file {path}: {type(e).__name__}: {e}")
|
46
75
|
return {
|
47
76
|
"success": False,
|
48
77
|
"path": path,
|
@@ -52,4 +81,6 @@ def file_writer(path: str, content: str) -> Dict[str, Any]:
|
|
52
81
|
|
53
82
|
|
54
83
|
# Register the built-in file_writer tool
|
84
|
+
logger.debug("🏗️ Registering built-in tools...")
|
55
85
|
register_tool("file_writer", file_writer)
|
86
|
+
logger.debug("✅ Built-in tools registration complete")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: dacp
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.1
|
4
4
|
Summary: Declarative Agent Communication Protocol - A protocol for managing LLM/agent communications and tool function calls
|
5
5
|
Author-email: Andrew Whitehouse <andrew.whitehouse@example.com>
|
6
6
|
License: MIT
|
@@ -132,6 +132,16 @@ response = dacp.call_llm("What is the weather like today?")
|
|
132
132
|
|
133
133
|
- `call_llm(prompt: str, model: str = "gpt-4") -> str`: Call OpenAI (legacy function)
|
134
134
|
|
135
|
+
### Logging
|
136
|
+
|
137
|
+
- `enable_info_logging(log_file: str = None) -> None`: Enable info-level logging with emoji format
|
138
|
+
- `enable_debug_logging(log_file: str = None) -> None`: Enable debug logging with detailed format
|
139
|
+
- `enable_quiet_logging() -> None`: Enable only error and critical logging
|
140
|
+
- `setup_dacp_logging(level, format_style, include_timestamp, log_file) -> None`: Custom logging setup
|
141
|
+
- `set_dacp_log_level(level: str) -> None`: Change log level dynamically
|
142
|
+
- `disable_dacp_logging() -> None`: Disable all DACP logging
|
143
|
+
- `enable_dacp_logging() -> None`: Re-enable DACP logging
|
144
|
+
|
135
145
|
### Protocol
|
136
146
|
|
137
147
|
- `parse_agent_response(response: str | dict) -> dict`: Parse agent response
|
@@ -348,6 +358,91 @@ else:
|
|
348
358
|
- ✅ Returns detailed success/error information
|
349
359
|
- ✅ Safe error handling
|
350
360
|
|
361
|
+
## Logging
|
362
|
+
|
363
|
+
DACP includes comprehensive logging to help you monitor agent operations, tool executions, and intelligence calls.
|
364
|
+
|
365
|
+
### Quick Setup
|
366
|
+
|
367
|
+
```python
|
368
|
+
import dacp
|
369
|
+
|
370
|
+
# Enable info-level logging with emoji format (recommended for production)
|
371
|
+
dacp.enable_info_logging()
|
372
|
+
|
373
|
+
# Enable debug logging for development (shows detailed information)
|
374
|
+
dacp.enable_debug_logging()
|
375
|
+
|
376
|
+
# Enable quiet logging (errors only)
|
377
|
+
dacp.enable_quiet_logging()
|
378
|
+
```
|
379
|
+
|
380
|
+
### Custom Configuration
|
381
|
+
|
382
|
+
```python
|
383
|
+
# Full control over logging configuration
|
384
|
+
dacp.setup_dacp_logging(
|
385
|
+
level="INFO", # DEBUG, INFO, WARNING, ERROR, CRITICAL
|
386
|
+
format_style="emoji", # "simple", "detailed", "emoji"
|
387
|
+
include_timestamp=True, # Include timestamps
|
388
|
+
log_file="dacp.log" # Optional: also log to file
|
389
|
+
)
|
390
|
+
|
391
|
+
# Change log level dynamically
|
392
|
+
dacp.set_dacp_log_level("DEBUG")
|
393
|
+
|
394
|
+
# Disable/enable logging
|
395
|
+
dacp.disable_dacp_logging()
|
396
|
+
dacp.enable_dacp_logging()
|
397
|
+
```
|
398
|
+
|
399
|
+
### What Gets Logged
|
400
|
+
|
401
|
+
With logging enabled, you'll see:
|
402
|
+
|
403
|
+
- **🎭 Agent Registration**: When agents are registered/unregistered
|
404
|
+
- **📨 Message Routing**: Messages sent to agents and broadcast operations
|
405
|
+
- **🔧 Tool Execution**: Tool calls, execution time, and results
|
406
|
+
- **🧠 Intelligence Calls**: LLM provider calls, configuration, and performance
|
407
|
+
- **❌ Errors**: Detailed error information with context
|
408
|
+
- **📊 Performance**: Execution times for operations
|
409
|
+
|
410
|
+
### Log Format Examples
|
411
|
+
|
412
|
+
**Emoji Format** (clean, production-friendly):
|
413
|
+
```
|
414
|
+
2025-07-02 09:54:58 - 🎭 Orchestrator initialized with session ID: session_1751414098
|
415
|
+
2025-07-02 09:54:58 - ✅ Agent 'demo-agent' registered successfully (type: MyAgent)
|
416
|
+
2025-07-02 09:54:58 - 📨 Sending message to agent 'demo-agent'
|
417
|
+
2025-07-02 09:54:58 - 🔧 Agent 'demo-agent' requested tool execution
|
418
|
+
2025-07-02 09:54:58 - 🛠️ Executing tool: 'file_writer' with args: {...}
|
419
|
+
2025-07-02 09:54:58 - ✅ Tool 'file_writer' executed successfully in 0.001s
|
420
|
+
```
|
421
|
+
|
422
|
+
**Detailed Format** (development/debugging):
|
423
|
+
```
|
424
|
+
2025-07-02 09:54:58 - dacp.orchestrator:89 - INFO - 📨 Sending message to agent 'demo-agent'
|
425
|
+
2025-07-02 09:54:58 - dacp.orchestrator:90 - DEBUG - 📋 Message content: {'task': 'greet'}
|
426
|
+
2025-07-02 09:54:58 - dacp.tools:26 - DEBUG - 🛠️ Executing tool 'file_writer' with args: {...}
|
427
|
+
```
|
428
|
+
|
429
|
+
### Example Usage
|
430
|
+
|
431
|
+
```python
|
432
|
+
import dacp
|
433
|
+
|
434
|
+
# Enable logging
|
435
|
+
dacp.enable_info_logging()
|
436
|
+
|
437
|
+
# Create and use components - logging happens automatically
|
438
|
+
orchestrator = dacp.Orchestrator()
|
439
|
+
agent = MyAgent()
|
440
|
+
orchestrator.register_agent("my-agent", agent)
|
441
|
+
|
442
|
+
# This will log the message sending, tool execution, etc.
|
443
|
+
response = orchestrator.send_message("my-agent", {"task": "process"})
|
444
|
+
```
|
445
|
+
|
351
446
|
## Development
|
352
447
|
|
353
448
|
```bash
|
@@ -0,0 +1,15 @@
|
|
1
|
+
dacp/__init__.py,sha256=qTwsfYAnuq4PiE_1Vm6gK7VAycBEz_K6C2hzmPOODI0,1352
|
2
|
+
dacp/exceptions.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
dacp/intelligence.py,sha256=dyVxbVHAX56tsgC-_C9jNTov9PstPTiGehBS4L57lXw,15002
|
4
|
+
dacp/llm.py,sha256=JxHqM-aIm9pAMcVHQevbJGxrlBH4uV1ngRQdvyp9L3A,827
|
5
|
+
dacp/logging_config.py,sha256=FqQp650BwQLKM32L0ITYjadXXzG22KbjsL21_JlK9LM,3950
|
6
|
+
dacp/main.py,sha256=ZcJLymC9S5A4iO4yV7X178RLlhDrDuAwirdevUD5Yn0,470
|
7
|
+
dacp/orchestrator.py,sha256=oJ0C27qMBVgAW8jPqTvNhVOuaYBn5wRa2kJIej05iac,10065
|
8
|
+
dacp/protocol.py,sha256=DVhLTdyDVlAu8ETSEX8trPeycKfMeirHwcWQ8-BY7eA,1026
|
9
|
+
dacp/tools.py,sha256=9YNBg-YJtAWKNo88VXgQ6bedu_4Z3pGWq2QWVXPJg30,3009
|
10
|
+
dacp/types.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
+
dacp-0.3.1.dist-info/licenses/LICENSE,sha256=tb5kgUYRypHqAy8wlrJUBSYI5l1SBmawSYHmCC-MVW0,1074
|
12
|
+
dacp-0.3.1.dist-info/METADATA,sha256=CAs6fi58DLlSzcKkn8uPMKd1QP19yPWbFNKF71Z8Q5s,14506
|
13
|
+
dacp-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
14
|
+
dacp-0.3.1.dist-info/top_level.txt,sha256=Qxy0cy5jl7ttTQoGFlY9LXB6CbSvsekJ2y0P8I7L1zA,5
|
15
|
+
dacp-0.3.1.dist-info/RECORD,,
|
dacp-0.3.0.dist-info/RECORD
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
dacp/__init__.py,sha256=-EFiHyOWg8IcG2FI-Hmd9wrHicGi8qAMv0cPwz0oCiU,833
|
2
|
-
dacp/exceptions.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
dacp/intelligence.py,sha256=SGLaq6dlJmeDqP0ZgT6bR_4WahNf-K7qWBaTG3yqBMI,9487
|
4
|
-
dacp/llm.py,sha256=JxHqM-aIm9pAMcVHQevbJGxrlBH4uV1ngRQdvyp9L3A,827
|
5
|
-
dacp/main.py,sha256=ZcJLymC9S5A4iO4yV7X178RLlhDrDuAwirdevUD5Yn0,470
|
6
|
-
dacp/orchestrator.py,sha256=PIur8Kts-vtUchmo7E8B59ZLm6s7C7yQ0ScK9E5FXhE,8756
|
7
|
-
dacp/protocol.py,sha256=DVhLTdyDVlAu8ETSEX8trPeycKfMeirHwcWQ8-BY7eA,1026
|
8
|
-
dacp/tools.py,sha256=FLgZa5Brni9H3Hyn336k3DGoVxIa_1rFM4WpIRXzVbc,1569
|
9
|
-
dacp/types.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
dacp-0.3.0.dist-info/licenses/LICENSE,sha256=tb5kgUYRypHqAy8wlrJUBSYI5l1SBmawSYHmCC-MVW0,1074
|
11
|
-
dacp-0.3.0.dist-info/METADATA,sha256=J9bKw2rxID--1fqZ9Z33lSs2SKTmat72zVLj-DUirU0,11220
|
12
|
-
dacp-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
13
|
-
dacp-0.3.0.dist-info/top_level.txt,sha256=Qxy0cy5jl7ttTQoGFlY9LXB6CbSvsekJ2y0P8I7L1zA,5
|
14
|
-
dacp-0.3.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|