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/tools.py CHANGED
@@ -1,86 +1,105 @@
1
+ """
2
+ DACP Tools - Built-in tool implementations.
3
+
4
+ This module provides the core tool functionality including tool registry,
5
+ execution, and built-in tools like file_writer.
6
+ """
7
+
1
8
  import logging
2
- from typing import Dict, Any, Callable
3
9
  from pathlib import Path
10
+ from typing import Dict, Any, Callable
4
11
 
5
- # Set up logger for this module
6
12
  logger = logging.getLogger("dacp.tools")
7
13
 
8
- TOOL_REGISTRY: Dict[str, Callable[..., Dict[str, Any]]] = {}
14
+ # Global tool registry
15
+ TOOL_REGISTRY: Dict[str, Callable[[Dict[str, Any]], Dict[str, Any]]] = {}
9
16
 
10
17
 
11
- def register_tool(tool_id: str, func: Callable[..., Dict[str, Any]]) -> None:
12
- """Register a tool function."""
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)}")
18
+ def register_tool(name: str, func: Callable[[Dict[str, Any]], Dict[str, Any]]) -> None:
19
+ """
20
+ Register a tool function.
21
+
22
+ Args:
23
+ name: Name of the tool
24
+ func: Function that takes args dict and returns result dict
25
+ """
26
+ TOOL_REGISTRY[name] = func
27
+ logger.info(f"🔧 Tool '{name}' registered")
16
28
 
17
29
 
18
- def run_tool(tool_id: str, args: Dict[str, Any]) -> Dict[str, Any]:
19
- """Run a registered tool with the given arguments."""
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())}")
23
- raise ValueError(f"Unknown tool: {tool_id}")
30
+ def execute_tool(name: str, args: Dict[str, Any]) -> Dict[str, Any]:
31
+ """
32
+ Execute a tool by name with given arguments.
33
+
34
+ Args:
35
+ name: Name of the tool to execute
36
+ args: Arguments to pass to the tool
37
+
38
+ Returns:
39
+ Tool execution result
40
+
41
+ Raises:
42
+ ValueError: If tool is not found
43
+ """
44
+ if name not in TOOL_REGISTRY:
45
+ available_tools = list(TOOL_REGISTRY.keys())
46
+ logger.error(
47
+ f"❌ Tool '{name}' not found. " f"Available tools: {available_tools}"
48
+ )
49
+ raise ValueError(f"Tool '{name}' not found. Available tools: {available_tools}")
50
+
51
+ logger.debug(f"🛠️ Executing tool '{name}' with args: {args}")
24
52
 
25
- tool_func = TOOL_REGISTRY[tool_id]
26
- logger.debug(f"🛠️ Executing tool '{tool_id}' with args: {args}")
27
-
28
- import time
29
- start_time = time.time()
30
-
31
53
  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}")
54
+ result = TOOL_REGISTRY[name](args)
55
+ logger.debug(f"✅ Tool '{name}' completed successfully")
36
56
  return result
37
57
  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}")
58
+ logger.error(f"❌ Tool '{name}' failed: {type(e).__name__}: {e}")
40
59
  raise
41
60
 
42
61
 
43
- def file_writer(path: str, content: str) -> Dict[str, Any]:
62
+ def file_writer(args: Dict[str, Any]) -> Dict[str, Any]:
44
63
  """
45
- Write content to a file, creating parent directories if they don't exist.
64
+ Write content to a file, creating directories as needed.
46
65
 
47
66
  Args:
48
- path: File path to write to
49
- content: Content to write to the file
67
+ args: Dictionary containing 'path' and 'content'
50
68
 
51
69
  Returns:
52
- Dict with success status and file path
70
+ Success status and file information
53
71
  """
54
- logger.debug(f"📝 Writing to file: {path} ({len(content)} characters)")
55
-
72
+ path = args.get("path")
73
+ content = args.get("content", "")
74
+
75
+ if not path:
76
+ raise ValueError("file_writer requires 'path' argument")
77
+
56
78
  try:
57
79
  # Create parent directories if they don't exist
