dacp 0.3.0__tar.gz → 0.3.1__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dacp
3
- Version: 0.3.0
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
@@ -85,6 +85,16 @@ response = dacp.call_llm("What is the weather like today?")
85
85
 
86
86
  - `call_llm(prompt: str, model: str = "gpt-4") -> str`: Call OpenAI (legacy function)
87
87
 
88
+ ### Logging
89
+
90
+ - `enable_info_logging(log_file: str = None) -> None`: Enable info-level logging with emoji format
91
+ - `enable_debug_logging(log_file: str = None) -> None`: Enable debug logging with detailed format
92
+ - `enable_quiet_logging() -> None`: Enable only error and critical logging
93
+ - `setup_dacp_logging(level, format_style, include_timestamp, log_file) -> None`: Custom logging setup
94
+ - `set_dacp_log_level(level: str) -> None`: Change log level dynamically
95
+ - `disable_dacp_logging() -> None`: Disable all DACP logging
96
+ - `enable_dacp_logging() -> None`: Re-enable DACP logging
97
+
88
98
  ### Protocol
89
99
 
90
100
  - `parse_agent_response(response: str | dict) -> dict`: Parse agent response
@@ -301,6 +311,91 @@ else:
301
311
  - ✅ Returns detailed success/error information
302
312
  - ✅ Safe error handling
303
313
 
314
+ ## Logging
315
+
316
+ DACP includes comprehensive logging to help you monitor agent operations, tool executions, and intelligence calls.
317
+
318
+ ### Quick Setup
319
+
320
+ ```python
321
+ import dacp
322
+
323
+ # Enable info-level logging with emoji format (recommended for production)
324
+ dacp.enable_info_logging()
325
+
326
+ # Enable debug logging for development (shows detailed information)
327
+ dacp.enable_debug_logging()
328
+
329
+ # Enable quiet logging (errors only)
330
+ dacp.enable_quiet_logging()
331
+ ```
332
+
333
+ ### Custom Configuration
334
+
335
+ ```python
336
+ # Full control over logging configuration
337
+ dacp.setup_dacp_logging(
338
+ level="INFO", # DEBUG, INFO, WARNING, ERROR, CRITICAL
339
+ format_style="emoji", # "simple", "detailed", "emoji"
340
+ include_timestamp=True, # Include timestamps
341
+ log_file="dacp.log" # Optional: also log to file
342
+ )
343
+
344
+ # Change log level dynamically
345
+ dacp.set_dacp_log_level("DEBUG")
346
+
347
+ # Disable/enable logging
348
+ dacp.disable_dacp_logging()
349
+ dacp.enable_dacp_logging()
350
+ ```
351
+
352
+ ### What Gets Logged
353
+
354
+ With logging enabled, you'll see:
355
+
356
+ - **🎭 Agent Registration**: When agents are registered/unregistered
357
+ - **📨 Message Routing**: Messages sent to agents and broadcast operations
358
+ - **🔧 Tool Execution**: Tool calls, execution time, and results
359
+ - **🧠 Intelligence Calls**: LLM provider calls, configuration, and performance
360
+ - **❌ Errors**: Detailed error information with context
361
+ - **📊 Performance**: Execution times for operations
362
+
363
+ ### Log Format Examples
364
+
365
+ **Emoji Format** (clean, production-friendly):
366
+ ```
367
+ 2025-07-02 09:54:58 - 🎭 Orchestrator initialized with session ID: session_1751414098
368
+ 2025-07-02 09:54:58 - ✅ Agent 'demo-agent' registered successfully (type: MyAgent)
369
+ 2025-07-02 09:54:58 - 📨 Sending message to agent 'demo-agent'
370
+ 2025-07-02 09:54:58 - 🔧 Agent 'demo-agent' requested tool execution
371
+ 2025-07-02 09:54:58 - 🛠️ Executing tool: 'file_writer' with args: {...}
372
+ 2025-07-02 09:54:58 - ✅ Tool 'file_writer' executed successfully in 0.001s
373
+ ```
374
+
375
+ **Detailed Format** (development/debugging):
376
+ ```
377
+ 2025-07-02 09:54:58 - dacp.orchestrator:89 - INFO - 📨 Sending message to agent 'demo-agent'
378
+ 2025-07-02 09:54:58 - dacp.orchestrator:90 - DEBUG - 📋 Message content: {'task': 'greet'}
379
+ 2025-07-02 09:54:58 - dacp.tools:26 - DEBUG - 🛠️ Executing tool 'file_writer' with args: {...}
380
+ ```
381
+
382
+ ### Example Usage
383
+
384
+ ```python
385
+ import dacp
386
+
387
+ # Enable logging
388
+ dacp.enable_info_logging()
389
+
390
+ # Create and use components - logging happens automatically
391
+ orchestrator = dacp.Orchestrator()
392
+ agent = MyAgent()
393
+ orchestrator.register_agent("my-agent", agent)
394
+
395
+ # This will log the message sending, tool execution, etc.
396
+ response = orchestrator.send_message("my-agent", {"task": "process"})
397
+ ```
398
+
304
399
  ## Development
305
400
 
306
401
  ```bash
@@ -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
  ]
@@ -6,7 +6,8 @@ import os
6
6
  import logging
7
7
  from typing import Dict, Any, Optional
8
8
 
9
- log = logging.getLogger(__name__)
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
- if engine == "openai":
50
- return _invoke_openai(prompt, config)
51
- elif engine == "anthropic":
52
- return _invoke_anthropic(prompt, config)
53
- elif engine == "azure":
54
- return _invoke_azure_openai(prompt, config)
55
- elif engine == "local":
56
- return _invoke_local(prompt, config)
57
- else:
58
- raise UnsupportedProviderError(f"Unsupported intelligence engine: {engine}")
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
- log.error(f"OpenAI API error: {e}")
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
- return response.content[0].text
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
- log.error(f"Anthropic API error: {e}")
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
- log.error(f"Azure OpenAI API error: {e}")
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
- # Generic local API format
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
- return result["response"] # Ollama format
285
+ response_text = result["response"] # Ollama format
286
+ logger.debug("✅ Local API call successful (Ollama format)")
214
287
  elif "text" in result:
215
- return result["text"] # Generic format
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
- return result["choices"][0].get("text", "") # OpenAI-compatible format
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
- log.error(f"Local provider request error: {e}")
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
- log.error(f"Local provider error: {e}")
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 True
378
+ return sanitized