signalwire-agents 0.1.1__py3-none-any.whl → 0.1.5__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 (34) hide show
  1. signalwire_agents/__init__.py +1 -1
  2. signalwire_agents/agent_server.py +1 -1
  3. signalwire_agents/core/__init__.py +29 -0
  4. signalwire_agents/core/agent_base.py +2541 -0
  5. signalwire_agents/core/function_result.py +123 -0
  6. signalwire_agents/core/pom_builder.py +204 -0
  7. signalwire_agents/core/security/__init__.py +9 -0
  8. signalwire_agents/core/security/session_manager.py +179 -0
  9. signalwire_agents/core/state/__init__.py +17 -0
  10. signalwire_agents/core/state/file_state_manager.py +219 -0
  11. signalwire_agents/core/state/state_manager.py +101 -0
  12. signalwire_agents/core/swaig_function.py +172 -0
  13. signalwire_agents/core/swml_builder.py +214 -0
  14. signalwire_agents/core/swml_handler.py +227 -0
  15. signalwire_agents/core/swml_renderer.py +368 -0
  16. signalwire_agents/core/swml_service.py +1057 -0
  17. signalwire_agents/prefabs/__init__.py +26 -0
  18. signalwire_agents/prefabs/concierge.py +267 -0
  19. signalwire_agents/prefabs/faq_bot.py +305 -0
  20. signalwire_agents/prefabs/info_gatherer.py +263 -0
  21. signalwire_agents/prefabs/receptionist.py +295 -0
  22. signalwire_agents/prefabs/survey.py +378 -0
  23. signalwire_agents/utils/__init__.py +9 -0
  24. signalwire_agents/utils/pom_utils.py +9 -0
  25. signalwire_agents/utils/schema_utils.py +357 -0
  26. signalwire_agents/utils/token_generators.py +9 -0
  27. signalwire_agents/utils/validators.py +9 -0
  28. {signalwire_agents-0.1.1.dist-info → signalwire_agents-0.1.5.dist-info}/METADATA +1 -1
  29. signalwire_agents-0.1.5.dist-info/RECORD +34 -0
  30. signalwire_agents-0.1.1.dist-info/RECORD +0 -9
  31. {signalwire_agents-0.1.1.data → signalwire_agents-0.1.5.data}/data/schema.json +0 -0
  32. {signalwire_agents-0.1.1.dist-info → signalwire_agents-0.1.5.dist-info}/WHEEL +0 -0
  33. {signalwire_agents-0.1.1.dist-info → signalwire_agents-0.1.5.dist-info}/licenses/LICENSE +0 -0
  34. {signalwire_agents-0.1.1.dist-info → signalwire_agents-0.1.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,26 @@
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
+
10
+ """
11
+ Prefab agents with specific functionality that can be used out-of-the-box
12
+ """
13
+
14
+ from signalwire_agents.prefabs.info_gatherer import InfoGathererAgent
15
+ from signalwire_agents.prefabs.faq_bot import FAQBotAgent
16
+ from signalwire_agents.prefabs.concierge import ConciergeAgent
17
+ from signalwire_agents.prefabs.survey import SurveyAgent
18
+ from signalwire_agents.prefabs.receptionist import ReceptionistAgent
19
+
20
+ __all__ = [
21
+ "InfoGathererAgent",
22
+ "FAQBotAgent",
23
+ "ConciergeAgent",
24
+ "SurveyAgent",
25
+ "ReceptionistAgent"
26
+ ]
@@ -0,0 +1,267 @@
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
+
10
+ """
11
+ ConciergeAgent - Prefab agent for providing virtual concierge services
12
+ """
13
+
14
+ from typing import List, Dict, Any, Optional, Union
15
+ import json
16
+ import os
17
+ from datetime import datetime
18
+
19
+ from signalwire_agents.core.agent_base import AgentBase
20
+ from signalwire_agents.core.function_result import SwaigFunctionResult
21
+
22
+
23
+ class ConciergeAgent(AgentBase):
24
+ """
25
+ A prefab agent designed to act as a virtual concierge, providing information
26
+ and services to users.
27
+
28
+ This agent will:
29
+ 1. Welcome users and explain available services
30
+ 2. Answer questions about amenities, hours, and directions
31
+ 3. Help with bookings and reservations
32
+ 4. Provide personalized recommendations
33
+
34
+ Example:
35
+ agent = ConciergeAgent(
36
+ venue_name="Grand Hotel",
37
+ services=["room service", "spa bookings", "restaurant reservations"],
38
+ amenities={
39
+ "pool": {"hours": "7 AM - 10 PM", "location": "2nd Floor"},
40
+ "gym": {"hours": "24 hours", "location": "3rd Floor"}
41
+ }
42
+ )
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ venue_name: str,
48
+ services: List[str],
49
+ amenities: Dict[str, Dict[str, str]],
50
+ hours_of_operation: Optional[Dict[str, str]] = None,
51
+ special_instructions: Optional[List[str]] = None,
52
+ welcome_message: Optional[str] = None,
53
+ **kwargs
54
+ ):
55
+ """
56
+ Initialize a concierge agent
57
+
58
+ Args:
59
+ venue_name: Name of the venue or business
60
+ services: List of services offered
61
+ amenities: Dictionary of amenities with details
62
+ hours_of_operation: Optional dictionary of operating hours
63
+ special_instructions: Optional list of special instructions
64
+ welcome_message: Optional custom welcome message
65
+ **kwargs: Additional arguments for AgentBase
66
+ """
67
+ # Initialize the base agent
68
+ super().__init__(
69
+ name="concierge",
70
+ route="/concierge",
71
+ use_pom=True,
72
+ **kwargs
73
+ )
74
+
75
+ # Store configuration
76
+ self.venue_name = venue_name
77
+ self.services = services
78
+ self.amenities = amenities
79
+ self.hours_of_operation = hours_of_operation or {"default": "9 AM - 5 PM"}
80
+ self.special_instructions = special_instructions or []
81
+
82
+ # Set up the agent's configuration
83
+ self._setup_concierge_agent(welcome_message)
84
+
85
+ def _setup_concierge_agent(self, welcome_message: Optional[str] = None):
86
+ """Configure the concierge agent with appropriate settings"""
87
+ # Basic personality and instructions
88
+ self.prompt_add_section("Personality",
89
+ body=f"You are a professional and helpful virtual concierge for {self.venue_name}."
90
+ )
91
+
92
+ self.prompt_add_section("Goal",
93
+ body="Provide exceptional service by helping users with information, recommendations, and booking assistance."
94
+ )
95
+
96
+ # Build detailed instructions
97
+ instructions = [
98
+ "Be warm and welcoming but professional at all times.",
99
+ "Provide accurate information about amenities, services, and operating hours.",
100
+ "Offer to help with reservations and bookings when appropriate.",
101
+ "Answer questions concisely with specific, relevant details."
102
+ ]
103
+
104
+ # Add any special instructions
105
+ instructions.extend(self.special_instructions)
106
+
107
+ self.prompt_add_section("Instructions", bullets=instructions)
108
+
109
+ # Services section
110
+ services_list = ", ".join(self.services)
111
+ self.prompt_add_section("Available Services",
112
+ body=f"The following services are available: {services_list}"
113
+ )
114
+
115
+ # Amenities section with details
116
+ amenities_subsections = []
117
+ for name, details in self.amenities.items():
118
+ subsection = {
119
+ "title": name.title(),
120
+ "body": "\n".join([f"{k.title()}: {v}" for k, v in details.items()])
121
+ }
122
+ amenities_subsections.append(subsection)
123
+
124
+ self.prompt_add_section("Amenities",
125
+ body="Information about available amenities:",
126
+ subsections=amenities_subsections
127
+ )
128
+
129
+ # Hours of operation
130
+ hours_list = "\n".join([f"{k.title()}: {v}" for k, v in self.hours_of_operation.items()])
131
+ self.prompt_add_section("Hours of Operation", body=hours_list)
132
+
133
+ # Set up the post-prompt for summary
134
+ self.set_post_prompt("""
135
+ Return a JSON summary of this interaction:
136
+ {
137
+ "topic": "MAIN_TOPIC",
138
+ "service_requested": "SPECIFIC_SERVICE_REQUESTED_OR_null",
139
+ "questions_answered": ["QUESTION_1", "QUESTION_2"],
140
+ "follow_up_needed": true/false
141
+ }
142
+ """)
143
+
144
+ # Configure hints for better understanding
145
+ self.add_hints([
146
+ self.venue_name,
147
+ *self.services,
148
+ *self.amenities.keys()
149
+ ])
150
+
151
+ # Set up parameters
152
+ self.set_params({
153
+ "wait_for_user": False,
154
+ "end_of_speech_timeout": 1000,
155
+ "ai_volume": 5,
156
+ "local_tz": "America/New_York"
157
+ })
158
+
159
+ # Add global data
160
+ self.set_global_data({
161
+ "venue_name": self.venue_name,
162
+ "services": self.services,
163
+ "amenities": self.amenities,
164
+ "hours": self.hours_of_operation
165
+ })
166
+
167
+ # Configure native functions
168
+ self.set_native_functions(["check_time"])
169
+
170
+ # Set custom welcome message if provided
171
+ if welcome_message:
172
+ self.set_params({
173
+ "static_greeting": welcome_message,
174
+ "static_greeting_no_barge": True
175
+ })
176
+
177
+ # Register tool methods
178
+ # These methods are already decorated with @AgentBase.tool in the class definition
179
+
180
+ @AgentBase.tool(
181
+ name="check_availability",
182
+ description="Check availability for a service on a specific date and time",
183
+ parameters={
184
+ "service": {
185
+ "type": "string",
186
+ "description": "The service to check (e.g., spa, restaurant)"
187
+ },
188
+ "date": {
189
+ "type": "string",
190
+ "description": "The date to check (YYYY-MM-DD format)"
191
+ },
192
+ "time": {
193
+ "type": "string",
194
+ "description": "The time to check (HH:MM format, 24-hour)"
195
+ }
196
+ }
197
+ )
198
+ def check_availability(self, args, raw_data):
199
+ """
200
+ Check availability for a service on a specific date and time
201
+
202
+ This is a simulated function that would typically connect to a real booking system.
203
+ In this example, it returns a mock availability response.
204
+ """
205
+ service = args.get("service", "").lower()
206
+ date = args.get("date", "")
207
+ time = args.get("time", "")
208
+
209
+ # Simple availability simulation - in a real application, this would
210
+ # connect to your actual booking system
211
+ if service in self.services:
212
+ # Generate a simple availability response
213
+ return SwaigFunctionResult(
214
+ f"Yes, {service} is available on {date} at {time}. Would you like to make a reservation?"
215
+ )
216
+ else:
217
+ return SwaigFunctionResult(
218
+ f"I'm sorry, we don't offer {service} at {self.venue_name}. "
219
+ f"Our available services are: {', '.join(self.services)}."
220
+ )
221
+
222
+ @AgentBase.tool(
223
+ name="get_directions",
224
+ description="Get directions to a specific location or amenity",
225
+ parameters={
226
+ "location": {
227
+ "type": "string",
228
+ "description": "The location or amenity to get directions to"
229
+ }
230
+ }
231
+ )
232
+ def get_directions(self, args, raw_data):
233
+ """Provide directions to a specific location or amenity"""
234
+ location = args.get("location", "").lower()
235
+
236
+ # Check if the location is an amenity
237
+ if location in self.amenities and "location" in self.amenities[location]:
238
+ amenity_location = self.amenities[location]["location"]
239
+ return SwaigFunctionResult(
240
+ f"The {location} is located at {amenity_location}. "
241
+ f"From the main entrance, follow the signs to {amenity_location}."
242
+ )
243
+ else:
244
+ return SwaigFunctionResult(
245
+ f"I don't have specific directions to {location}. "
246
+ f"You can ask our staff at the front desk for assistance."
247
+ )
248
+
249
+ def on_summary(self, summary, raw_data=None):
250
+ """
251
+ Process the interaction summary
252
+
253
+ Args:
254
+ summary: Summary data from the conversation
255
+ raw_data: The complete raw POST data from the request
256
+ """
257
+ if summary:
258
+ try:
259
+ # For structured summary
260
+ if isinstance(summary, dict):
261
+ print(f"Concierge interaction summary: {json.dumps(summary, indent=2)}")
262
+ else:
263
+ print(f"Concierge interaction summary: {summary}")
264
+
265
+ # Subclasses can override this to log or process the interaction
266
+ except Exception as e:
267
+ print(f"Error processing summary: {str(e)}")
@@ -0,0 +1,305 @@
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
+
10
+ """
11
+ FAQBotAgent - Prefab agent for answering frequently asked questions
12
+ """
13
+
14
+ from typing import List, Dict, Any, Optional, Union
15
+ import json
16
+ import os
17
+
18
+ from signalwire_agents.core.agent_base import AgentBase
19
+ from signalwire_agents.core.function_result import SwaigFunctionResult
20
+
21
+
22
+ class FAQBotAgent(AgentBase):
23
+ """
24
+ A prefab agent designed to answer frequently asked questions based on
25
+ a provided list of question/answer pairs.
26
+
27
+ This agent will:
28
+ 1. Match user questions against the FAQ database
29
+ 2. Provide the most relevant answer
30
+ 3. Suggest other relevant questions when appropriate
31
+
32
+ Example:
33
+ agent = FAQBotAgent(
34
+ faqs=[
35
+ {
36
+ "question": "What is SignalWire?",
37
+ "answer": "SignalWire is a developer-friendly cloud communications platform."
38
+ },
39
+ {
40
+ "question": "How much does it cost?",
41
+ "answer": "SignalWire offers pay-as-you-go pricing with no monthly fees."
42
+ }
43
+ ]
44
+ )
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ faqs: List[Dict[str, str]],
50
+ suggest_related: bool = True,
51
+ persona: Optional[str] = None,
52
+ name: str = "faq_bot",
53
+ route: str = "/faq",
54
+ **kwargs
55
+ ):
56
+ """
57
+ Initialize an FAQ bot agent
58
+
59
+ Args:
60
+ faqs: List of FAQ items, each with:
61
+ - question: The question text
62
+ - answer: The answer text
63
+ - categories: Optional list of category tags
64
+ suggest_related: Whether to suggest related questions
65
+ persona: Optional custom personality description
66
+ name: Agent name for the route
67
+ route: HTTP route for this agent
68
+ **kwargs: Additional arguments for AgentBase
69
+ """
70
+ # Initialize the base agent
71
+ super().__init__(
72
+ name=name,
73
+ route=route,
74
+ use_pom=True,
75
+ **kwargs
76
+ )
77
+
78
+ self.faqs = faqs
79
+ self.suggest_related = suggest_related
80
+ self.persona = persona or "You are a helpful FAQ bot that provides accurate answers to common questions."
81
+
82
+ # Build the prompt
83
+ self._build_faq_bot_prompt()
84
+
85
+ # Set up the post-prompt template
86
+ self._setup_post_prompt()
87
+
88
+ # Configure additional agent settings
89
+ self._configure_agent_settings()
90
+
91
+ def _build_faq_bot_prompt(self):
92
+ """Build the agent prompt for the FAQ bot"""
93
+ # Set up the personality
94
+ self.prompt_add_section(
95
+ "Personality",
96
+ body=self.persona
97
+ )
98
+
99
+ # Set up the goal
100
+ self.prompt_add_section(
101
+ "Goal",
102
+ body="Answer user questions by matching them to the most similar FAQ in your database."
103
+ )
104
+
105
+ # Set up the instructions
106
+ instructions = [
107
+ "Compare user questions to your FAQ database and find the best match.",
108
+ "Provide the answer from the FAQ database for the matching question.",
109
+ "If no close match exists, politely say you don't have that information.",
110
+ "Be concise and factual in your responses."
111
+ ]
112
+
113
+ # Add instruction about suggesting related questions if enabled
114
+ if self.suggest_related:
115
+ instructions.append(
116
+ "When appropriate, suggest other related questions from the FAQ database that might be helpful."
117
+ )
118
+
119
+ self.prompt_add_section(
120
+ "Instructions",
121
+ bullets=instructions
122
+ )
123
+
124
+ # Add FAQ Database section with subsections for each FAQ
125
+ faq_subsections = []
126
+ for faq in self.faqs:
127
+ question = faq.get("question", "")
128
+ answer = faq.get("answer", "")
129
+ categories = faq.get("categories", [])
130
+
131
+ # Skip invalid entries
132
+ if not question or not answer:
133
+ continue
134
+
135
+ # Build the body text with answer and optional categories
136
+ body_text = answer
137
+ if categories:
138
+ category_str = "Categories: " + ", ".join(categories)
139
+ body_text = f"{body_text}\n\n{category_str}"
140
+
141
+ faq_subsections.append({
142
+ "title": question,
143
+ "body": body_text
144
+ })
145
+
146
+ # Add the FAQ Database section with all FAQ subsections
147
+ self.prompt_add_section(
148
+ "FAQ Database",
149
+ body="Here is your database of frequently asked questions and answers:",
150
+ subsections=faq_subsections
151
+ )
152
+
153
+ # Add section about suggesting related questions if enabled
154
+ if self.suggest_related:
155
+ self.prompt_add_section(
156
+ "Related Questions",
157
+ body="When appropriate, suggest other related questions from the FAQ database that might be helpful."
158
+ )
159
+
160
+ def _setup_post_prompt(self):
161
+ """Set up the post-prompt for summary"""
162
+ post_prompt = """
163
+ Return a JSON summary of this interaction:
164
+ {
165
+ "question": "MAIN_QUESTION_ASKED",
166
+ "matched_faq": "MATCHED_FAQ_QUESTION_OR_null",
167
+ "answered_successfully": true/false,
168
+ "suggested_related": []
169
+ }
170
+ """
171
+ self.set_post_prompt(post_prompt)
172
+
173
+ def _configure_agent_settings(self):
174
+ """Configure additional agent settings"""
175
+ # Add hints for better recognition of FAQ-related terms
176
+ hints = []
177
+
178
+ # Add questions and categories as hints
179
+ for faq in self.faqs:
180
+ # Extract key terms from questions
181
+ question = faq.get("question", "")
182
+ if question:
183
+ # Split question into words and add words with 4+ characters
184
+ words = [word.strip(".,?!") for word in question.split() if len(word.strip(".,?!")) >= 4]
185
+ hints.extend(words)
186
+
187
+ # Add categories as hints
188
+ categories = faq.get("categories", [])
189
+ hints.extend(categories)
190
+
191
+ # Remove duplicates and add as hints
192
+ unique_hints = list(set(hints))
193
+ if unique_hints:
194
+ self.add_hints(unique_hints)
195
+
196
+ # Set AI behavior parameters
197
+ self.set_params({
198
+ "wait_for_user": False,
199
+ "end_of_speech_timeout": 1000,
200
+ "ai_volume": 5
201
+ })
202
+
203
+ # Add global data
204
+ self.set_global_data({
205
+ "faq_count": len(self.faqs),
206
+ "categories": list(set(
207
+ category
208
+ for faq in self.faqs
209
+ for category in faq.get("categories", [])
210
+ ))
211
+ })
212
+
213
+ # Configure native functions
214
+ self.set_native_functions(["check_time"])
215
+
216
+ @AgentBase.tool(
217
+ name="search_faqs",
218
+ description="Search for FAQs matching a specific query or category",
219
+ parameters={
220
+ "query": {
221
+ "type": "string",
222
+ "description": "The search query"
223
+ },
224
+ "category": {
225
+ "type": "string",
226
+ "description": "Optional category to filter by"
227
+ }
228
+ }
229
+ )
230
+ def search_faqs(self, args, raw_data):
231
+ """
232
+ Search for FAQs matching a specific query or category
233
+
234
+ This function helps find relevant FAQs based on a search query or category.
235
+ It returns matching FAQs in order of relevance.
236
+ """
237
+ query = args.get("query", "").lower()
238
+ category = args.get("category", "").lower()
239
+
240
+ # Simple search logic (in a real implementation, you would use more
241
+ # sophisticated search algorithms such as vector embeddings)
242
+ results = []
243
+
244
+ for faq in self.faqs:
245
+ question = faq.get("question", "").lower()
246
+ categories = [c.lower() for c in faq.get("categories", [])]
247
+ match_score = 0
248
+
249
+ # Match on query
250
+ if query and query in question:
251
+ # Simple substring matching - higher score for exact matches
252
+ if query == question:
253
+ match_score += 100
254
+ else:
255
+ match_score += 50
256
+
257
+ # Boost score for matches at the beginning of the question
258
+ if question.startswith(query):
259
+ match_score += 25
260
+
261
+ # Match on category
262
+ if category and category in categories:
263
+ match_score += 30
264
+
265
+ # Only include results with a positive match score
266
+ if match_score > 0:
267
+ results.append({
268
+ "question": faq.get("question"),
269
+ "score": match_score
270
+ })
271
+
272
+ # Sort results by score (highest first)
273
+ results.sort(key=lambda x: x["score"], reverse=True)
274
+
275
+ # Limit to top 3 results
276
+ top_results = results[:3]
277
+
278
+ if top_results:
279
+ result_text = "Here are the most relevant FAQs:\n\n"
280
+ for i, result in enumerate(top_results, 1):
281
+ result_text += f"{i}. {result['question']}\n"
282
+
283
+ return SwaigFunctionResult(result_text)
284
+ else:
285
+ return SwaigFunctionResult("No matching FAQs found.")
286
+
287
+ def on_summary(self, summary, raw_data=None):
288
+ """
289
+ Process the interaction summary
290
+
291
+ Args:
292
+ summary: Summary data from the conversation
293
+ raw_data: The complete raw POST data from the request
294
+ """
295
+ if summary:
296
+ try:
297
+ # For structured summary
298
+ if isinstance(summary, dict):
299
+ print(f"FAQ interaction summary: {json.dumps(summary, indent=2)}")
300
+ else:
301
+ print(f"FAQ interaction summary: {summary}")
302
+
303
+ # Subclasses can override this to log or save the interaction
304
+ except Exception as e:
305
+ print(f"Error processing summary: {str(e)}")