dacp 0.1.0__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.
- dacp-0.1.0/LICENSE +21 -0
- dacp-0.1.0/PKG-INFO +114 -0
- dacp-0.1.0/README.md +82 -0
- dacp-0.1.0/dacp/__init__.py +31 -0
- dacp-0.1.0/dacp/exceptions.py +0 -0
- dacp-0.1.0/dacp/llm.py +19 -0
- dacp-0.1.0/dacp/protocol.py +35 -0
- dacp-0.1.0/dacp/tools.py +30 -0
- dacp-0.1.0/dacp/types.py +0 -0
- dacp-0.1.0/dacp.egg-info/PKG-INFO +114 -0
- dacp-0.1.0/dacp.egg-info/SOURCES.txt +17 -0
- dacp-0.1.0/dacp.egg-info/dependency_links.txt +1 -0
- dacp-0.1.0/dacp.egg-info/requires.txt +9 -0
- dacp-0.1.0/dacp.egg-info/top_level.txt +1 -0
- dacp-0.1.0/pyproject.toml +101 -0
- dacp-0.1.0/setup.cfg +4 -0
- dacp-0.1.0/tests/test_llm.py +116 -0
- dacp-0.1.0/tests/test_protocol.py +116 -0
- dacp-0.1.0/tests/test_tools.py +72 -0
dacp-0.1.0/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 DACP Contributors
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
dacp-0.1.0/PKG-INFO
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: dacp
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: DACP - Declarative Agent Communication Protocol for LLM/agent communications and tool function calls
|
5
|
+
Author-email: Your Name <your.email@example.com>
|
6
|
+
License: MIT
|
7
|
+
Project-URL: Homepage, https://github.com/yourusername/dacp
|
8
|
+
Project-URL: Repository, https://github.com/yourusername/dacp
|
9
|
+
Project-URL: Issues, https://github.com/yourusername/dacp/issues
|
10
|
+
Keywords: llm,agent,tools,openai,communication
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
12
|
+
Classifier: Intended Audience :: Developers
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
20
|
+
Requires-Python: >=3.8
|
21
|
+
Description-Content-Type: text/markdown
|
22
|
+
License-File: LICENSE
|
23
|
+
Requires-Dist: openai>=1.0.0
|
24
|
+
Provides-Extra: dev
|
25
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
26
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
27
|
+
Requires-Dist: black>=22.0.0; extra == "dev"
|
28
|
+
Requires-Dist: flake8>=5.0.0; extra == "dev"
|
29
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
30
|
+
Requires-Dist: types-requests>=2.28.0; extra == "dev"
|
31
|
+
Dynamic: license-file
|
32
|
+
|
33
|
+
# DACP - Delcarative Agent Communication Protocol
|
34
|
+
|
35
|
+
A Python library for managing LLM/agent communications and tool function calls following the OAS Open Agent Specification.
|
36
|
+
|
37
|
+
## Installation
|
38
|
+
|
39
|
+
```bash
|
40
|
+
pip install -e .
|
41
|
+
```
|
42
|
+
|
43
|
+
## Quick Start
|
44
|
+
|
45
|
+
```python
|
46
|
+
import dacp
|
47
|
+
|
48
|
+
# Register a custom tool
|
49
|
+
def my_custom_tool(param1: str, param2: int) -> dict:
|
50
|
+
return {"result": f"Processed {param1} with {param2}"}
|
51
|
+
|
52
|
+
dacp.register_tool("my_custom_tool", my_custom_tool)
|
53
|
+
|
54
|
+
# Call an LLM
|
55
|
+
response = dacp.call_llm("What is the weather like today?")
|
56
|
+
|
57
|
+
# Parse agent response
|
58
|
+
parsed = dacp.parse_agent_response(response)
|
59
|
+
|
60
|
+
# Check if it's a tool request
|
61
|
+
if dacp.is_tool_request(parsed):
|
62
|
+
tool_name, args = dacp.get_tool_request(parsed)
|
63
|
+
result = dacp.run_tool(tool_name, args)
|
64
|
+
tool_response = dacp.wrap_tool_result(tool_name, result)
|
65
|
+
```
|
66
|
+
|
67
|
+
## Features
|
68
|
+
|
69
|
+
- **Tool Registry**: Register and manage custom tools for LLM agents
|
70
|
+
- **LLM Integration**: Built-in support for OpenAI models (extensible)
|
71
|
+
- **Protocol Parsing**: Parse and validate agent responses
|
72
|
+
- **Tool Execution**: Safe execution of registered tools
|
73
|
+
- **OAS Compliance**: Follows Open Agent Specification standards
|
74
|
+
|
75
|
+
## API Reference
|
76
|
+
|
77
|
+
### Tools
|
78
|
+
|
79
|
+
- `register_tool(tool_id: str, func)`: Register a new tool
|
80
|
+
- `run_tool(tool_id: str, args: Dict) -> dict`: Execute a registered tool
|
81
|
+
- `TOOL_REGISTRY`: Access the current tool registry
|
82
|
+
|
83
|
+
### LLM
|
84
|
+
|
85
|
+
- `call_llm(prompt: str, model: str = "gpt-4") -> str`: Call an LLM with a prompt
|
86
|
+
|
87
|
+
### Protocol
|
88
|
+
|
89
|
+
- `parse_agent_response(response: str | dict) -> dict`: Parse agent response
|
90
|
+
- `is_tool_request(msg: dict) -> bool`: Check if message is a tool request
|
91
|
+
- `get_tool_request(msg: dict) -> tuple[str, dict]`: Extract tool request details
|
92
|
+
- `wrap_tool_result(name: str, result: dict) -> dict`: Wrap tool result for agent
|
93
|
+
- `is_final_response(msg: dict) -> bool`: Check if message is a final response
|
94
|
+
- `get_final_response(msg: dict) -> dict`: Extract final response
|
95
|
+
|
96
|
+
## Development
|
97
|
+
|
98
|
+
```bash
|
99
|
+
# Install development dependencies
|
100
|
+
pip install -e .[dev]
|
101
|
+
|
102
|
+
# Run tests
|
103
|
+
pytest
|
104
|
+
|
105
|
+
# Format code
|
106
|
+
black .
|
107
|
+
|
108
|
+
# Lint code
|
109
|
+
flake8
|
110
|
+
```
|
111
|
+
|
112
|
+
## License
|
113
|
+
|
114
|
+
MIT License
|
dacp-0.1.0/README.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# DACP - Delcarative Agent Communication Protocol
|
2
|
+
|
3
|
+
A Python library for managing LLM/agent communications and tool function calls following the OAS Open Agent Specification.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```bash
|
8
|
+
pip install -e .
|
9
|
+
```
|
10
|
+
|
11
|
+
## Quick Start
|
12
|
+
|
13
|
+
```python
|
14
|
+
import dacp
|
15
|
+
|
16
|
+
# Register a custom tool
|
17
|
+
def my_custom_tool(param1: str, param2: int) -> dict:
|
18
|
+
return {"result": f"Processed {param1} with {param2}"}
|
19
|
+
|
20
|
+
dacp.register_tool("my_custom_tool", my_custom_tool)
|
21
|
+
|
22
|
+
# Call an LLM
|
23
|
+
response = dacp.call_llm("What is the weather like today?")
|
24
|
+
|
25
|
+
# Parse agent response
|
26
|
+
parsed = dacp.parse_agent_response(response)
|
27
|
+
|
28
|
+
# Check if it's a tool request
|
29
|
+
if dacp.is_tool_request(parsed):
|
30
|
+
tool_name, args = dacp.get_tool_request(parsed)
|
31
|
+
result = dacp.run_tool(tool_name, args)
|
32
|
+
tool_response = dacp.wrap_tool_result(tool_name, result)
|
33
|
+
```
|
34
|
+
|
35
|
+
## Features
|
36
|
+
|
37
|
+
- **Tool Registry**: Register and manage custom tools for LLM agents
|
38
|
+
- **LLM Integration**: Built-in support for OpenAI models (extensible)
|
39
|
+
- **Protocol Parsing**: Parse and validate agent responses
|
40
|
+
- **Tool Execution**: Safe execution of registered tools
|
41
|
+
- **OAS Compliance**: Follows Open Agent Specification standards
|
42
|
+
|
43
|
+
## API Reference
|
44
|
+
|
45
|
+
### Tools
|
46
|
+
|
47
|
+
- `register_tool(tool_id: str, func)`: Register a new tool
|
48
|
+
- `run_tool(tool_id: str, args: Dict) -> dict`: Execute a registered tool
|
49
|
+
- `TOOL_REGISTRY`: Access the current tool registry
|
50
|
+
|
51
|
+
### LLM
|
52
|
+
|
53
|
+
- `call_llm(prompt: str, model: str = "gpt-4") -> str`: Call an LLM with a prompt
|
54
|
+
|
55
|
+
### Protocol
|
56
|
+
|
57
|
+
- `parse_agent_response(response: str | dict) -> dict`: Parse agent response
|
58
|
+
- `is_tool_request(msg: dict) -> bool`: Check if message is a tool request
|
59
|
+
- `get_tool_request(msg: dict) -> tuple[str, dict]`: Extract tool request details
|
60
|
+
- `wrap_tool_result(name: str, result: dict) -> dict`: Wrap tool result for agent
|
61
|
+
- `is_final_response(msg: dict) -> bool`: Check if message is a final response
|
62
|
+
- `get_final_response(msg: dict) -> dict`: Extract final response
|
63
|
+
|
64
|
+
## Development
|
65
|
+
|
66
|
+
```bash
|
67
|
+
# Install development dependencies
|
68
|
+
pip install -e .[dev]
|
69
|
+
|
70
|
+
# Run tests
|
71
|
+
pytest
|
72
|
+
|
73
|
+
# Format code
|
74
|
+
black .
|
75
|
+
|
76
|
+
# Lint code
|
77
|
+
flake8
|
78
|
+
```
|
79
|
+
|
80
|
+
## License
|
81
|
+
|
82
|
+
MIT License
|
@@ -0,0 +1,31 @@
|
|
1
|
+
"""
|
2
|
+
DACP - Declarative Agent Communication Protocol
|
3
|
+
|
4
|
+
A Python library for managing LLM/agent communications and tool function calls
|
5
|
+
following the OAS Open Agent Specification.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from .tools import register_tool, run_tool, TOOL_REGISTRY
|
9
|
+
from .llm import call_llm
|
10
|
+
from .protocol import (
|
11
|
+
parse_agent_response,
|
12
|
+
is_tool_request,
|
13
|
+
get_tool_request,
|
14
|
+
wrap_tool_result,
|
15
|
+
is_final_response,
|
16
|
+
get_final_response,
|
17
|
+
)
|
18
|
+
|
19
|
+
__version__ = "0.1.0"
|
20
|
+
__all__ = [
|
21
|
+
"register_tool",
|
22
|
+
"run_tool",
|
23
|
+
"TOOL_REGISTRY",
|
24
|
+
"call_llm",
|
25
|
+
"parse_agent_response",
|
26
|
+
"is_tool_request",
|
27
|
+
"get_tool_request",
|
28
|
+
"wrap_tool_result",
|
29
|
+
"is_final_response",
|
30
|
+
"get_final_response",
|
31
|
+
]
|
File without changes
|
dacp-0.1.0/dacp/llm.py
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
import os
|
2
|
+
import openai
|
3
|
+
|
4
|
+
|
5
|
+
def call_llm(prompt: str, model: str = "gpt-4") -> str:
|
6
|
+
# Default: OpenAI, can later extend for other LLMs
|
7
|
+
client = openai.OpenAI(
|
8
|
+
api_key=os.getenv("OPENAI_API_KEY"), base_url="https://api.openai.com/v1"
|
9
|
+
)
|
10
|
+
response = client.chat.completions.create(
|
11
|
+
model=model,
|
12
|
+
messages=[{"role": "user", "content": prompt}],
|
13
|
+
temperature=0.7,
|
14
|
+
max_tokens=150,
|
15
|
+
)
|
16
|
+
content = response.choices[0].message.content
|
17
|
+
if content is None:
|
18
|
+
raise ValueError("LLM returned empty response")
|
19
|
+
return content
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import json
|
2
|
+
from typing import Dict, Any, Union, Tuple, cast
|
3
|
+
|
4
|
+
|
5
|
+
def parse_agent_response(response: Union[str, Dict[str, Any]]) -> Dict[str, Any]:
|
6
|
+
"""
|
7
|
+
Parse the agent/LLM response (as string or dict) and return a dict.
|
8
|
+
"""
|
9
|
+
if isinstance(response, dict):
|
10
|
+
return response
|
11
|
+
try:
|
12
|
+
return cast(Dict[str, Any], json.loads(response))
|
13
|
+
except Exception as e:
|
14
|
+
raise ValueError(f"Malformed agent response: {e}")
|
15
|
+
|
16
|
+
|
17
|
+
def is_tool_request(msg: Dict[str, Any]) -> bool:
|
18
|
+
return "tool_request" in msg
|
19
|
+
|
20
|
+
|
21
|
+
def get_tool_request(msg: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]:
|
22
|
+
req = msg["tool_request"]
|
23
|
+
return req["name"], req.get("args", {})
|
24
|
+
|
25
|
+
|
26
|
+
def wrap_tool_result(name: str, result: Dict[str, Any]) -> Dict[str, Any]:
|
27
|
+
return {"tool_result": {"name": name, "result": result}}
|
28
|
+
|
29
|
+
|
30
|
+
def is_final_response(msg: Dict[str, Any]) -> bool:
|
31
|
+
return "final_response" in msg
|
32
|
+
|
33
|
+
|
34
|
+
def get_final_response(msg: Dict[str, Any]) -> Dict[str, Any]:
|
35
|
+
return cast(Dict[str, Any], msg["final_response"])
|
dacp-0.1.0/dacp/tools.py
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
from typing import Dict, Any, Callable
|
2
|
+
|
3
|
+
TOOL_REGISTRY: Dict[str, Callable[..., Dict[str, Any]]] = {}
|
4
|
+
|
5
|
+
|
6
|
+
def register_tool(tool_id: str, func: Callable[..., Dict[str, Any]]) -> None:
|
7
|
+
"""Register a tool function."""
|
8
|
+
TOOL_REGISTRY[tool_id] = func
|
9
|
+
|
10
|
+
|
11
|
+
def run_tool(tool_id: str, args: Dict[str, Any]) -> Dict[str, Any]:
|
12
|
+
"""Run a registered tool with the given arguments."""
|
13
|
+
if tool_id not in TOOL_REGISTRY:
|
14
|
+
raise ValueError(f"Unknown tool: {tool_id}")
|
15
|
+
tool_func = TOOL_REGISTRY[tool_id]
|
16
|
+
return tool_func(**args)
|
17
|
+
|
18
|
+
|
19
|
+
# --- Example tool ---
|
20
|
+
def file_writer(path: str, content: str) -> dict:
|
21
|
+
# Only allow paths in /tmp or ./output for now!
|
22
|
+
allowed_prefixes = ["./output/", "/tmp/"]
|
23
|
+
if not any(path.startswith(prefix) for prefix in allowed_prefixes):
|
24
|
+
raise ValueError("Path not allowed")
|
25
|
+
with open(path, "w") as f:
|
26
|
+
f.write(content)
|
27
|
+
return {"result": f"Written to {path}"}
|
28
|
+
|
29
|
+
|
30
|
+
register_tool("file_writer", file_writer)
|
dacp-0.1.0/dacp/types.py
ADDED
File without changes
|
@@ -0,0 +1,114 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: dacp
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: DACP - Declarative Agent Communication Protocol for LLM/agent communications and tool function calls
|
5
|
+
Author-email: Your Name <your.email@example.com>
|
6
|
+
License: MIT
|
7
|
+
Project-URL: Homepage, https://github.com/yourusername/dacp
|
8
|
+
Project-URL: Repository, https://github.com/yourusername/dacp
|
9
|
+
Project-URL: Issues, https://github.com/yourusername/dacp/issues
|
10
|
+
Keywords: llm,agent,tools,openai,communication
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
12
|
+
Classifier: Intended Audience :: Developers
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
20
|
+
Requires-Python: >=3.8
|
21
|
+
Description-Content-Type: text/markdown
|
22
|
+
License-File: LICENSE
|
23
|
+
Requires-Dist: openai>=1.0.0
|
24
|
+
Provides-Extra: dev
|
25
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
26
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
27
|
+
Requires-Dist: black>=22.0.0; extra == "dev"
|
28
|
+
Requires-Dist: flake8>=5.0.0; extra == "dev"
|
29
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
30
|
+
Requires-Dist: types-requests>=2.28.0; extra == "dev"
|
31
|
+
Dynamic: license-file
|
32
|
+
|
33
|
+
# DACP - Delcarative Agent Communication Protocol
|
34
|
+
|
35
|
+
A Python library for managing LLM/agent communications and tool function calls following the OAS Open Agent Specification.
|
36
|
+
|
37
|
+
## Installation
|
38
|
+
|
39
|
+
```bash
|
40
|
+
pip install -e .
|
41
|
+
```
|
42
|
+
|
43
|
+
## Quick Start
|
44
|
+
|
45
|
+
```python
|
46
|
+
import dacp
|
47
|
+
|
48
|
+
# Register a custom tool
|
49
|
+
def my_custom_tool(param1: str, param2: int) -> dict:
|
50
|
+
return {"result": f"Processed {param1} with {param2}"}
|
51
|
+
|
52
|
+
dacp.register_tool("my_custom_tool", my_custom_tool)
|
53
|
+
|
54
|
+
# Call an LLM
|
55
|
+
response = dacp.call_llm("What is the weather like today?")
|
56
|
+
|
57
|
+
# Parse agent response
|
58
|
+
parsed = dacp.parse_agent_response(response)
|
59
|
+
|
60
|
+
# Check if it's a tool request
|
61
|
+
if dacp.is_tool_request(parsed):
|
62
|
+
tool_name, args = dacp.get_tool_request(parsed)
|
63
|
+
result = dacp.run_tool(tool_name, args)
|
64
|
+
tool_response = dacp.wrap_tool_result(tool_name, result)
|
65
|
+
```
|
66
|
+
|
67
|
+
## Features
|
68
|
+
|
69
|
+
- **Tool Registry**: Register and manage custom tools for LLM agents
|
70
|
+
- **LLM Integration**: Built-in support for OpenAI models (extensible)
|
71
|
+
- **Protocol Parsing**: Parse and validate agent responses
|
72
|
+
- **Tool Execution**: Safe execution of registered tools
|
73
|
+
- **OAS Compliance**: Follows Open Agent Specification standards
|
74
|
+
|
75
|
+
## API Reference
|
76
|
+
|
77
|
+
### Tools
|
78
|
+
|
79
|
+
- `register_tool(tool_id: str, func)`: Register a new tool
|
80
|
+
- `run_tool(tool_id: str, args: Dict) -> dict`: Execute a registered tool
|
81
|
+
- `TOOL_REGISTRY`: Access the current tool registry
|
82
|
+
|
83
|
+
### LLM
|
84
|
+
|
85
|
+
- `call_llm(prompt: str, model: str = "gpt-4") -> str`: Call an LLM with a prompt
|
86
|
+
|
87
|
+
### Protocol
|
88
|
+
|
89
|
+
- `parse_agent_response(response: str | dict) -> dict`: Parse agent response
|
90
|
+
- `is_tool_request(msg: dict) -> bool`: Check if message is a tool request
|
91
|
+
- `get_tool_request(msg: dict) -> tuple[str, dict]`: Extract tool request details
|
92
|
+
- `wrap_tool_result(name: str, result: dict) -> dict`: Wrap tool result for agent
|
93
|
+
- `is_final_response(msg: dict) -> bool`: Check if message is a final response
|
94
|
+
- `get_final_response(msg: dict) -> dict`: Extract final response
|
95
|
+
|
96
|
+
## Development
|
97
|
+
|
98
|
+
```bash
|
99
|
+
# Install development dependencies
|
100
|
+
pip install -e .[dev]
|
101
|
+
|
102
|
+
# Run tests
|
103
|
+
pytest
|
104
|
+
|
105
|
+
# Format code
|
106
|
+
black .
|
107
|
+
|
108
|
+
# Lint code
|
109
|
+
flake8
|
110
|
+
```
|
111
|
+
|
112
|
+
## License
|
113
|
+
|
114
|
+
MIT License
|
@@ -0,0 +1,17 @@
|
|
1
|
+
LICENSE
|
2
|
+
README.md
|
3
|
+
pyproject.toml
|
4
|
+
dacp/__init__.py
|
5
|
+
dacp/exceptions.py
|
6
|
+
dacp/llm.py
|
7
|
+
dacp/protocol.py
|
8
|
+
dacp/tools.py
|
9
|
+
dacp/types.py
|
10
|
+
dacp.egg-info/PKG-INFO
|
11
|
+
dacp.egg-info/SOURCES.txt
|
12
|
+
dacp.egg-info/dependency_links.txt
|
13
|
+
dacp.egg-info/requires.txt
|
14
|
+
dacp.egg-info/top_level.txt
|
15
|
+
tests/test_llm.py
|
16
|
+
tests/test_protocol.py
|
17
|
+
tests/test_tools.py
|
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
dacp
|
@@ -0,0 +1,101 @@
|
|
1
|
+
[build-system]
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
3
|
+
build-backend = "setuptools.build_meta"
|
4
|
+
|
5
|
+
[project]
|
6
|
+
name = "dacp"
|
7
|
+
version = "0.1.0"
|
8
|
+
description = "DACP - Declarative Agent Communication Protocol for LLM/agent communications and tool function calls"
|
9
|
+
readme = "README.md"
|
10
|
+
requires-python = ">=3.8"
|
11
|
+
license = {text = "MIT"}
|
12
|
+
authors = [
|
13
|
+
{name = "Your Name", email = "your.email@example.com"}
|
14
|
+
]
|
15
|
+
keywords = ["llm", "agent", "tools", "openai", "communication"]
|
16
|
+
classifiers = [
|
17
|
+
"Development Status :: 3 - Alpha",
|
18
|
+
"Intended Audience :: Developers",
|
19
|
+
"License :: OSI Approved :: MIT License",
|
20
|
+
"Programming Language :: Python :: 3",
|
21
|
+
"Programming Language :: Python :: 3.8",
|
22
|
+
"Programming Language :: Python :: 3.9",
|
23
|
+
"Programming Language :: Python :: 3.10",
|
24
|
+
"Programming Language :: Python :: 3.11",
|
25
|
+
"Programming Language :: Python :: 3.12",
|
26
|
+
]
|
27
|
+
|
28
|
+
dependencies = [
|
29
|
+
"openai>=1.0.0",
|
30
|
+
]
|
31
|
+
|
32
|
+
[project.optional-dependencies]
|
33
|
+
dev = [
|
34
|
+
"pytest>=7.0.0",
|
35
|
+
"pytest-cov>=4.0.0",
|
36
|
+
"black>=22.0.0",
|
37
|
+
"flake8>=5.0.0",
|
38
|
+
"mypy>=1.0.0",
|
39
|
+
"types-requests>=2.28.0",
|
40
|
+
]
|
41
|
+
|
42
|
+
[project.urls]
|
43
|
+
Homepage = "https://github.com/yourusername/dacp"
|
44
|
+
Repository = "https://github.com/yourusername/dacp"
|
45
|
+
Issues = "https://github.com/yourusername/dacp/issues"
|
46
|
+
|
47
|
+
[tool.setuptools.packages.find]
|
48
|
+
where = ["."]
|
49
|
+
include = ["dacp*"]
|
50
|
+
exclude = ["tests*"]
|
51
|
+
|
52
|
+
[tool.black]
|
53
|
+
line-length = 88
|
54
|
+
target-version = ['py38']
|
55
|
+
|
56
|
+
[tool.pytest.ini_options]
|
57
|
+
testpaths = ["tests"]
|
58
|
+
python_files = ["test_*.py"]
|
59
|
+
addopts = "--cov=dacp --cov-report=term-missing --cov-report=html"
|
60
|
+
|
61
|
+
[tool.coverage.run]
|
62
|
+
source = ["dacp"]
|
63
|
+
omit = ["tests/*"]
|
64
|
+
|
65
|
+
[tool.coverage.report]
|
66
|
+
exclude_lines = [
|
67
|
+
"pragma: no cover",
|
68
|
+
"def __repr__",
|
69
|
+
"if self.debug:",
|
70
|
+
"if settings.DEBUG",
|
71
|
+
"raise AssertionError",
|
72
|
+
"raise NotImplementedError",
|
73
|
+
"if 0:",
|
74
|
+
"if __name__ == .__main__.:",
|
75
|
+
"class .*\\bProtocol\\):",
|
76
|
+
"@(abc\\.)?abstractmethod",
|
77
|
+
]
|
78
|
+
|
79
|
+
[tool.flake8]
|
80
|
+
max-line-length = 88
|
81
|
+
|
82
|
+
[tool.mypy]
|
83
|
+
python_version = "3.8"
|
84
|
+
warn_return_any = true
|
85
|
+
warn_unused_configs = true
|
86
|
+
disallow_untyped_defs = true
|
87
|
+
disallow_incomplete_defs = true
|
88
|
+
check_untyped_defs = true
|
89
|
+
disallow_untyped_decorators = true
|
90
|
+
no_implicit_optional = true
|
91
|
+
warn_redundant_casts = true
|
92
|
+
warn_unused_ignores = true
|
93
|
+
warn_no_return = true
|
94
|
+
warn_unreachable = true
|
95
|
+
strict_equality = true
|
96
|
+
|
97
|
+
[[tool.mypy.overrides]]
|
98
|
+
module = [
|
99
|
+
"openai.*",
|
100
|
+
]
|
101
|
+
ignore_missing_imports = true
|
dacp-0.1.0/setup.cfg
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
import pytest
|
2
|
+
from unittest.mock import patch, MagicMock
|
3
|
+
import os
|
4
|
+
from dacp.llm import call_llm
|
5
|
+
|
6
|
+
|
7
|
+
@patch("dacp.llm.openai")
|
8
|
+
def test_call_llm_success(mock_openai):
|
9
|
+
"""Test successful LLM call."""
|
10
|
+
# Mock the OpenAI client and response
|
11
|
+
mock_client = MagicMock()
|
12
|
+
mock_openai.OpenAI.return_value = mock_client
|
13
|
+
|
14
|
+
mock_response = MagicMock()
|
15
|
+
mock_response.choices = [MagicMock()]
|
16
|
+
mock_response.choices[0].message.content = "Hello, world!"
|
17
|
+
mock_client.chat.completions.create.return_value = mock_response
|
18
|
+
|
19
|
+
# Test the function
|
20
|
+
result = call_llm("Test prompt")
|
21
|
+
|
22
|
+
# Verify the result
|
23
|
+
assert result == "Hello, world!"
|
24
|
+
|
25
|
+
# Verify OpenAI was called correctly
|
26
|
+
mock_openai.OpenAI.assert_called_once_with(
|
27
|
+
api_key=os.getenv("OPENAI_API_KEY"), base_url="https://api.openai.com/v1"
|
28
|
+
)
|
29
|
+
mock_client.chat.completions.create.assert_called_once_with(
|
30
|
+
model="gpt-4",
|
31
|
+
messages=[{"role": "user", "content": "Test prompt"}],
|
32
|
+
temperature=0.7,
|
33
|
+
max_tokens=150,
|
34
|
+
)
|
35
|
+
|
36
|
+
|
37
|
+
@patch("dacp.llm.openai")
|
38
|
+
def test_call_llm_custom_model(mock_openai):
|
39
|
+
"""Test LLM call with custom model."""
|
40
|
+
mock_client = MagicMock()
|
41
|
+
mock_openai.OpenAI.return_value = mock_client
|
42
|
+
|
43
|
+
mock_response = MagicMock()
|
44
|
+
mock_response.choices = [MagicMock()]
|
45
|
+
mock_response.choices[0].message.content = "Custom model response"
|
46
|
+
mock_client.chat.completions.create.return_value = mock_response
|
47
|
+
|
48
|
+
# Test with custom model
|
49
|
+
result = call_llm("Test prompt", model="gpt-3.5-turbo")
|
50
|
+
|
51
|
+
assert result == "Custom model response"
|
52
|
+
|
53
|
+
# Verify custom model was used
|
54
|
+
mock_client.chat.completions.create.assert_called_once_with(
|
55
|
+
model="gpt-3.5-turbo",
|
56
|
+
messages=[{"role": "user", "content": "Test prompt"}],
|
57
|
+
temperature=0.7,
|
58
|
+
max_tokens=150,
|
59
|
+
)
|
60
|
+
|
61
|
+
|
62
|
+
@patch("dacp.llm.openai")
|
63
|
+
def test_call_llm_api_error(mock_openai):
|
64
|
+
"""Test LLM call with API error."""
|
65
|
+
mock_client = MagicMock()
|
66
|
+
mock_openai.OpenAI.return_value = mock_client
|
67
|
+
|
68
|
+
# Mock an API error
|
69
|
+
mock_client.chat.completions.create.side_effect = Exception("API Error")
|
70
|
+
|
71
|
+
# Test that the error is raised
|
72
|
+
with pytest.raises(Exception):
|
73
|
+
call_llm("Test prompt")
|
74
|
+
|
75
|
+
|
76
|
+
@patch("dacp.llm.openai")
|
77
|
+
def test_call_llm_environment_variable(mock_openai):
|
78
|
+
"""Test that the function uses the OPENAI_API_KEY environment variable."""
|
79
|
+
# Set a test API key
|
80
|
+
test_api_key = "test-api-key-123"
|
81
|
+
with patch.dict(os.environ, {"OPENAI_API_KEY": test_api_key}):
|
82
|
+
mock_client = MagicMock()
|
83
|
+
mock_openai.OpenAI.return_value = mock_client
|
84
|
+
|
85
|
+
mock_response = MagicMock()
|
86
|
+
mock_response.choices = [MagicMock()]
|
87
|
+
mock_response.choices[0].message.content = "Test response"
|
88
|
+
mock_client.chat.completions.create.return_value = mock_response
|
89
|
+
|
90
|
+
call_llm("Test prompt")
|
91
|
+
|
92
|
+
# Verify the API key was used
|
93
|
+
mock_openai.OpenAI.assert_called_once_with(
|
94
|
+
api_key=test_api_key, base_url="https://api.openai.com/v1"
|
95
|
+
)
|
96
|
+
|
97
|
+
|
98
|
+
@patch("dacp.llm.openai")
|
99
|
+
def test_call_llm_default_parameters(mock_openai):
|
100
|
+
"""Test that default parameters are used correctly."""
|
101
|
+
mock_client = MagicMock()
|
102
|
+
mock_openai.OpenAI.return_value = mock_client
|
103
|
+
|
104
|
+
mock_response = MagicMock()
|
105
|
+
mock_response.choices = [MagicMock()]
|
106
|
+
mock_response.choices[0].message.content = "Default response"
|
107
|
+
mock_client.chat.completions.create.return_value = mock_response
|
108
|
+
|
109
|
+
call_llm("Test prompt")
|
110
|
+
|
111
|
+
# Verify default parameters
|
112
|
+
call_args = mock_client.chat.completions.create.call_args
|
113
|
+
assert call_args[1]["model"] == "gpt-4"
|
114
|
+
assert call_args[1]["temperature"] == 0.7
|
115
|
+
assert call_args[1]["max_tokens"] == 150
|
116
|
+
assert call_args[1]["messages"] == [{"role": "user", "content": "Test prompt"}]
|
@@ -0,0 +1,116 @@
|
|
1
|
+
import pytest
|
2
|
+
import json
|
3
|
+
from dacp.protocol import (
|
4
|
+
parse_agent_response,
|
5
|
+
is_tool_request,
|
6
|
+
get_tool_request,
|
7
|
+
wrap_tool_result,
|
8
|
+
is_final_response,
|
9
|
+
get_final_response,
|
10
|
+
)
|
11
|
+
|
12
|
+
|
13
|
+
def test_parse_agent_response_dict():
|
14
|
+
"""Test parsing agent response that's already a dict."""
|
15
|
+
response = {"tool_request": {"name": "test_tool", "args": {"param": "value"}}}
|
16
|
+
result = parse_agent_response(response)
|
17
|
+
assert result == response
|
18
|
+
|
19
|
+
|
20
|
+
def test_parse_agent_response_string():
|
21
|
+
"""Test parsing agent response that's a JSON string."""
|
22
|
+
response_dict = {"tool_request": {"name": "test_tool", "args": {"param": "value"}}}
|
23
|
+
response_string = json.dumps(response_dict)
|
24
|
+
result = parse_agent_response(response_string)
|
25
|
+
assert result == response_dict
|
26
|
+
|
27
|
+
|
28
|
+
def test_parse_agent_response_invalid_json():
|
29
|
+
"""Test parsing invalid JSON string."""
|
30
|
+
with pytest.raises(ValueError, match="Malformed agent response"):
|
31
|
+
parse_agent_response("invalid json")
|
32
|
+
|
33
|
+
|
34
|
+
def test_is_tool_request_true():
|
35
|
+
"""Test is_tool_request returns True for tool requests."""
|
36
|
+
msg = {"tool_request": {"name": "test_tool", "args": {}}}
|
37
|
+
assert is_tool_request(msg) is True
|
38
|
+
|
39
|
+
|
40
|
+
def test_is_tool_request_false():
|
41
|
+
"""Test is_tool_request returns False for non-tool requests."""
|
42
|
+
msg = {"final_response": {"content": "Hello"}}
|
43
|
+
assert is_tool_request(msg) is False
|
44
|
+
|
45
|
+
|
46
|
+
def test_get_tool_request():
|
47
|
+
"""Test getting tool request details."""
|
48
|
+
msg = {"tool_request": {"name": "test_tool", "args": {"param": "value"}}}
|
49
|
+
name, args = get_tool_request(msg)
|
50
|
+
assert name == "test_tool"
|
51
|
+
assert args == {"param": "value"}
|
52
|
+
|
53
|
+
|
54
|
+
def test_get_tool_request_no_args():
|
55
|
+
"""Test getting tool request with no args."""
|
56
|
+
msg = {"tool_request": {"name": "test_tool"}}
|
57
|
+
name, args = get_tool_request(msg)
|
58
|
+
assert name == "test_tool"
|
59
|
+
assert args == {}
|
60
|
+
|
61
|
+
|
62
|
+
def test_wrap_tool_result():
|
63
|
+
"""Test wrapping tool result."""
|
64
|
+
result = {"output": "success"}
|
65
|
+
wrapped = wrap_tool_result("test_tool", result)
|
66
|
+
expected = {"tool_result": {"name": "test_tool", "result": result}}
|
67
|
+
assert wrapped == expected
|
68
|
+
|
69
|
+
|
70
|
+
def test_is_final_response_true():
|
71
|
+
"""Test is_final_response returns True for final responses."""
|
72
|
+
msg = {"final_response": {"content": "Hello"}}
|
73
|
+
assert is_final_response(msg) is True
|
74
|
+
|
75
|
+
|
76
|
+
def test_is_final_response_false():
|
77
|
+
"""Test is_final_response returns False for non-final responses."""
|
78
|
+
msg = {"tool_request": {"name": "test_tool", "args": {}}}
|
79
|
+
assert is_final_response(msg) is False
|
80
|
+
|
81
|
+
|
82
|
+
def test_get_final_response():
|
83
|
+
"""Test getting final response content."""
|
84
|
+
final_content = {"content": "Hello, world!"}
|
85
|
+
msg = {"final_response": final_content}
|
86
|
+
result = get_final_response(msg)
|
87
|
+
assert result == final_content
|
88
|
+
|
89
|
+
|
90
|
+
def test_complex_agent_response():
|
91
|
+
"""Test parsing a complex agent response."""
|
92
|
+
complex_response = {
|
93
|
+
"tool_request": {
|
94
|
+
"name": "weather_api",
|
95
|
+
"args": {"location": "New York", "units": "celsius"},
|
96
|
+
},
|
97
|
+
"metadata": {"timestamp": "2024-01-01T00:00:00Z", "session_id": "abc123"},
|
98
|
+
}
|
99
|
+
|
100
|
+
# Test parsing
|
101
|
+
parsed = parse_agent_response(complex_response)
|
102
|
+
assert parsed == complex_response
|
103
|
+
|
104
|
+
# Test tool request detection
|
105
|
+
assert is_tool_request(parsed) is True
|
106
|
+
|
107
|
+
# Test getting tool request
|
108
|
+
name, args = get_tool_request(parsed)
|
109
|
+
assert name == "weather_api"
|
110
|
+
assert args == {"location": "New York", "units": "celsius"}
|
111
|
+
|
112
|
+
# Test wrapping result
|
113
|
+
tool_result = {"temperature": 20, "condition": "sunny"}
|
114
|
+
wrapped = wrap_tool_result(name, tool_result)
|
115
|
+
assert wrapped["tool_result"]["name"] == "weather_api"
|
116
|
+
assert wrapped["tool_result"]["result"] == tool_result
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import pytest
|
2
|
+
from dacp.tools import register_tool, run_tool, TOOL_REGISTRY
|
3
|
+
|
4
|
+
|
5
|
+
def test_register_tool():
|
6
|
+
"""Test registering a tool."""
|
7
|
+
|
8
|
+
def test_tool(param1: str, param2: int) -> dict:
|
9
|
+
return {"result": f"Processed {param1} with {param2}"}
|
10
|
+
|
11
|
+
register_tool("test_tool", test_tool)
|
12
|
+
assert "test_tool" in TOOL_REGISTRY
|
13
|
+
assert TOOL_REGISTRY["test_tool"] == test_tool
|
14
|
+
|
15
|
+
|
16
|
+
def test_run_tool():
|
17
|
+
"""Test running a registered tool."""
|
18
|
+
|
19
|
+
def test_tool(param1: str, param2: int) -> dict:
|
20
|
+
return {"result": f"Processed {param1} with {param2}"}
|
21
|
+
|
22
|
+
register_tool("test_tool", test_tool)
|
23
|
+
result = run_tool("test_tool", {"param1": "hello", "param2": 42})
|
24
|
+
assert result["result"] == "Processed hello with 42"
|
25
|
+
|
26
|
+
|
27
|
+
def test_run_nonexistent_tool():
|
28
|
+
"""Test running a tool that doesn't exist."""
|
29
|
+
with pytest.raises(ValueError):
|
30
|
+
run_tool("nonexistent_tool", {})
|
31
|
+
|
32
|
+
|
33
|
+
def test_tool_with_no_args():
|
34
|
+
"""Test running a tool with no arguments."""
|
35
|
+
|
36
|
+
def no_args_tool() -> dict:
|
37
|
+
return {"result": "success"}
|
38
|
+
|
39
|
+
register_tool("no_args_tool", no_args_tool)
|
40
|
+
result = run_tool("no_args_tool", {})
|
41
|
+
assert result["result"] == "success"
|
42
|
+
|
43
|
+
|
44
|
+
def test_tool_with_optional_args():
|
45
|
+
"""Test running a tool with optional arguments."""
|
46
|
+
|
47
|
+
def optional_args_tool(required: str, optional: str = "default") -> dict:
|
48
|
+
return {"result": f"{required}:{optional}"}
|
49
|
+
|
50
|
+
register_tool("optional_args_tool", optional_args_tool)
|
51
|
+
|
52
|
+
# Test with both args
|
53
|
+
result = run_tool("optional_args_tool", {"required": "test", "optional": "custom"})
|
54
|
+
assert result["result"] == "test:custom"
|
55
|
+
|
56
|
+
# Test with only required arg
|
57
|
+
result = run_tool("optional_args_tool", {"required": "test"})
|
58
|
+
assert result["result"] == "test:default"
|
59
|
+
|
60
|
+
|
61
|
+
def test_clear_tool_registry():
|
62
|
+
"""Test that we can clear and re-register tools."""
|
63
|
+
|
64
|
+
def test_tool() -> dict:
|
65
|
+
return {"result": "test"}
|
66
|
+
|
67
|
+
register_tool("test_tool", test_tool)
|
68
|
+
assert "test_tool" in TOOL_REGISTRY
|
69
|
+
|
70
|
+
# Clear registry
|
71
|
+
TOOL_REGISTRY.clear()
|
72
|
+
assert "test_tool" not in TOOL_REGISTRY
|