58
- parent_dir = Path(path).parent
59
- if not parent_dir.exists():
60
- logger.debug(f"📁 Creating parent directories: {parent_dir}")
61
- parent_dir.mkdir(parents=True, exist_ok=True)
80
+ file_path = Path(path)
81
+ file_path.parent.mkdir(parents=True, exist_ok=True)
62
82
 
63
- # Write the content to the file
64
- with open(path, "w", encoding="utf-8") as f:
83
+ # Write content to file
84
+ with open(file_path, "w", encoding="utf-8") as f:
65
85
  f.write(content)
66
86
 
67
87
  logger.info(f"✅ File written successfully: {path}")
88
+
68
89
  return {
69
90
  "success": True,
70
- "path": path,
91
+ "path": str(file_path),
71
92
  "message": f"Successfully wrote {len(content)} characters to {path}",
72
93
  }
94
+
73
95
  except Exception as e:
74
- logger.error(f"❌ Failed to write file {path}: {type(e).__name__}: {e}")
96
+ logger.error(f"❌ Failed to write file {path}: {e}")
75
97
  return {
76
98
  "success": False,
77
99
  "path": path,
78
100
  "error": str(e),
79
- "message": f"Failed to write to {path}: {e}",
80
101
  }
81
102
 
82
103
 
83
- # Register the built-in file_writer tool
84
- logger.debug("🏗️ Registering built-in tools...")
104
+ # Register built-in tools
85
105
  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.1
3
+ Version: 0.3.2
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
@@ -443,6 +443,347 @@ orchestrator.register_agent("my-agent", agent)
443
443
  response = orchestrator.send_message("my-agent", {"task": "process"})
