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