signalwire-agents 0.1.6__py3-none-any.whl → 1.0.7__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 +130 -4
- signalwire_agents/agent_server.py +438 -32
- signalwire_agents/agents/bedrock.py +296 -0
- signalwire_agents/cli/__init__.py +18 -0
- signalwire_agents/cli/build_search.py +1367 -0
- signalwire_agents/cli/config.py +80 -0
- signalwire_agents/cli/core/__init__.py +10 -0
- signalwire_agents/cli/core/agent_loader.py +470 -0
- signalwire_agents/cli/core/argparse_helpers.py +179 -0
- signalwire_agents/cli/core/dynamic_config.py +71 -0
- signalwire_agents/cli/core/service_loader.py +303 -0
- signalwire_agents/cli/execution/__init__.py +10 -0
- signalwire_agents/cli/execution/datamap_exec.py +446 -0
- signalwire_agents/cli/execution/webhook_exec.py +134 -0
- signalwire_agents/cli/init_project.py +1225 -0
- signalwire_agents/cli/output/__init__.py +10 -0
- signalwire_agents/cli/output/output_formatter.py +255 -0
- signalwire_agents/cli/output/swml_dump.py +186 -0
- signalwire_agents/cli/simulation/__init__.py +10 -0
- signalwire_agents/cli/simulation/data_generation.py +374 -0
- signalwire_agents/cli/simulation/data_overrides.py +200 -0
- signalwire_agents/cli/simulation/mock_env.py +282 -0
- signalwire_agents/cli/swaig_test_wrapper.py +52 -0
- signalwire_agents/cli/test_swaig.py +809 -0
- signalwire_agents/cli/types.py +81 -0
- signalwire_agents/core/__init__.py +2 -2
- signalwire_agents/core/agent/__init__.py +12 -0
- signalwire_agents/core/agent/config/__init__.py +12 -0
- signalwire_agents/core/agent/deployment/__init__.py +9 -0
- signalwire_agents/core/agent/deployment/handlers/__init__.py +9 -0
- signalwire_agents/core/agent/prompt/__init__.py +14 -0
- signalwire_agents/core/agent/prompt/manager.py +306 -0
- signalwire_agents/core/agent/routing/__init__.py +9 -0
- signalwire_agents/core/agent/security/__init__.py +9 -0
- signalwire_agents/core/agent/swml/__init__.py +9 -0
- signalwire_agents/core/agent/tools/__init__.py +15 -0
- signalwire_agents/core/agent/tools/decorator.py +97 -0
- signalwire_agents/core/agent/tools/registry.py +210 -0
- signalwire_agents/core/agent_base.py +959 -2166
- signalwire_agents/core/auth_handler.py +233 -0
- signalwire_agents/core/config_loader.py +259 -0
- signalwire_agents/core/contexts.py +707 -0
- signalwire_agents/core/data_map.py +487 -0
- signalwire_agents/core/function_result.py +1150 -1
- signalwire_agents/core/logging_config.py +376 -0
- signalwire_agents/core/mixins/__init__.py +28 -0
- signalwire_agents/core/mixins/ai_config_mixin.py +442 -0
- signalwire_agents/core/mixins/auth_mixin.py +287 -0
- signalwire_agents/core/mixins/prompt_mixin.py +358 -0
- signalwire_agents/core/mixins/serverless_mixin.py +368 -0
- signalwire_agents/core/mixins/skill_mixin.py +55 -0
- signalwire_agents/core/mixins/state_mixin.py +153 -0
- signalwire_agents/core/mixins/tool_mixin.py +230 -0
- signalwire_agents/core/mixins/web_mixin.py +1134 -0
- signalwire_agents/core/security/session_manager.py +174 -86
- signalwire_agents/core/security_config.py +333 -0
- signalwire_agents/core/skill_base.py +200 -0
- signalwire_agents/core/skill_manager.py +244 -0
- signalwire_agents/core/swaig_function.py +33 -9
- signalwire_agents/core/swml_builder.py +212 -12
- signalwire_agents/core/swml_handler.py +43 -13
- signalwire_agents/core/swml_renderer.py +123 -297
- signalwire_agents/core/swml_service.py +277 -260
- signalwire_agents/prefabs/concierge.py +6 -2
- signalwire_agents/prefabs/info_gatherer.py +149 -33
- signalwire_agents/prefabs/receptionist.py +14 -22
- signalwire_agents/prefabs/survey.py +6 -2
- signalwire_agents/schema.json +9218 -5489
- signalwire_agents/search/__init__.py +137 -0
- signalwire_agents/search/document_processor.py +1223 -0
- signalwire_agents/search/index_builder.py +804 -0
- signalwire_agents/search/migration.py +418 -0
- signalwire_agents/search/models.py +30 -0
- signalwire_agents/search/pgvector_backend.py +752 -0
- signalwire_agents/search/query_processor.py +502 -0
- signalwire_agents/search/search_engine.py +1264 -0
- signalwire_agents/search/search_service.py +574 -0
- signalwire_agents/skills/README.md +452 -0
- signalwire_agents/skills/__init__.py +23 -0
- signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
- signalwire_agents/skills/api_ninjas_trivia/__init__.py +12 -0
- signalwire_agents/skills/api_ninjas_trivia/skill.py +237 -0
- signalwire_agents/skills/datasphere/README.md +210 -0
- signalwire_agents/skills/datasphere/__init__.py +12 -0
- signalwire_agents/skills/datasphere/skill.py +310 -0
- signalwire_agents/skills/datasphere_serverless/README.md +258 -0
- signalwire_agents/skills/datasphere_serverless/__init__.py +10 -0
- signalwire_agents/skills/datasphere_serverless/skill.py +237 -0
- signalwire_agents/skills/datetime/README.md +132 -0
- signalwire_agents/skills/datetime/__init__.py +10 -0
- signalwire_agents/skills/datetime/skill.py +126 -0
- signalwire_agents/skills/joke/README.md +149 -0
- signalwire_agents/skills/joke/__init__.py +10 -0
- signalwire_agents/skills/joke/skill.py +109 -0
- signalwire_agents/skills/math/README.md +161 -0
- signalwire_agents/skills/math/__init__.py +10 -0
- signalwire_agents/skills/math/skill.py +105 -0
- signalwire_agents/skills/mcp_gateway/README.md +230 -0
- signalwire_agents/skills/mcp_gateway/__init__.py +10 -0
- signalwire_agents/skills/mcp_gateway/skill.py +421 -0
- signalwire_agents/skills/native_vector_search/README.md +210 -0
- signalwire_agents/skills/native_vector_search/__init__.py +10 -0
- signalwire_agents/skills/native_vector_search/skill.py +820 -0
- signalwire_agents/skills/play_background_file/README.md +218 -0
- signalwire_agents/skills/play_background_file/__init__.py +12 -0
- signalwire_agents/skills/play_background_file/skill.py +242 -0
- signalwire_agents/skills/registry.py +459 -0
- signalwire_agents/skills/spider/README.md +236 -0
- signalwire_agents/skills/spider/__init__.py +13 -0
- signalwire_agents/skills/spider/skill.py +598 -0
- signalwire_agents/skills/swml_transfer/README.md +395 -0
- signalwire_agents/skills/swml_transfer/__init__.py +10 -0
- signalwire_agents/skills/swml_transfer/skill.py +359 -0
- signalwire_agents/skills/weather_api/README.md +178 -0
- signalwire_agents/skills/weather_api/__init__.py +12 -0
- signalwire_agents/skills/weather_api/skill.py +191 -0
- signalwire_agents/skills/web_search/README.md +163 -0
- signalwire_agents/skills/web_search/__init__.py +10 -0
- signalwire_agents/skills/web_search/skill.py +739 -0
- signalwire_agents/skills/wikipedia_search/README.md +228 -0
- signalwire_agents/{core/state → skills/wikipedia_search}/__init__.py +5 -4
- signalwire_agents/skills/wikipedia_search/skill.py +210 -0
- signalwire_agents/utils/__init__.py +14 -0
- signalwire_agents/utils/schema_utils.py +111 -44
- signalwire_agents/web/__init__.py +17 -0
- signalwire_agents/web/web_service.py +559 -0
- signalwire_agents-1.0.7.data/data/share/man/man1/sw-agent-init.1 +307 -0
- signalwire_agents-1.0.7.data/data/share/man/man1/sw-search.1 +483 -0
- signalwire_agents-1.0.7.data/data/share/man/man1/swaig-test.1 +308 -0
- signalwire_agents-1.0.7.dist-info/METADATA +992 -0
- signalwire_agents-1.0.7.dist-info/RECORD +142 -0
- {signalwire_agents-0.1.6.dist-info → signalwire_agents-1.0.7.dist-info}/WHEEL +1 -1
- signalwire_agents-1.0.7.dist-info/entry_points.txt +4 -0
- signalwire_agents/core/state/file_state_manager.py +0 -219
- signalwire_agents/core/state/state_manager.py +0 -101
- signalwire_agents-0.1.6.data/data/schema.json +0 -5611
- signalwire_agents-0.1.6.dist-info/METADATA +0 -199
- signalwire_agents-0.1.6.dist-info/RECORD +0 -34
- {signalwire_agents-0.1.6.dist-info → signalwire_agents-1.0.7.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.6.dist-info → signalwire_agents-1.0.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,310 @@
|
|
|
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
|
+
import requests
|
|
11
|
+
import json
|
|
12
|
+
from typing import Optional, List, Dict, Any
|
|
13
|
+
|
|
14
|
+
from signalwire_agents.core.skill_base import SkillBase
|
|
15
|
+
from signalwire_agents.core.function_result import SwaigFunctionResult
|
|
16
|
+
|
|
17
|
+
class DataSphereSkill(SkillBase):
|
|
18
|
+
"""SignalWire DataSphere knowledge search capability"""
|
|
19
|
+
|
|
20
|
+
SKILL_NAME = "datasphere"
|
|
21
|
+
SKILL_DESCRIPTION = "Search knowledge using SignalWire DataSphere RAG stack"
|
|
22
|
+
SKILL_VERSION = "1.0.0"
|
|
23
|
+
REQUIRED_PACKAGES = ["requests"]
|
|
24
|
+
REQUIRED_ENV_VARS = [] # No required env vars since all config comes from params
|
|
25
|
+
|
|
26
|
+
# Enable multiple instances support
|
|
27
|
+
SUPPORTS_MULTIPLE_INSTANCES = True
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def get_parameter_schema(cls) -> Dict[str, Dict[str, Any]]:
|
|
31
|
+
"""Get parameter schema for DataSphere skill"""
|
|
32
|
+
schema = super().get_parameter_schema()
|
|
33
|
+
schema.update({
|
|
34
|
+
"space_name": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "SignalWire space name (e.g., 'mycompany' from mycompany.signalwire.com)",
|
|
37
|
+
"required": True
|
|
38
|
+
},
|
|
39
|
+
"project_id": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "SignalWire project ID",
|
|
42
|
+
"required": True,
|
|
43
|
+
"env_var": "SIGNALWIRE_PROJECT_ID"
|
|
44
|
+
},
|
|
45
|
+
"token": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"description": "SignalWire API token",
|
|
48
|
+
"required": True,
|
|
49
|
+
"hidden": True,
|
|
50
|
+
"env_var": "SIGNALWIRE_TOKEN"
|
|
51
|
+
},
|
|
52
|
+
"document_id": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "DataSphere document ID to search within",
|
|
55
|
+
"required": True
|
|
56
|
+
},
|
|
57
|
+
"count": {
|
|
58
|
+
"type": "integer",
|
|
59
|
+
"description": "Number of search results to return",
|
|
60
|
+
"default": 1,
|
|
61
|
+
"required": False,
|
|
62
|
+
"minimum": 1,
|
|
63
|
+
"maximum": 10
|
|
64
|
+
},
|
|
65
|
+
"distance": {
|
|
66
|
+
"type": "number",
|
|
67
|
+
"description": "Maximum distance threshold for results (lower is more relevant)",
|
|
68
|
+
"default": 3.0,
|
|
69
|
+
"required": False,
|
|
70
|
+
"minimum": 0.0,
|
|
71
|
+
"maximum": 10.0
|
|
72
|
+
},
|
|
73
|
+
"tags": {
|
|
74
|
+
"type": "array",
|
|
75
|
+
"description": "Tags to filter search results",
|
|
76
|
+
"required": False,
|
|
77
|
+
"items": {
|
|
78
|
+
"type": "string"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"language": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"description": "Language code for query expansion (e.g., 'en', 'es')",
|
|
84
|
+
"required": False
|
|
85
|
+
},
|
|
86
|
+
"pos_to_expand": {
|
|
87
|
+
"type": "array",
|
|
88
|
+
"description": "Parts of speech to expand with synonyms",
|
|
89
|
+
"required": False,
|
|
90
|
+
"items": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"enum": ["NOUN", "VERB", "ADJ", "ADV"]
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"max_synonyms": {
|
|
96
|
+
"type": "integer",
|
|
97
|
+
"description": "Maximum number of synonyms to use for query expansion",
|
|
98
|
+
"required": False,
|
|
99
|
+
"minimum": 1,
|
|
100
|
+
"maximum": 10
|
|
101
|
+
},
|
|
102
|
+
"no_results_message": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"description": "Message to return when no results are found",
|
|
105
|
+
"default": "I couldn't find any relevant information for '{query}' in the knowledge base. Try rephrasing your question or asking about a different topic.",
|
|
106
|
+
"required": False
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
return schema
|
|
110
|
+
|
|
111
|
+
def get_instance_key(self) -> str:
|
|
112
|
+
"""
|
|
113
|
+
Get the key used to track this skill instance
|
|
114
|
+
|
|
115
|
+
For DataSphere, we use 'search_knowledge' as the default tool name instead of 'datasphere'
|
|
116
|
+
"""
|
|
117
|
+
tool_name = self.params.get('tool_name', 'search_knowledge')
|
|
118
|
+
return f"{self.SKILL_NAME}_{tool_name}"
|
|
119
|
+
|
|
120
|
+
def setup(self) -> bool:
|
|
121
|
+
"""Setup the datasphere skill"""
|
|
122
|
+
# Validate required parameters
|
|
123
|
+
required_params = ['space_name', 'project_id', 'token', 'document_id']
|
|
124
|
+
missing_params = [param for param in required_params if not self.params.get(param)]
|
|
125
|
+
if missing_params:
|
|
126
|
+
self.logger.error(f"Missing required parameters: {missing_params}")
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
# Set required parameters
|
|
130
|
+
self.space_name = self.params['space_name']
|
|
131
|
+
self.project_id = self.params['project_id']
|
|
132
|
+
self.token = self.params['token']
|
|
133
|
+
self.document_id = self.params['document_id']
|
|
134
|
+
|
|
135
|
+
# Set optional parameters with defaults
|
|
136
|
+
self.count = self.params.get('count', 1)
|
|
137
|
+
self.distance = self.params.get('distance', 3.0)
|
|
138
|
+
self.tags = self.params.get('tags', None) # None means don't include in request
|
|
139
|
+
self.language = self.params.get('language', None) # None means don't include in request
|
|
140
|
+
self.pos_to_expand = self.params.get('pos_to_expand', None) # None means don't include in request
|
|
141
|
+
self.max_synonyms = self.params.get('max_synonyms', None) # None means don't include in request
|
|
142
|
+
|
|
143
|
+
# Tool name (for multiple instances)
|
|
144
|
+
self.tool_name = self.params.get('tool_name', 'search_knowledge')
|
|
145
|
+
|
|
146
|
+
# No results message
|
|
147
|
+
self.no_results_message = self.params.get('no_results_message',
|
|
148
|
+
"I couldn't find any relevant information for '{query}' in the knowledge base. "
|
|
149
|
+
"Try rephrasing your question or asking about a different topic."
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Build API URL
|
|
153
|
+
self.api_url = f"https://{self.space_name}.signalwire.com/api/datasphere/documents/search"
|
|
154
|
+
|
|
155
|
+
# Setup session for requests
|
|
156
|
+
self.session = requests.Session()
|
|
157
|
+
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
def register_tools(self) -> None:
|
|
161
|
+
"""Register knowledge search tool with the agent"""
|
|
162
|
+
self.define_tool(
|
|
163
|
+
name=self.tool_name,
|
|
164
|
+
description="Search the knowledge base for information on any topic and return relevant results",
|
|
165
|
+
parameters={
|
|
166
|
+
"query": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"description": "The search query - what information you're looking for in the knowledge base"
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
handler=self._search_knowledge_handler
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def _search_knowledge_handler(self, args, raw_data):
|
|
175
|
+
"""Handler for knowledge search tool"""
|
|
176
|
+
query = args.get("query", "").strip()
|
|
177
|
+
|
|
178
|
+
if not query:
|
|
179
|
+
return SwaigFunctionResult(
|
|
180
|
+
"Please provide a search query. What would you like me to search for in the knowledge base?"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
self.logger.info(f"DataSphere search requested: '{query}' (document: {self.document_id})")
|
|
184
|
+
|
|
185
|
+
# Build request payload
|
|
186
|
+
payload = {
|
|
187
|
+
"document_id": self.document_id,
|
|
188
|
+
"query_string": query,
|
|
189
|
+
"distance": self.distance,
|
|
190
|
+
"count": self.count
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# Add optional parameters only if they were provided
|
|
194
|
+
if self.tags is not None:
|
|
195
|
+
payload["tags"] = self.tags
|
|
196
|
+
if self.language is not None:
|
|
197
|
+
payload["language"] = self.language
|
|
198
|
+
if self.pos_to_expand is not None:
|
|
199
|
+
payload["pos_to_expand"] = self.pos_to_expand
|
|
200
|
+
if self.max_synonyms is not None:
|
|
201
|
+
payload["max_synonyms"] = self.max_synonyms
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
# Make API request
|
|
205
|
+
response = self.session.post(
|
|
206
|
+
self.api_url,
|
|
207
|
+
auth=(self.project_id, self.token),
|
|
208
|
+
headers={
|
|
209
|
+
'Content-Type': 'application/json',
|
|
210
|
+
'Accept': 'application/json'
|
|
211
|
+
},
|
|
212
|
+
json=payload,
|
|
213
|
+
timeout=30
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
response.raise_for_status()
|
|
217
|
+
data = response.json()
|
|
218
|
+
|
|
219
|
+
# Check if we have valid response data
|
|
220
|
+
if not data or not isinstance(data, dict):
|
|
221
|
+
self.logger.warning(f"DataSphere API returned invalid data: {data}")
|
|
222
|
+
formatted_message = self.no_results_message.format(query=query) if '{query}' in self.no_results_message else self.no_results_message
|
|
223
|
+
return SwaigFunctionResult(formatted_message)
|
|
224
|
+
|
|
225
|
+
# Extract results - DataSphere API returns 'chunks', not 'results'
|
|
226
|
+
chunks = data.get('chunks', [])
|
|
227
|
+
|
|
228
|
+
if not chunks:
|
|
229
|
+
formatted_message = self.no_results_message.format(query=query) if '{query}' in self.no_results_message else self.no_results_message
|
|
230
|
+
return SwaigFunctionResult(formatted_message)
|
|
231
|
+
|
|
232
|
+
# Format the results
|
|
233
|
+
formatted_results = self._format_search_results(query, chunks)
|
|
234
|
+
return SwaigFunctionResult(formatted_results)
|
|
235
|
+
|
|
236
|
+
except requests.exceptions.Timeout:
|
|
237
|
+
self.logger.error("DataSphere API request timed out")
|
|
238
|
+
return SwaigFunctionResult(
|
|
239
|
+
"Sorry, the knowledge search timed out. Please try again."
|
|
240
|
+
)
|
|
241
|
+
except requests.exceptions.HTTPError as e:
|
|
242
|
+
self.logger.error(f"DataSphere API HTTP error: {e}")
|
|
243
|
+
return SwaigFunctionResult(
|
|
244
|
+
"Sorry, there was an error accessing the knowledge base. Please try again later."
|
|
245
|
+
)
|
|
246
|
+
except Exception as e:
|
|
247
|
+
self.logger.error(f"Error performing DataSphere search: {e}")
|
|
248
|
+
return SwaigFunctionResult(
|
|
249
|
+
"Sorry, I encountered an error while searching the knowledge base. Please try again later."
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def _format_search_results(self, query: str, chunks: List[Dict[str, Any]]) -> str:
|
|
253
|
+
"""Format search results for display"""
|
|
254
|
+
if len(chunks) == 1:
|
|
255
|
+
result_text = f"I found 1 result for '{query}':\n\n"
|
|
256
|
+
else:
|
|
257
|
+
result_text = f"I found {len(chunks)} results for '{query}':\n\n"
|
|
258
|
+
|
|
259
|
+
formatted_results = []
|
|
260
|
+
|
|
261
|
+
for i, chunk in enumerate(chunks, 1):
|
|
262
|
+
result_content = f"=== RESULT {i} ===\n"
|
|
263
|
+
|
|
264
|
+
# DataSphere API returns chunks with 'text' field
|
|
265
|
+
if 'text' in chunk:
|
|
266
|
+
result_content += chunk['text']
|
|
267
|
+
elif 'content' in chunk:
|
|
268
|
+
result_content += chunk['content']
|
|
269
|
+
elif 'chunk' in chunk:
|
|
270
|
+
result_content += chunk['chunk']
|
|
271
|
+
else:
|
|
272
|
+
# Fallback to the entire result as JSON if we don't recognize the format
|
|
273
|
+
result_content += json.dumps(chunk, indent=2)
|
|
274
|
+
|
|
275
|
+
result_content += f"\n{'='*50}\n\n"
|
|
276
|
+
formatted_results.append(result_content)
|
|
277
|
+
|
|
278
|
+
return result_text + '\n'.join(formatted_results)
|
|
279
|
+
|
|
280
|
+
def get_hints(self) -> List[str]:
|
|
281
|
+
"""Return speech recognition hints"""
|
|
282
|
+
# Currently no hints provided, but you could add them like:
|
|
283
|
+
# return [
|
|
284
|
+
# "knowledge", "search", "information", "database", "find",
|
|
285
|
+
# "look up", "research", "query", "datasphere", "document"
|
|
286
|
+
# ]
|
|
287
|
+
return []
|
|
288
|
+
|
|
289
|
+
def get_global_data(self) -> Dict[str, Any]:
|
|
290
|
+
"""Return global data for agent context"""
|
|
291
|
+
return {
|
|
292
|
+
"datasphere_enabled": True,
|
|
293
|
+
"document_id": self.document_id,
|
|
294
|
+
"knowledge_provider": "SignalWire DataSphere"
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
def get_prompt_sections(self) -> List[Dict[str, Any]]:
|
|
298
|
+
"""Return prompt sections to add to agent"""
|
|
299
|
+
return [
|
|
300
|
+
{
|
|
301
|
+
"title": "Knowledge Search Capability",
|
|
302
|
+
"body": f"You can search a knowledge base for information using the {self.tool_name} tool.",
|
|
303
|
+
"bullets": [
|
|
304
|
+
f"Use the {self.tool_name} tool when users ask for information that might be in the knowledge base",
|
|
305
|
+
"Search for relevant information using clear, specific queries",
|
|
306
|
+
"Summarize search results in a clear, helpful way",
|
|
307
|
+
"If no results are found, suggest the user try rephrasing their question"
|
|
308
|
+
]
|
|
309
|
+
}
|
|
310
|
+
]
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# DataSphere Serverless Skill
|
|
2
|
+
|
|
3
|
+
The datasphere_serverless skill provides knowledge search capabilities using SignalWire DataSphere's RAG (Retrieval-Augmented Generation) stack with serverless execution via DataMap. This skill offers the same functionality as the standard datasphere skill but executes on SignalWire servers rather than your agent server.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Serverless Execution**: Runs on SignalWire infrastructure via DataMap
|
|
8
|
+
- **SignalWire DataSphere Integration**: Vector-based knowledge search
|
|
9
|
+
- **Identical API**: Same parameters and functionality as the standard datasphere skill
|
|
10
|
+
- **Multi-language Support**: Synonym expansion and language-specific search
|
|
11
|
+
- **Tag-based Filtering**: Targeted searches using document tags
|
|
12
|
+
- **Custom No-results Messages**: Configurable response templates
|
|
13
|
+
- **Multiple Instance Support**: Search different knowledge bases with different configurations
|
|
14
|
+
- **No Webhook Infrastructure**: No need to expose HTTP endpoints
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- **Packages**: None (DataMap handles API calls serverlessly)
|
|
19
|
+
- **SignalWire Account**: DataSphere-enabled space with uploaded documents
|
|
20
|
+
|
|
21
|
+
## Parameters
|
|
22
|
+
|
|
23
|
+
### Required Parameters
|
|
24
|
+
|
|
25
|
+
- `space_name` (string): SignalWire space name
|
|
26
|
+
- `project_id` (string): SignalWire project ID
|
|
27
|
+
- `token` (string): SignalWire authentication token
|
|
28
|
+
- `document_id` (string): DataSphere document ID to search
|
|
29
|
+
|
|
30
|
+
### Optional Parameters
|
|
31
|
+
|
|
32
|
+
- `count` (integer, default: 1): Number of search results to return
|
|
33
|
+
- `distance` (float, default: 3.0): Distance threshold for search matching (lower = more similar)
|
|
34
|
+
- `tags` (list): List of tags to filter search results
|
|
35
|
+
- `language` (string): Language code to limit search (e.g., "en", "es")
|
|
36
|
+
- `pos_to_expand` (list): Parts of speech for synonym expansion (e.g., ["NOUN", "VERB"])
|
|
37
|
+
- `max_synonyms` (integer): Maximum number of synonyms to use for each word
|
|
38
|
+
- `tool_name` (string, default: "search_knowledge"): Custom name for the search tool (enables multiple instances)
|
|
39
|
+
- `no_results_message` (string): Custom message when no results are found
|
|
40
|
+
- Default: "I couldn't find any relevant information for '{query}' in the knowledge base. Try rephrasing your question or asking about a different topic."
|
|
41
|
+
- Use `{query}` as placeholder for the search query
|
|
42
|
+
|
|
43
|
+
### Advanced Parameters
|
|
44
|
+
|
|
45
|
+
- `swaig_fields` (dict): Additional SWAIG function configuration
|
|
46
|
+
- `secure` (boolean): Override security settings
|
|
47
|
+
- `fillers` (dict): Language-specific filler phrases during search
|
|
48
|
+
- Any other SWAIG function parameters
|
|
49
|
+
|
|
50
|
+
## Tools Created
|
|
51
|
+
|
|
52
|
+
- **Default**: `search_knowledge` - Search the knowledge base for information
|
|
53
|
+
- **Custom**: Uses the `tool_name` parameter value
|
|
54
|
+
|
|
55
|
+
## Usage Examples
|
|
56
|
+
|
|
57
|
+
### Basic Usage
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
# Minimal configuration - same as standard datasphere skill
|
|
61
|
+
agent.add_skill("datasphere_serverless", {
|
|
62
|
+
"space_name": "my-space",
|
|
63
|
+
"project_id": "my-project-id",
|
|
64
|
+
"token": "my-token",
|
|
65
|
+
"document_id": "my-document-id"
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Advanced Configuration
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
# Comprehensive search with filtering - identical to standard skill
|
|
73
|
+
agent.add_skill("datasphere_serverless", {
|
|
74
|
+
"space_name": "my-space",
|
|
75
|
+
"project_id": "my-project-id",
|
|
76
|
+
"token": "my-token",
|
|
77
|
+
"document_id": "my-document-id",
|
|
78
|
+
"count": 3,
|
|
79
|
+
"distance": 5.0,
|
|
80
|
+
"tags": ["FAQ", "Support"],
|
|
81
|
+
"language": "en",
|
|
82
|
+
"pos_to_expand": ["NOUN", "VERB"],
|
|
83
|
+
"max_synonyms": 3,
|
|
84
|
+
"no_results_message": "I couldn't find information about '{query}' in our support documentation."
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Multiple Instances
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
# Product documentation search
|
|
92
|
+
agent.add_skill("datasphere_serverless", {
|
|
93
|
+
"space_name": "my-space",
|
|
94
|
+
"project_id": "my-project-id",
|
|
95
|
+
"token": "my-token",
|
|
96
|
+
"document_id": "product-docs-id",
|
|
97
|
+
"tool_name": "search_product_docs",
|
|
98
|
+
"tags": ["Products", "Features"],
|
|
99
|
+
"count": 2
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
# Support knowledge base search
|
|
103
|
+
agent.add_skill("datasphere_serverless", {
|
|
104
|
+
"space_name": "my-space",
|
|
105
|
+
"project_id": "my-project-id",
|
|
106
|
+
"token": "my-token",
|
|
107
|
+
"document_id": "support-kb-id",
|
|
108
|
+
"tool_name": "search_support",
|
|
109
|
+
"tags": ["Support", "Troubleshooting"],
|
|
110
|
+
"count": 3,
|
|
111
|
+
"distance": 4.0
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## DataMap Implementation Details
|
|
116
|
+
|
|
117
|
+
This skill demonstrates advanced DataMap usage patterns:
|
|
118
|
+
|
|
119
|
+
### 1. **Serverless API Integration**
|
|
120
|
+
- API calls execute on SignalWire servers, not your agent server
|
|
121
|
+
- No webhook endpoints required
|
|
122
|
+
- Built-in authentication and error handling
|
|
123
|
+
|
|
124
|
+
### 2. **Dynamic Request Building**
|
|
125
|
+
```python
|
|
126
|
+
webhook_body = {
|
|
127
|
+
"document_id": self.document_id,
|
|
128
|
+
"query_string": "${args.query}", # Dynamic from user input
|
|
129
|
+
"distance": self.distance, # Static from configuration
|
|
130
|
+
"count": self.count # Static from configuration
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Optional parameters added conditionally
|
|
134
|
+
if self.tags is not None:
|
|
135
|
+
webhook_body["tags"] = self.tags
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 3. **Response Processing with Foreach**
|
|
139
|
+
```python
|
|
140
|
+
.foreach({
|
|
141
|
+
"input_key": "results", # API response key containing array
|
|
142
|
+
"output_key": "formatted_results", # Name for built string
|
|
143
|
+
"max": self.count, # Limit processing
|
|
144
|
+
"append": "=== RESULT ${this.index} ===\n${this.content}\n========\n\n"
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The `foreach` mechanism:
|
|
149
|
+
- Iterates over the `results` array from DataSphere API response
|
|
150
|
+
- For each result, expands `${this.content}` with the result's content field
|
|
151
|
+
- Builds a concatenated string stored as `formatted_results`
|
|
152
|
+
- Limits processing to `max` items
|
|
153
|
+
|
|
154
|
+
### 4. **Variable Expansion in Output**
|
|
155
|
+
```python
|
|
156
|
+
.output(SwaigFunctionResult('I found ${results.length} result(s) for "${args.query}":\n\n${formatted_results}'))
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
References:
|
|
160
|
+
- `${results.length}`: Number of results from API
|
|
161
|
+
- `${args.query}`: User's search query
|
|
162
|
+
- `${formatted_results}`: String built by foreach
|
|
163
|
+
|
|
164
|
+
### 5. **Error Handling**
|
|
165
|
+
```python
|
|
166
|
+
.error_keys(['error', 'message'])
|
|
167
|
+
.fallback_output(SwaigFunctionResult(self.no_results_message.replace('{query}', '${args.query}')))
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Comparison: Standard vs Serverless
|
|
171
|
+
|
|
172
|
+
| Feature | Standard DataSphere | DataSphere Serverless |
|
|
173
|
+
|---------|-------------------|---------------------|
|
|
174
|
+
| **Execution** | Agent server | SignalWire servers |
|
|
175
|
+
| **Parameters** | Identical | Identical |
|
|
176
|
+
| **Functionality** | Full Python logic | DataMap templates |
|
|
177
|
+
| **Performance** | Agent server load | No agent server load |
|
|
178
|
+
| **Deployment** | Webhook infrastructure | No infrastructure needed |
|
|
179
|
+
| **Response Formatting** | Custom Python code | DataMap foreach/templates |
|
|
180
|
+
| **Error Handling** | Granular Python exceptions | DataMap error keys |
|
|
181
|
+
| **Use Case** | Complex custom logic | Standard API integration |
|
|
182
|
+
|
|
183
|
+
## When to Use Serverless vs Standard
|
|
184
|
+
|
|
185
|
+
### **Use DataSphere Serverless When:**
|
|
186
|
+
- You want simple deployment without webhook infrastructure
|
|
187
|
+
- Performance on agent server is a concern
|
|
188
|
+
- Standard response formatting is sufficient
|
|
189
|
+
- You prefer serverless execution model
|
|
190
|
+
|
|
191
|
+
### **Use Standard DataSphere When:**
|
|
192
|
+
- You need complex custom response formatting
|
|
193
|
+
- You want granular error handling with different messages per error type
|
|
194
|
+
- You need runtime decision-making logic
|
|
195
|
+
- You want full control over the search process
|
|
196
|
+
|
|
197
|
+
## Benefits of DataMap Implementation
|
|
198
|
+
|
|
199
|
+
1. **Simplified Deployment**: No HTTP endpoints to expose or manage
|
|
200
|
+
2. **Better Performance**: Executes on SignalWire infrastructure
|
|
201
|
+
3. **Reduced Complexity**: Declarative configuration vs imperative code
|
|
202
|
+
4. **Automatic Scaling**: SignalWire handles execution scaling
|
|
203
|
+
5. **Built-in Reliability**: Server-side execution with built-in retry logic
|
|
204
|
+
|
|
205
|
+
## How It Works
|
|
206
|
+
|
|
207
|
+
1. **Configuration**: Skill parameters are validated and stored during setup
|
|
208
|
+
2. **Tool Registration**: DataMap configuration is built with static values from setup
|
|
209
|
+
3. **Execution**: When called, DataMap executes on SignalWire servers:
|
|
210
|
+
- Makes POST request to DataSphere API with user's query
|
|
211
|
+
- Processes response array using foreach mechanism
|
|
212
|
+
- Formats results using template expansion
|
|
213
|
+
- Returns formatted response to agent
|
|
214
|
+
4. **Response**: Agent receives formatted results without any local processing
|
|
215
|
+
|
|
216
|
+
## Multiple Instance Support
|
|
217
|
+
|
|
218
|
+
Like the standard datasphere skill, this supports multiple instances:
|
|
219
|
+
|
|
220
|
+
- Each instance creates a tool with a unique name (`tool_name` parameter)
|
|
221
|
+
- Different configurations per instance (different documents, tags, etc.)
|
|
222
|
+
- Instance tracking via `get_instance_key()` method
|
|
223
|
+
- Same agent can search multiple knowledge bases
|
|
224
|
+
|
|
225
|
+
## Error Handling
|
|
226
|
+
|
|
227
|
+
- **API Errors**: Handled by `error_keys` configuration
|
|
228
|
+
- **No Results**: Uses `fallback_output` with custom message
|
|
229
|
+
- **Invalid Parameters**: Validated during skill setup
|
|
230
|
+
- **Timeout/Network**: Handled by SignalWire infrastructure
|
|
231
|
+
|
|
232
|
+
## Troubleshooting
|
|
233
|
+
|
|
234
|
+
### Common Issues
|
|
235
|
+
|
|
236
|
+
1. **"Missing required parameters"**
|
|
237
|
+
- Ensure all required parameters are provided
|
|
238
|
+
- Check parameter names match exactly
|
|
239
|
+
|
|
240
|
+
2. **"No results found"**
|
|
241
|
+
- Verify document_id exists and is accessible
|
|
242
|
+
- Check distance threshold isn't too restrictive
|
|
243
|
+
- Ensure tags match document tags if specified
|
|
244
|
+
|
|
245
|
+
3. **"Authentication failed"**
|
|
246
|
+
- Verify project_id and token are correct
|
|
247
|
+
- Ensure token has DataSphere permissions
|
|
248
|
+
|
|
249
|
+
### Debugging
|
|
250
|
+
|
|
251
|
+
Enable debug logging to see DataMap execution:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
import logging
|
|
255
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
DataMap execution details are logged by the SignalWire server infrastructure.
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
"""DataSphere Serverless Skill for SignalWire Agents using DataMap"""
|