444
444
  ```
445
445
 
446
+ ## Usage Patterns: Open Agent Spec vs Independent Client Usage
447
+
448
+ DACP supports two primary usage patterns: integration with Open Agent Specification (OAS) projects and independent client usage. Both provide full access to DACP's capabilities but with different integration approaches.
449
+
450
+ ### Open Agent Specification (OAS) Integration
451
+
452
+ **For OAS developers:** DACP integrates seamlessly with generated agents through YAML configuration and automatic setup.
453
+
454
+ #### YAML Configuration Pattern
455
+
456
+ ```yaml
457
+ # agent_config.yaml (Open Agent Specification)
458
+ apiVersion: "v1"
459
+ kind: "Agent"
460
+ metadata:
461
+ name: "data-analysis-agent"
462
+ type: "smart_analysis"
463
+
464
+ # DACP automatically configures logging
465
+ logging:
466
+ enabled: true
467
+ level: "INFO"
468
+ format_style: "emoji"
469
+ log_file: "./logs/agent.log"
470
+ env_overrides:
471
+ level: "DACP_LOG_LEVEL"
472
+
473
+ # Multi-provider intelligence configuration
474
+ intelligence:
475
+ engine: "anthropic"
476
+ model: "claude-3-haiku-20240618"
477
+ # API key from environment: ANTHROPIC_API_KEY
478
+
479
+ # Define agent capabilities
480
+ capabilities:
481
+ - name: "analyze_data"
482
+ description: "Analyze datasets and generate insights"
483
+ - name: "generate_report"
484
+ description: "Generate analysis reports"
485
+ ```
486
+
487
+ #### Generated Agent Code (OAS Pattern)
488
+
489
+ ```python
490
+ # Generated by OAS with DACP integration
491
+ import dacp
492
+ import yaml
493
+
494
+ class DataAnalysisAgent(dacp.Agent):
495
+ def __init__(self, config_path="agent_config.yaml"):
496
+ # DACP auto-configures logging from YAML
497
+ with open(config_path, 'r') as f:
498
+ self.config = yaml.safe_load(f)
499
+
500
+ # Automatic logging setup
501
+ self.setup_logging()
502
+
503
+ # Load intelligence configuration
504
+ self.intelligence_config = self.config.get('intelligence', {})
505
+
506
+ def setup_logging(self):
507
+ """Auto-configure DACP logging from YAML config."""
508
+ logging_config = self.config.get('logging', {})
509
+ if logging_config.get('enabled', False):
510
+ dacp.setup_dacp_logging(
511
+ level=logging_config.get('level', 'INFO'),
512
+ format_style=logging_config.get('format_style', 'emoji'),
513
+ log_file=logging_config.get('log_file')
514
+ )
515
+
516
+ def handle_message(self, message):
517
+ """Handle capabilities defined in YAML."""
518
+ task = message.get("task")
519
+
520
+ if task == "analyze_data":
521
+ return self.analyze_data(message)
522
+ elif task == "generate_report":
523
+ return self.generate_report(message)
524
+ else:
525
+ return {"error": f"Unknown task: {task}"}
526
+
527
+ def analyze_data(self, message):
528
+ """Analyze data using configured intelligence provider."""
529
+ data = message.get("data", "No data provided")
530
+
531
+ try:
532
+ result = dacp.invoke_intelligence(
533
+ f"Analyze this data and provide insights: {data}",
534
+ self.intelligence_config
535
+ )
536
+ return {"response": result}
537
+ except Exception as e:
538
+ return {"error": f"Analysis failed: {e}"}
539
+
540
+ def generate_report(self, message):
541
+ """Generate reports using DACP's file_writer tool."""
542
+ subject = message.get("subject", "report")
543
+ data = message.get("data", "No data")
544
+
545
+ return {
546
+ "tool_request": {
547
+ "name": "file_writer",
548
+ "args": {
549
+ "path": f"./reports/{subject}.txt",
550
+ "content": f"# Analysis Report: {subject}\n\nData: {data}\n"
551
+ }
552
+ }
553
+ }
554
+
555
+ # Auto-generated main function
556
+ def main():
557
+ # Zero-configuration setup
558
+ orchestrator = dacp.Orchestrator()
559
+ agent = DataAnalysisAgent()
560
+ orchestrator.register_agent("data-analysis-agent", agent)
561
+
562
+ print("🚀 OAS Agent running with DACP integration!")
563
+ # Agent ready for messages via orchestrator
564
+
565
+ if __name__ == "__main__":
566
+ main()
567
+ ```
568
+
569
+ #### OAS Benefits
570
+
571
+ - ✅ **Zero Configuration**: Logging and intelligence work out of the box
572
+ - ✅ **YAML-Driven**: All configuration in standard OAS YAML format
573
+ - ✅ **Auto-Generated**: Complete agents generated from specifications
574
+ - ✅ **Environment Overrides**: Runtime configuration via environment variables
575
+ - ✅ **Standardized**: Consistent interface across all OAS agents
576
+
577
+ ### Independent Client Usage
578
+
579
+ **For independent developers:** Use DACP directly as a flexible agent router and orchestration platform.
580
+
581
+ #### Direct Integration Pattern
582
+
583
+ ```python
584
+ import dacp
585
+ import os
586
+
587
+ class MyCustomAgent(dacp.Agent):
588
+ """Independent client's custom agent."""
589
+
590
+ def __init__(self):
591
+ # Manual setup - full control
592
+ self.setup_intelligence()
593
+ self.setup_logging()
594
+
595
+ def setup_intelligence(self):
596
+ """Configure intelligence providers manually."""
597
+ self.intelligence_configs = {
598
+ "research": {
599
+ "engine": "openai",
600
+ "model": "gpt-4",
601
+ "api_key": os.getenv("OPENAI_API_KEY")
602
+ },
603
+ "analysis": {
604
+ "engine": "anthropic",
605
+ "model": "claude-3-sonnet-20240229",
606
+ "api_key": os.getenv("ANTHROPIC_API_KEY")
607
+ },
608
+ "local": {
609
+ "engine": "local",
610
+ "model": "llama2",
611
+ "endpoint": "http://localhost:11434/api/generate"
612
+ }
613
+ }
614
+
615
+ def setup_logging(self):
616
+ """Configure logging manually."""
617
+ dacp.enable_info_logging(log_file="./logs/custom_agent.log")
618
+
619
+ def handle_message(self, message):
620
+ """Custom business logic."""
621
+ task = message.get("task")
622
+
623
+ if task == "research_topic":
624
+ return self.research_with_multiple_llms(message)
625
+ elif task == "process_data":
626
+ return self.multi_step_processing(message)
627
+ elif task == "custom_workflow":
628
+ return self.handle_custom_workflow(message)
629
+ else:
630
+ return {"error": f"Unknown task: {task}"}
631
+
632
+ def research_with_multiple_llms(self, message):
633
+ """Use multiple LLM providers for comprehensive research."""
634
+ topic = message.get("topic", "AI Research")
635
+
636
+ # Use different LLMs for different aspects
637
+ research_prompt = f"Research the topic: {topic}"
638
+ analysis_prompt = f"Analyze research findings for: {topic}"
639
+
640
+ try:
641
+ # Research with GPT-4
642
+ research = dacp.invoke_intelligence(
643
+ research_prompt,
644
+ self.intelligence_configs["research"]
645
+ )
646
+
647
+ # Analysis with Claude
648
+ analysis = dacp.invoke_intelligence(
649
+ f"Analyze: {research}",
650
+ self.intelligence_configs["analysis"]
651
+ )
652
+
653
+ return {
654
+ "research": research,
655
+ "analysis": analysis,
656
+ "status": "completed"
657
+ }
658
+ except Exception as e:
659
+ return {"error": f"Research failed: {e}"}
660
+
661
+ def multi_step_processing(self, message):
662
+ """Multi-step workflow with tool chaining."""
663
+ data = message.get("data", "sample data")
664
+
665
+ # Step 1: Process and save data
666
+ return {
667
+ "tool_request": {
668
+ "name": "file_writer",
669
+ "args": {
670
+ "path": "./processing/input_data.txt",
671
+ "content": f"Raw data: {data}\nProcessed at: {dacp.time.time()}"
672
+ }
673
+ }
674
+ }
675
+ # In real implementation, would continue workflow in subsequent messages
676
+
677
+ # Independent client setup
678
+ def main():
679
+ # Manual orchestrator setup
680
+ orchestrator = dacp.Orchestrator()
681
+
682
+ # Register multiple custom agents
683
+ research_agent = MyCustomAgent()
684
+ data_agent = MyCustomAgent()
685
+ workflow_agent = MyCustomAgent()
686
+
687
+ orchestrator.register_agent("researcher", research_agent)
688
+ orchestrator.register_agent("processor", data_agent)
689
+ orchestrator.register_agent("workflow", workflow_agent)
690
+
691
+ # Direct control over routing
692
+ print("🚀 Independent client agents running!")
693
+
694
+ # Example: Route complex task across multiple agents
695
+ research_result = orchestrator.send_message("researcher", {
696
+ "task": "research_topic",
697
+ "topic": "Multi-Agent Systems"
698
+ })
699
+
700
+ processing_result = orchestrator.send_message("processor", {
701
+ "task": "process_data",
702
+ "data": research_result
703
+ })
704
+
705
+ # Broadcast updates to all agents
706
+ orchestrator.broadcast_message({
707
+ "task": "status_update",
708
+ "message": "Workflow completed"
709
+ })
710
+
711
+ if __name__ == "__main__":
712
+ main()
713
+ ```
714
+
715
+ #### Advanced Independent Usage
716
+
717
+ ```python
718
+ # Register custom tools for specialized business logic
719
+ def custom_data_processor(args):
720
+ """Client's proprietary data processing tool."""
721
+ data = args.get("data", [])
722
+ algorithm = args.get("algorithm", "default")
723
+
724
+ # Custom processing logic
725
+ processed = [item * 2 for item in data if isinstance(item, (int, float))]
726
+
727
+ return {
728
+ "success": True,
729
+ "processed_data": processed,
730
+ "algorithm_used": algorithm,
731
+ "count": len(processed)
732
+ }
733
+
734
+ # Register with DACP
735
+ dacp.register_tool("custom_processor", custom_data_processor)
736
+
737
+ # Use in agents
738
+ class SpecializedAgent(dacp.Agent):
739
+ def handle_message(self, message):
740
+ if message.get("task") == "process_with_custom_tool":
741
+ return {
742
+ "tool_request": {
743
+ "name": "custom_processor",
744
+ "args": {
745
+ "data": message.get("data", []),
746
+ "algorithm": "proprietary_v2"
747
+ }
748
+ }
749
+ }
750
+ ```
751
+
752
+ #### Independent Client Benefits
753
+
754
+ - ✅ **Full Control**: Manual configuration of all components
755
+ - ✅ **Flexible Architecture**: Design your own agent interactions
756
+ - ✅ **Custom Tools**: Register proprietary business logic tools
757
+ - ✅ **Multi-Provider**: Use different LLMs for different tasks
758
+ - ✅ **Direct API Access**: Call DACP functions directly when needed
759
+ - ✅ **Complex Workflows**: Build sophisticated multi-agent orchestrations
760
+
761
+ ### Choosing Your Pattern
762
+
763
+ | Feature | OAS Integration | Independent Client |
764
+ |---------|----------------|-------------------|
765
+ | **Setup Complexity** | Minimal (auto-generated) | Manual (full control) |
766
+ | **Configuration** | YAML-driven | Programmatic |
767
+ | **Agent Generation** | Automatic from spec | Manual implementation |
768
+ | **Customization** | Template-based | Unlimited flexibility |
769
+ | **Best For** | Rapid prototyping, standard agents | Complex workflows, custom logic |
770
+ | **Learning Curve** | Low | Medium |
771
+
772
+ ### Getting Started
773
+
774
+ **For OAS Integration:**
775
+ 1. Add DACP logging section to your YAML spec
776
+ 2. Generate agents with DACP base class
777
+ 3. Agents work with zero additional configuration
778
+
779
+ **For Independent Usage:**
780
+ 1. `pip install dacp`
781
+ 2. Create agents inheriting from `dacp.Agent`
782
+ 3. Register with `dacp.Orchestrator()`
783
+ 4. Build your custom workflows
784
+
785
+ Both patterns provide full access to DACP's capabilities: multi-provider LLM routing, tool execution, comprehensive logging, conversation history, and multi-agent orchestration.
786
+
446
787
  ## Development
447
788
 
448
789
  ```bash
