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.
Files changed (33) hide show
  1. signalwire_agents/__init__.py +10 -1
  2. signalwire_agents/agent_server.py +73 -44
  3. {signalwire_agents-0.1.0.dist-info → signalwire_agents-0.1.1.dist-info}/METADATA +75 -30
  4. signalwire_agents-0.1.1.dist-info/RECORD +9 -0
  5. {signalwire_agents-0.1.0.dist-info → signalwire_agents-0.1.1.dist-info}/WHEEL +1 -1
  6. signalwire_agents-0.1.1.dist-info/licenses/LICENSE +21 -0
  7. signalwire_agents/core/__init__.py +0 -20
  8. signalwire_agents/core/agent_base.py +0 -2449
  9. signalwire_agents/core/function_result.py +0 -104
  10. signalwire_agents/core/pom_builder.py +0 -195
  11. signalwire_agents/core/security/__init__.py +0 -0
  12. signalwire_agents/core/security/session_manager.py +0 -170
  13. signalwire_agents/core/state/__init__.py +0 -8
  14. signalwire_agents/core/state/file_state_manager.py +0 -210
  15. signalwire_agents/core/state/state_manager.py +0 -92
  16. signalwire_agents/core/swaig_function.py +0 -163
  17. signalwire_agents/core/swml_builder.py +0 -205
  18. signalwire_agents/core/swml_handler.py +0 -218
  19. signalwire_agents/core/swml_renderer.py +0 -359
  20. signalwire_agents/core/swml_service.py +0 -1009
  21. signalwire_agents/prefabs/__init__.py +0 -15
  22. signalwire_agents/prefabs/concierge.py +0 -276
  23. signalwire_agents/prefabs/faq_bot.py +0 -314
  24. signalwire_agents/prefabs/info_gatherer.py +0 -253
  25. signalwire_agents/prefabs/survey.py +0 -387
  26. signalwire_agents/utils/__init__.py +0 -0
  27. signalwire_agents/utils/pom_utils.py +0 -0
  28. signalwire_agents/utils/schema_utils.py +0 -348
  29. signalwire_agents/utils/token_generators.py +0 -0
  30. signalwire_agents/utils/validators.py +0 -0
  31. signalwire_agents-0.1.0.dist-info/RECORD +0 -32
  32. {signalwire_agents-0.1.0.data → signalwire_agents-0.1.1.data}/data/schema.json +0 -0
  33. {signalwire_agents-0.1.0.dist-info → signalwire_agents-0.1.1.dist-info}/top_level.txt +0 -0
@@ -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.0"
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.0"
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 hasattr(self, '_sip_auto_map') and self._sip_auto_map and self._sip_routing_enabled:
107
- self._auto_map_agent_sip_usernames(agent, route)
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 adds a special endpoint that can route SIP requests to the appropriate
114
- agent based on the SIP username in the request.
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 route for SIP requests
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
- # Register the SIP endpoint
141
- @self.app.post(f"{route}")
142
- @self.app.post(f"{route}/")
143
- async def handle_sip_request(request: Request):
144
- """Handle SIP requests and route to the appropriate agent"""
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
- try:
148
- # Extract the request body
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
- if sip_username:
155
- self.logger.info(f"Extracted SIP username: {sip_username}")
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
- # If we get here, either no SIP username was found or no matching route exists
172
- # Return a basic SWML response
173
- return {"version": "1.0.0", "sections": {"main": []}}
174
-
175
- except Exception as e:
176
- self.logger.error(f"Error processing SIP request: {str(e)}")
177
- return {"version": "1.0.0", "sections": {"main": []}}
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.0
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
- - **Security Built-In**: Session management, per-call tokens, and basic auth
37
- - **State Management**: Persistent conversation state with lifecycle hooks
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
- self.set_personality("You are a helpful assistant.")
57
- self.set_goal("Help users with basic questions.")
58
- self.add_instruction("Be concise and clear.")
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(name="get_time", parameters={})
61
- def get_time(self):
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
- # These methods are automatically registered when enable_state_tracking=True
91
- def startup_hook(self, args, raw_data):
92
- """Called when a conversation starts"""
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
- def hangup_hook(self, args, raw_data):
100
- """Called when a conversation ends"""
101
- call_id = raw_data.get("call_id")
102
- state = self.get_state(call_id)
103
- return "Call completed"
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, route="/support")
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
- See the [full documentation](https://docs.signalwire.com/ai-agents) for details on:
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
- - Creating custom agents
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.6.0)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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
- ]