signalwire-agents 0.1.0__py3-none-any.whl → 0.1.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.
- signalwire_agents/__init__.py +10 -1
- signalwire_agents/agent_server.py +73 -44
- {signalwire_agents-0.1.0.dist-info → signalwire_agents-0.1.1.dist-info}/METADATA +75 -30
- signalwire_agents-0.1.1.dist-info/RECORD +9 -0
- {signalwire_agents-0.1.0.dist-info → signalwire_agents-0.1.1.dist-info}/WHEEL +1 -1
- signalwire_agents-0.1.1.dist-info/licenses/LICENSE +21 -0
- signalwire_agents/core/__init__.py +0 -20
- signalwire_agents/core/agent_base.py +0 -2449
- signalwire_agents/core/function_result.py +0 -104
- signalwire_agents/core/pom_builder.py +0 -195
- signalwire_agents/core/security/__init__.py +0 -0
- signalwire_agents/core/security/session_manager.py +0 -170
- signalwire_agents/core/state/__init__.py +0 -8
- signalwire_agents/core/state/file_state_manager.py +0 -210
- signalwire_agents/core/state/state_manager.py +0 -92
- signalwire_agents/core/swaig_function.py +0 -163
- signalwire_agents/core/swml_builder.py +0 -205
- signalwire_agents/core/swml_handler.py +0 -218
- signalwire_agents/core/swml_renderer.py +0 -359
- signalwire_agents/core/swml_service.py +0 -1009
- signalwire_agents/prefabs/__init__.py +0 -15
- signalwire_agents/prefabs/concierge.py +0 -276
- signalwire_agents/prefabs/faq_bot.py +0 -314
- signalwire_agents/prefabs/info_gatherer.py +0 -253
- signalwire_agents/prefabs/survey.py +0 -387
- signalwire_agents/utils/__init__.py +0 -0
- signalwire_agents/utils/pom_utils.py +0 -0
- signalwire_agents/utils/schema_utils.py +0 -348
- signalwire_agents/utils/token_generators.py +0 -0
- signalwire_agents/utils/validators.py +0 -0
- signalwire_agents-0.1.0.dist-info/RECORD +0 -32
- {signalwire_agents-0.1.0.data → signalwire_agents-0.1.1.data}/data/schema.json +0 -0
- {signalwire_agents-0.1.0.dist-info → signalwire_agents-0.1.1.dist-info}/top_level.txt +0 -0
signalwire_agents/__init__.py
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
"""
|
2
|
+
Copyright (c) 2025 SignalWire
|
3
|
+
|
4
|
+
This file is part of the SignalWire AI Agents SDK.
|
5
|
+
|
6
|
+
Licensed under the MIT License.
|
7
|
+
See LICENSE file in the project root for full license information.
|
8
|
+
"""
|
9
|
+
|
1
10
|
"""
|
2
11
|
SignalWire AI Agents SDK
|
3
12
|
=======================
|
@@ -5,7 +14,7 @@ SignalWire AI Agents SDK
|
|
5
14
|
A package for building AI agents using SignalWire's AI and SWML capabilities.
|
6
15
|
"""
|
7
16
|
|
8
|
-
__version__ = "0.1.
|
17
|
+
__version__ = "0.1.1"
|
9
18
|
|
10
19
|
# Import core classes for easier access
|
11
20
|
from signalwire_agents.core.agent_base import AgentBase
|
@@ -1,3 +1,12 @@
|
|
1
|
+
"""
|
2
|
+
Copyright (c) 2025 SignalWire
|
3
|
+
|
4
|
+
This file is part of the SignalWire AI Agents SDK.
|
5
|
+
|
6
|
+
Licensed under the MIT License.
|
7
|
+
See LICENSE file in the project root for full license information.
|
8
|
+
"""
|
9
|
+
|
1
10
|
"""
|
2
11
|
AgentServer - Class for hosting multiple SignalWire AI Agents in a single server
|
3
12
|
"""
|
@@ -57,7 +66,7 @@ class AgentServer:
|
|
57
66
|
self.app = FastAPI(
|
58
67
|
title="SignalWire AI Agents",
|
59
68
|
description="Hosted SignalWire AI Agents",
|
60
|
-
version="0.1.
|
69
|
+
version="0.1.1"
|
61
70
|
)
|
62
71
|
|
63
72
|
# Keep track of registered agents
|
@@ -103,18 +112,25 @@ class AgentServer:
|
|
103
112
|
self.logger.info(f"Registered agent '{agent.get_name()}' at route '{route}'")
|
104
113
|
|
105
114
|
# If SIP routing is enabled and auto-mapping is on, register SIP usernames for this agent
|
106
|
-
if
|
107
|
-
|
115
|
+
if self._sip_routing_enabled:
|
116
|
+
# Auto-map SIP usernames if enabled
|
117
|
+
if getattr(self, '_sip_auto_map', False):
|
118
|
+
self._auto_map_agent_sip_usernames(agent, route)
|
119
|
+
|
120
|
+
# Register the SIP routing callback with this agent if we have one
|
121
|
+
if hasattr(self, '_sip_routing_callback') and self._sip_routing_callback:
|
122
|
+
agent.register_routing_callback(self._sip_routing_callback, path=self._sip_route)
|
108
123
|
|
109
124
|
def setup_sip_routing(self, route: str = "/sip", auto_map: bool = True) -> None:
|
110
125
|
"""
|
111
126
|
Set up central SIP-based routing for the server
|
112
127
|
|
113
|
-
This
|
114
|
-
|
128
|
+
This configures all agents to handle SIP requests at the specified path,
|
129
|
+
using a coordinated routing system where each agent checks if it can
|
130
|
+
handle SIP requests for specific usernames.
|
115
131
|
|
116
132
|
Args:
|
117
|
-
route: The
|
133
|
+
route: The path for SIP routing (default: "/sip")
|
118
134
|
auto_map: Whether to automatically map SIP usernames to agent routes
|
119
135
|
"""
|
120
136
|
if self._sip_routing_enabled:
|
@@ -136,48 +152,38 @@ class AgentServer:
|
|
136
152
|
if auto_map:
|
137
153
|
for agent_route, agent in self.agents.items():
|
138
154
|
self._auto_map_agent_sip_usernames(agent, agent_route)
|
139
|
-
|
140
|
-
#
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
self.logger.debug(f"Received request at SIP endpoint: {route}")
|
155
|
+
|
156
|
+
# Create a unified routing callback that checks all registered usernames
|
157
|
+
def server_sip_routing_callback(request: Request, body: Dict[str, Any]) -> Optional[str]:
|
158
|
+
"""Unified SIP routing callback that checks all registered usernames"""
|
159
|
+
# Extract the SIP username
|
160
|
+
sip_username = SWMLService.extract_sip_username(body)
|
146
161
|
|
147
|
-
|
148
|
-
|
149
|
-
body = await request.json()
|
150
|
-
|
151
|
-
# Extract the SIP username
|
152
|
-
sip_username = SWMLService.extract_sip_username(body)
|
162
|
+
if sip_username:
|
163
|
+
self.logger.info(f"Extracted SIP username: {sip_username}")
|
153
164
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
# Look up the route for this username
|
158
|
-
target_route = self._lookup_sip_route(sip_username)
|
159
|
-
|
160
|
-
if target_route:
|
161
|
-
self.logger.info(f"Routing SIP request to {target_route}")
|
162
|
-
|
163
|
-
# Create a redirect response to the target route
|
164
|
-
# Use 307 Temporary Redirect to preserve the POST method
|
165
|
-
response = Response(status_code=307)
|
166
|
-
response.headers["Location"] = target_route
|
167
|
-
return response
|
168
|
-
else:
|
169
|
-
self.logger.warning(f"No route found for SIP username: {sip_username}")
|
165
|
+
# Look up the route for this username
|
166
|
+
target_route = self._lookup_sip_route(sip_username)
|
170
167
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
168
|
+
if target_route:
|
169
|
+
self.logger.info(f"Routing SIP request to {target_route}")
|
170
|
+
return target_route
|
171
|
+
else:
|
172
|
+
self.logger.warning(f"No route found for SIP username: {sip_username}")
|
173
|
+
|
174
|
+
# No routing needed (will be handled by the current agent)
|
175
|
+
return None
|
176
|
+
|
177
|
+
# Save the callback for later use with new agents
|
178
|
+
self._sip_routing_callback = server_sip_routing_callback
|
179
|
+
|
180
|
+
# Register this callback with each agent
|
181
|
+
for agent in self.agents.values():
|
182
|
+
# Each agent gets the same routing callback but at their own path
|
183
|
+
agent.register_routing_callback(server_sip_routing_callback, path=route)
|
184
|
+
|
185
|
+
self.logger.info(f"SIP routing enabled at {route} on all agents")
|
178
186
|
|
179
|
-
self.logger.info(f"SIP routing enabled at {route}")
|
180
|
-
|
181
187
|
def register_sip_username(self, username: str, route: str) -> None:
|
182
188
|
"""
|
183
189
|
Register a mapping from SIP username to agent route
|
@@ -334,3 +340,26 @@ class AgentServer:
|
|
334
340
|
port=port,
|
335
341
|
log_level=self.log_level
|
336
342
|
)
|
343
|
+
|
344
|
+
def register_global_routing_callback(self, callback_fn: Callable[[Request, Dict[str, Any]], Optional[str]],
|
345
|
+
path: str) -> None:
|
346
|
+
"""
|
347
|
+
Register a routing callback across all agents
|
348
|
+
|
349
|
+
This allows you to add unified routing logic to all agents at the same path.
|
350
|
+
|
351
|
+
Args:
|
352
|
+
callback_fn: The callback function to register
|
353
|
+
path: The path to register the callback at
|
354
|
+
"""
|
355
|
+
# Normalize the path
|
356
|
+
if not path.startswith("/"):
|
357
|
+
path = f"/{path}"
|
358
|
+
|
359
|
+
path = path.rstrip("/")
|
360
|
+
|
361
|
+
# Register with all existing agents
|
362
|
+
for agent in self.agents.values():
|
363
|
+
agent.register_routing_callback(callback_fn, path=path)
|
364
|
+
|
365
|
+
self.logger.info(f"Registered global routing callback at {path} on all agents")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: signalwire_agents
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.1
|
4
4
|
Summary: SignalWire AI Agents SDK
|
5
5
|
Author-email: SignalWire Team <info@signalwire.com>
|
6
6
|
Project-URL: Homepage, https://github.com/signalwire/signalwire-ai-agents
|
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
16
16
|
Requires-Python: >=3.7
|
17
17
|
Description-Content-Type: text/markdown
|
18
|
+
License-File: LICENSE
|
18
19
|
Requires-Dist: fastapi==0.115.12
|
19
20
|
Requires-Dist: pydantic==2.11.4
|
20
21
|
Requires-Dist: PyYAML==6.0.2
|
@@ -23,6 +24,7 @@ Requires-Dist: setuptools==66.1.1
|
|
23
24
|
Requires-Dist: signalwire_pom==2.7.1
|
24
25
|
Requires-Dist: structlog==25.3.0
|
25
26
|
Requires-Dist: uvicorn==0.34.2
|
27
|
+
Dynamic: license-file
|
26
28
|
|
27
29
|
# SignalWire AI Agent SDK
|
28
30
|
|
@@ -33,8 +35,9 @@ A Python SDK for creating, hosting, and securing SignalWire AI agents as microse
|
|
33
35
|
- **Self-Contained Agents**: Each agent is both a web app and an AI persona
|
34
36
|
- **Prompt Object Model**: Structured prompt composition using POM
|
35
37
|
- **SWAIG Integration**: Easily define and handle AI tools/functions
|
36
|
-
- **
|
37
|
-
- **
|
38
|
+
- **Custom Routing**: Dynamic request handling for different paths and content
|
39
|
+
- **Security Built-In**: Session management, function-specific security tokens, and basic auth
|
40
|
+
- **State Management**: Persistent conversation state with automatic tracking
|
38
41
|
- **Prefab Archetypes**: Ready-to-use agent types for common scenarios
|
39
42
|
- **Multi-Agent Support**: Host multiple agents on a single server
|
40
43
|
|
@@ -53,12 +56,23 @@ from signalwire_agents.core.function_result import SwaigFunctionResult
|
|
53
56
|
class SimpleAgent(AgentBase):
|
54
57
|
def __init__(self):
|
55
58
|
super().__init__(name="simple", route="/simple")
|
56
|
-
|
57
|
-
|
58
|
-
self.
|
59
|
+
|
60
|
+
# Configure the agent's personality
|
61
|
+
self.prompt_add_section("Personality", body="You are a helpful assistant.")
|
62
|
+
self.prompt_add_section("Goal", body="Help users with basic questions.")
|
63
|
+
self.prompt_add_section("Instructions", bullets=["Be concise and clear."])
|
64
|
+
|
65
|
+
# Alternative using convenience methods:
|
66
|
+
# self.setPersonality("You are a helpful assistant.")
|
67
|
+
# self.setGoal("Help users with basic questions.")
|
68
|
+
# self.setInstructions(["Be concise and clear."])
|
59
69
|
|
60
|
-
@AgentBase.tool(
|
61
|
-
|
70
|
+
@AgentBase.tool(
|
71
|
+
name="get_time",
|
72
|
+
description="Get the current time",
|
73
|
+
parameters={}
|
74
|
+
)
|
75
|
+
def get_time(self, args, raw_data):
|
62
76
|
from datetime import datetime
|
63
77
|
now = datetime.now().strftime("%H:%M:%S")
|
64
78
|
return SwaigFunctionResult(f"The current time is {now}")
|
@@ -73,6 +87,7 @@ if __name__ == "__main__":
|
|
73
87
|
|
74
88
|
```python
|
75
89
|
from signalwire_agents import AgentBase
|
90
|
+
from signalwire_agents.core.function_result import SwaigFunctionResult
|
76
91
|
from signalwire_agents.core.state import FileStateManager
|
77
92
|
|
78
93
|
class StatefulAgent(AgentBase):
|
@@ -86,21 +101,44 @@ class StatefulAgent(AgentBase):
|
|
86
101
|
enable_state_tracking=True, # Enable state tracking
|
87
102
|
state_manager=state_manager # Use custom state manager
|
88
103
|
)
|
104
|
+
|
105
|
+
# When enable_state_tracking=True, startup_hook and hangup_hook
|
106
|
+
# are automatically registered to track session lifecycle
|
89
107
|
|
90
|
-
#
|
91
|
-
|
92
|
-
""
|
108
|
+
# Custom tool for accessing and updating state
|
109
|
+
@AgentBase.tool(
|
110
|
+
name="save_preference",
|
111
|
+
description="Save a user preference",
|
112
|
+
parameters={
|
113
|
+
"preference_name": {
|
114
|
+
"type": "string",
|
115
|
+
"description": "Name of the preference to save"
|
116
|
+
},
|
117
|
+
"preference_value": {
|
118
|
+
"type": "string",
|
119
|
+
"description": "Value of the preference"
|
120
|
+
}
|
121
|
+
}
|
122
|
+
)
|
123
|
+
def save_preference(self, args, raw_data):
|
124
|
+
# Get the call ID from the raw data
|
93
125
|
call_id = raw_data.get("call_id")
|
94
|
-
state = self.get_state(call_id) or {}
|
95
|
-
state["started_at"] = "2023-01-01T12:00:00Z"
|
96
|
-
self.update_state(call_id, state)
|
97
|
-
return "Call initialized"
|
98
126
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
127
|
+
if call_id:
|
128
|
+
# Get current state or empty dict if none exists
|
129
|
+
state = self.get_state(call_id) or {}
|
130
|
+
|
131
|
+
# Update the state
|
132
|
+
preferences = state.get("preferences", {})
|
133
|
+
preferences[args.get("preference_name")] = args.get("preference_value")
|
134
|
+
state["preferences"] = preferences
|
135
|
+
|
136
|
+
# Save the updated state
|
137
|
+
self.update_state(call_id, state)
|
138
|
+
|
139
|
+
return SwaigFunctionResult("Preference saved successfully")
|
140
|
+
else:
|
141
|
+
return SwaigFunctionResult("Could not save preference: No call ID")
|
104
142
|
```
|
105
143
|
|
106
144
|
## Using Prefab Agents
|
@@ -113,12 +151,20 @@ agent = InfoGathererAgent(
|
|
113
151
|
{"name": "full_name", "prompt": "What is your full name?"},
|
114
152
|
{"name": "reason", "prompt": "How can I help you today?"}
|
115
153
|
],
|
116
|
-
confirmation_template="Thanks {full_name}, I'll help you with {reason}."
|
154
|
+
confirmation_template="Thanks {full_name}, I'll help you with {reason}.",
|
155
|
+
name="info-gatherer",
|
156
|
+
route="/info-gatherer"
|
117
157
|
)
|
118
158
|
|
119
|
-
agent.serve(host="0.0.0.0", port=8000
|
159
|
+
agent.serve(host="0.0.0.0", port=8000)
|
120
160
|
```
|
121
161
|
|
162
|
+
Available prefabs include:
|
163
|
+
- `InfoGathererAgent`: Collects structured information from users
|
164
|
+
- `FAQBotAgent`: Answers questions based on a knowledge base
|
165
|
+
- `ConciergeAgent`: Routes users to specialized agents
|
166
|
+
- `SurveyAgent`: Conducts structured surveys with questions and rating scales
|
167
|
+
|
122
168
|
## Configuration
|
123
169
|
|
124
170
|
### Environment Variables
|
@@ -132,6 +178,7 @@ The SDK supports the following environment variables:
|
|
132
178
|
- `SWML_SSL_CERT_PATH`: Path to SSL certificate file
|
133
179
|
- `SWML_SSL_KEY_PATH`: Path to SSL private key file
|
134
180
|
- `SWML_DOMAIN`: Domain name for SSL certificate and external URLs
|
181
|
+
- `SWML_SCHEMA_PATH`: Optional path to override the location of the schema.json file
|
135
182
|
|
136
183
|
When the auth environment variables are set, they will be used for all agents instead of generating random credentials. The proxy URL base is useful when your service is behind a reverse proxy or when you need external services to access your webhooks.
|
137
184
|
|
@@ -139,15 +186,13 @@ To enable HTTPS directly (without a reverse proxy), set `SWML_SSL_ENABLED` to "t
|
|
139
186
|
|
140
187
|
## Documentation
|
141
188
|
|
142
|
-
|
189
|
+
The package includes comprehensive documentation in the `docs/` directory:
|
190
|
+
|
191
|
+
- [Agent Guide](docs/agent_guide.md) - Detailed guide to creating and customizing agents
|
192
|
+
- [Architecture](docs/architecture.md) - Overview of the SDK architecture and core concepts
|
193
|
+
- [SWML Service Guide](docs/swml_service_guide.md) - Guide to the underlying SWML service
|
143
194
|
|
144
|
-
-
|
145
|
-
- Using prefab agents
|
146
|
-
- SWAIG function definitions
|
147
|
-
- State management and persistence
|
148
|
-
- Security model
|
149
|
-
- Deployment options
|
150
|
-
- Multi-agent hosting
|
195
|
+
These documents provide in-depth explanations of the features, APIs, and usage patterns.
|
151
196
|
|
152
197
|
## License
|
153
198
|
|
@@ -0,0 +1,9 @@
|
|
1
|
+
signalwire_agents/__init__.py,sha256=fOxVP6ME-O9x_nCGn9-VcssR7QnOD6cG6YlHP0dts4M,800
|
2
|
+
signalwire_agents/agent_server.py,sha256=cyHVjsCSXHBVtJCB-eNgIPZNLKe-VT1fDFQR82TOkzY,12850
|
3
|
+
signalwire_agents/schema.json,sha256=M8Mn6pQda2P9jhbmkALrLr1wt-fRuhYRqdmEi9Rbhqk,178075
|
4
|
+
signalwire_agents-0.1.1.data/data/schema.json,sha256=M8Mn6pQda2P9jhbmkALrLr1wt-fRuhYRqdmEi9Rbhqk,178075
|
5
|
+
signalwire_agents-0.1.1.dist-info/licenses/LICENSE,sha256=NYvAsB-rTcSvG9cqHt9EUHAWLiA9YzM4Qfz-mPdvDR0,1067
|
6
|
+
signalwire_agents-0.1.1.dist-info/METADATA,sha256=yK8j3RqoCVO40A5MxR7UnGFCXCVJv7VS07-7WXQtbKw,7414
|
7
|
+
signalwire_agents-0.1.1.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
8
|
+
signalwire_agents-0.1.1.dist-info/top_level.txt,sha256=kDGS6ZYv84K9P5Kyg9_S8P_pbUXoHkso0On_DB5bbWc,18
|
9
|
+
signalwire_agents-0.1.1.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 SignalWire
|
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.
|
@@ -1,20 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Core components for SignalWire AI Agents
|
3
|
-
"""
|
4
|
-
|
5
|
-
from signalwire_agents.core.agent_base import AgentBase
|
6
|
-
from signalwire_agents.core.function_result import SwaigFunctionResult
|
7
|
-
from signalwire_agents.core.swaig_function import SwaigFunction
|
8
|
-
from signalwire_agents.core.swml_service import SWMLService
|
9
|
-
from signalwire_agents.core.swml_handler import SWMLVerbHandler, VerbHandlerRegistry
|
10
|
-
from signalwire_agents.core.swml_builder import SWMLBuilder
|
11
|
-
|
12
|
-
__all__ = [
|
13
|
-
'AgentBase',
|
14
|
-
'SwaigFunctionResult',
|
15
|
-
'SwaigFunction',
|
16
|
-
'SWMLService',
|
17
|
-
'SWMLVerbHandler',
|
18
|
-
'VerbHandlerRegistry',
|
19
|
-
'SWMLBuilder'
|
20
|
-
]
|