@@ -0,0 +1,15 @@
1
+ dacp/__init__.py,sha256=5EUO_gnPX7DISwAuPPkDSWrHoH9MNEoDRjDG16c7q_w,1351
2
+ dacp/exceptions.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ dacp/intelligence.py,sha256=z8RqRYXKKKDd9MCm1KLBNQ3DJ9Zl4ZntbjFcaqtuv9s,9713
4
+ dacp/llm.py,sha256=K78QefD3LCOBTrsNHtfRs-UzcHNYCJNxcJ28HGirwfU,1064
5
+ dacp/logging_config.py,sha256=g5iMe9mloZag4oFQ9FQrRTikDTCI-XxeTGX0Y1KXVMw,3927
6
+ dacp/main.py,sha256=YReioZotkURiYMuMLVf_-hPzVPSpqA5Esgwz7D36DhE,229
7
+ dacp/orchestrator.py,sha256=5GxAVQFYW6OkNB8O5TT2eaJtQat7YUN_je7j-V0kXNM,9315
8
+ dacp/protocol.py,sha256=DVhLTdyDVlAu8ETSEX8trPeycKfMeirHwcWQ8-BY7eA,1026
9
+ dacp/tools.py,sha256=wfuUQ12UVvyMLUcDA4GGxwwzQJ-k4ftWbewg7qwNQGg,2872
10
+ dacp/types.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ dacp-0.3.2.dist-info/licenses/LICENSE,sha256=tb5kgUYRypHqAy8wlrJUBSYI5l1SBmawSYHmCC-MVW0,1074
12
+ dacp-0.3.2.dist-info/METADATA,sha256=RHlO4D7HV5kNPendOYH9yd3c5hzDOsIKf8bMSJcaNEk,25756
13
+ dacp-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ dacp-0.3.2.dist-info/top_level.txt,sha256=Qxy0cy5jl7ttTQoGFlY9LXB6CbSvsekJ2y0P8I7L1zA,5
15
+ dacp-0.3.2.dist-info/RECORD,,
@@ -1,15 +0,0 @@
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,,
File without changes