Skip to content

Commit a6d97cd

Browse files
kirankv21joobi-keyvalueaavani-kv
authored
[feat]: Add python agent toolkit (#18)
* feat: initial commit for python sdk * fix: updated readme and package, and tested happy flow for send message * feat:update schemas and fix import errors * fix: update schema type hints and add default factory for input_data field * fix: resolve Pydantic validation errors in CrewAI tool wrapper and add default factory for notify field * docs: expand README documentation and improve code examples * refactor: rename package from siren_agent_toolkit to agenttoolkit and reorganize files * refactor: move example docs to dedicated README and remove comment headers in code --------- Co-authored-by: joobi-keyvalue <[email protected]> Co-authored-by: aavani-kv <[email protected]>
1 parent 357885f commit a6d97cd

File tree

26 files changed

+1579
-0
lines changed

26 files changed

+1579
-0
lines changed

python/.gitignore

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
share/python-wheels/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
MANIFEST
28+
29+
# PyInstaller
30+
*.manifest
31+
*.spec
32+
33+
# Installer logs
34+
pip-log.txt
35+
pip-delete-this-directory.txt
36+
37+
# Unit test / coverage reports
38+
htmlcov/
39+
.tox/
40+
.nox/
41+
.coverage
42+
.coverage.*
43+
.cache
44+
nosetests.xml
45+
coverage.xml
46+
*.cover
47+
*.py,cover
48+
.hypothesis/
49+
.pytest_cache/
50+
cover/
51+
52+
# Translations
53+
*.mo
54+
*.pot
55+
56+
# Django stuff:
57+
*.log
58+
local_settings.py
59+
db.sqlite3
60+
db.sqlite3-journal
61+
62+
# Flask stuff:
63+
instance/
64+
.webassets-cache
65+
66+
# Scrapy stuff:
67+
.scrapy
68+
69+
# Sphinx documentation
70+
docs/_build/
71+
72+
# PyBuilder
73+
.pybuilder/
74+
target/
75+
76+
# Jupyter Notebook
77+
.ipynb_checkpoints
78+
79+
# IPython
80+
profile_default/
81+
ipython_config.py
82+
83+
# pyenv
84+
.python-version
85+
86+
# pipenv
87+
Pipfile.lock
88+
89+
# poetry
90+
poetry.lock
91+
92+
# pdm
93+
.pdm.toml
94+
95+
# PEP 582
96+
__pypackages__/
97+
98+
# Celery stuff
99+
celerybeat-schedule
100+
celerybeat.pid
101+
102+
# SageMath parsed files
103+
*.sage.py
104+
105+
# Environments
106+
.env
107+
.venv
108+
env/
109+
venv/
110+
ENV/
111+
env.bak/
112+
venv.bak/
113+
114+
# Spyder project settings
115+
.spyderproject
116+
.spyproject
117+
118+
# Rope project settings
119+
.ropeproject
120+
121+
# mkdocs documentation
122+
/site
123+
124+
# mypy
125+
.mypy_cache/
126+
.dmypy.json
127+
dmypy.json
128+
129+
# Pyre type checker
130+
.pyre/
131+
132+
# pytype static type analyzer
133+
.pytype/
134+
135+
# Cython debug symbols
136+
cython_debug/
137+
138+
# PyCharm
139+
.idea/
140+
141+
# VS Code
142+
.vscode/
143+
144+
# OS
145+
.DS_Store
146+
Thumbs.db

python/README.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Siren Agent Toolkit (Python)
2+
3+
The **Siren Agent Toolkit** provides a unified Python interface and agent tools for interacting with the Siren MCP (Model Context Protocol) platform. It enables messaging, template management, user management, workflow automation, and webhook configuration, with seamless integration into popular agent frameworks like LangChain, OpenAI, and CrewAI.
4+
5+
## Features & Capabilities
6+
7+
### Messaging
8+
- Send messages via various channels (Email, SMS, WhatsApp, Slack, Teams, Discord, Line, etc.)
9+
- Retrieve message status and replies
10+
- Support for template-based and direct messaging
11+
12+
### Templates
13+
- List, create, update, delete, and publish notification templates
14+
- Create and manage channel-specific templates
15+
- Support for template variables and versioning
16+
17+
### Users
18+
- Add, update, and delete users
19+
- Manage user attributes and contact information
20+
21+
### Workflows
22+
- Trigger workflows (single or bulk operations)
23+
- Schedule workflows for future or recurring execution
24+
- Pass custom data to workflow executions
25+
26+
### Webhooks
27+
- Configure webhooks for status updates
28+
- Set up inbound message webhooks
29+
- Optional webhook verification with secrets
30+
31+
## 📋 Requirements
32+
33+
- A Siren API key (get one from [Siren Dashboard](https://app.trysiren.io/configuration))
34+
35+
## Installation
36+
37+
```bash
38+
pip install siren-agent-toolkit
39+
```
40+
41+
For local development:
42+
43+
```bash
44+
# From the python/ directory
45+
pip install -e .
46+
```
47+
48+
## Usage
49+
50+
### Basic Example
51+
52+
```python
53+
from siren_agent_toolkit.api import SirenAPI
54+
55+
# Initialize with your API key
56+
api = SirenAPI(api_key="YOUR_API_KEY")
57+
58+
# Send a simple email message
59+
result = api.run("send_message", {
60+
"recipient_value": "[email protected]",
61+
"channel": "EMAIL",
62+
"subject": "Important Update",
63+
"body": "Hello from Siren! This is an important notification."
64+
})
65+
print(result)
66+
```
67+
68+
## Examples
69+
70+
Complete working examples are available in the `examples/` directory:
71+
72+
- `examples/langchain/main.py` — Using Siren tools with LangChain
73+
- `examples/openai/main.py` — Using Siren tools with OpenAI
74+
- `examples/crewai/main.py` — Using Siren tools with CrewAI
75+
76+
## Development
77+
78+
### Configuration
79+
80+
The toolkit supports flexible configuration options:
81+
82+
```python
83+
from siren_agent_toolkit.api import SirenAPI
84+
85+
api = SirenAPI(
86+
api_key="YOUR_API_KEY",
87+
context={"env": "production"} # Optional environment configuration
88+
)
89+
```
90+
91+
### Building Locally
92+
93+
```bash
94+
# From the python/ directory
95+
pip install -e .
96+
# Install development dependencies
97+
pip install -r requirements.txt
98+
```
99+
100+
### Running Tests
101+
102+
```bash
103+
pytest tests/
104+
```
105+
## License
106+
107+
MIT

python/agenttoolkit/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""Siren Agent Toolkit for Python."""
2+
3+
from .api import SirenAPI
4+
from .configuration import Configuration, Context, Actions, is_tool_allowed
5+
from .tools import tools
6+
from .schema import *
7+
8+
__version__ = "1.0.0"
9+
__all__ = [
10+
"SirenAPI",
11+
"Configuration",
12+
"Context",
13+
"Actions",
14+
"is_tool_allowed",
15+
"tools",
16+
]

python/agenttoolkit/api.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from typing import Any, Dict, Optional
2+
from siren import SirenClient
3+
from .configuration import Context
4+
5+
6+
class SirenAPI:
7+
"""API wrapper that integrates with the Siren Python SDK."""
8+
9+
def __init__(self, api_key: str, context: Optional[Context] = None):
10+
self.client = SirenClient(
11+
api_key=api_key,
12+
env=context.get("env") if context else None,
13+
)
14+
15+
def run(self, method: str, params: Dict[str, Any]) -> Any:
16+
"""Execute a method on the Siren client with the given parameters."""
17+
18+
if method == "send_message":
19+
return self.client.message.send(**params)
20+
elif method == "get_message_status":
21+
return self.client.message.get_status(params["message_id"])
22+
elif method == "get_message_replies":
23+
return self.client.message.get_replies(params["message_id"])
24+
25+
elif method == "list_templates":
26+
return self.client.template.get(**params)
27+
elif method == "create_template":
28+
return self.client.template.create(**params)
29+
elif method == "update_template":
30+
template_id = params.pop("template_id")
31+
return self.client.template.update(template_id, **params)
32+
elif method == "delete_template":
33+
return self.client.template.delete(params["template_id"])
34+
elif method == "publish_template":
35+
return self.client.template.publish(params["template_id"])
36+
elif method == "create_channel_templates":
37+
template_id = params.pop("template_id")
38+
return self.client.template.create_channel_templates(template_id, **params)
39+
elif method == "get_channel_templates":
40+
version_id = params.pop("version_id")
41+
return self.client.template.get_channel_templates(version_id, **params)
42+
43+
elif method == "add_user":
44+
return self.client.user.add(**params)
45+
elif method == "update_user":
46+
unique_id = params.pop("unique_id")
47+
return self.client.user.update(unique_id, **params)
48+
elif method == "delete_user":
49+
return self.client.user.delete(params["unique_id"])
50+
elif method == "get_user":
51+
raise NotImplementedError("get_user is not implemented")
52+
#return self.client.user.get(params["unique_id"])
53+
elif method == "list_users":
54+
raise NotImplementedError("list_users is not implemented")
55+
#return self.client.user.list(**params)
56+
57+
elif method == "trigger_workflow":
58+
return self.client.workflow.trigger(**params)
59+
elif method == "trigger_workflow_bulk":
60+
return self.client.workflow.trigger_bulk(**params)
61+
elif method == "schedule_workflow":
62+
return self.client.workflow.schedule(**params)
63+
64+
elif method == "configure_notification_webhooks":
65+
return self.client.webhook.configure_notifications(**params)
66+
elif method == "configure_inbound_webhooks":
67+
return self.client.webhook.configure_inbound(**params)
68+
69+
else:
70+
raise ValueError(f"Unknown method: {method}")
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from typing import Dict, List, Literal, Optional, TypedDict
2+
3+
4+
Object = Literal[
5+
"messaging",
6+
"templates",
7+
"users",
8+
"workflows",
9+
"webhooks",
10+
]
11+
12+
Permission = Literal["create", "update", "read", "delete", "trigger", "schedule"]
13+
14+
15+
class Actions(TypedDict, total=False):
16+
messaging: Optional[Dict[Permission, bool]]
17+
templates: Optional[Dict[Permission, bool]]
18+
users: Optional[Dict[Permission, bool]]
19+
workflows: Optional[Dict[Permission, bool]]
20+
webhooks: Optional[Dict[Permission, bool]]
21+
22+
23+
class Context(TypedDict, total=False):
24+
env: Optional[Literal["dev", "prod"]]
25+
api_key: Optional[str]
26+
base_url: Optional[str]
27+
timeout: Optional[int]
28+
29+
30+
class Configuration(TypedDict, total=False):
31+
actions: Optional[Actions]
32+
context: Optional[Context]
33+
34+
35+
def is_tool_allowed(tool: Dict, configuration: Optional[Configuration] = None) -> bool:
36+
"""Check if a tool is allowed based on configuration permissions."""
37+
if not configuration or not configuration.get("actions"):
38+
return True # Allow all tools if no configuration is provided
39+
40+
for resource, permissions in tool.get("actions", {}).items():
41+
if resource not in configuration["actions"]:
42+
return False
43+
for permission in permissions:
44+
if not configuration["actions"].get(resource, {}).get(permission, False):
45+
return False
46+
return True

0 commit comments

Comments
 (0)