signalwire-agents 0.1.13__py3-none-any.whl → 1.0.17.dev4__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 (143) hide show
  1. signalwire_agents/__init__.py +99 -15
  2. signalwire_agents/agent_server.py +248 -60
  3. signalwire_agents/agents/bedrock.py +296 -0
  4. signalwire_agents/cli/__init__.py +9 -0
  5. signalwire_agents/cli/build_search.py +951 -41
  6. signalwire_agents/cli/config.py +80 -0
  7. signalwire_agents/cli/core/__init__.py +10 -0
  8. signalwire_agents/cli/core/agent_loader.py +470 -0
  9. signalwire_agents/cli/core/argparse_helpers.py +179 -0
  10. signalwire_agents/cli/core/dynamic_config.py +71 -0
  11. signalwire_agents/cli/core/service_loader.py +303 -0
  12. signalwire_agents/cli/dokku.py +2320 -0
  13. signalwire_agents/cli/execution/__init__.py +10 -0
  14. signalwire_agents/cli/execution/datamap_exec.py +446 -0
  15. signalwire_agents/cli/execution/webhook_exec.py +134 -0
  16. signalwire_agents/cli/init_project.py +2636 -0
  17. signalwire_agents/cli/output/__init__.py +10 -0
  18. signalwire_agents/cli/output/output_formatter.py +255 -0
  19. signalwire_agents/cli/output/swml_dump.py +186 -0
  20. signalwire_agents/cli/simulation/__init__.py +10 -0
  21. signalwire_agents/cli/simulation/data_generation.py +374 -0
  22. signalwire_agents/cli/simulation/data_overrides.py +200 -0
  23. signalwire_agents/cli/simulation/mock_env.py +282 -0
  24. signalwire_agents/cli/swaig_test_wrapper.py +52 -0
  25. signalwire_agents/cli/test_swaig.py +566 -2366
  26. signalwire_agents/cli/types.py +81 -0
  27. signalwire_agents/core/__init__.py +2 -2
  28. signalwire_agents/core/agent/__init__.py +12 -0
  29. signalwire_agents/core/agent/config/__init__.py +12 -0
  30. signalwire_agents/core/agent/deployment/__init__.py +9 -0
  31. signalwire_agents/core/agent/deployment/handlers/__init__.py +9 -0
  32. signalwire_agents/core/agent/prompt/__init__.py +14 -0
  33. signalwire_agents/core/agent/prompt/manager.py +306 -0
  34. signalwire_agents/core/agent/routing/__init__.py +9 -0
  35. signalwire_agents/core/agent/security/__init__.py +9 -0
  36. signalwire_agents/core/agent/swml/__init__.py +9 -0
  37. signalwire_agents/core/agent/tools/__init__.py +15 -0
  38. signalwire_agents/core/agent/tools/decorator.py +97 -0
  39. signalwire_agents/core/agent/tools/registry.py +210 -0
  40. signalwire_agents/core/agent_base.py +845 -2916
  41. signalwire_agents/core/auth_handler.py +233 -0
  42. signalwire_agents/core/config_loader.py +259 -0
  43. signalwire_agents/core/contexts.py +418 -0
  44. signalwire_agents/core/data_map.py +3 -15
  45. signalwire_agents/core/function_result.py +116 -44
  46. signalwire_agents/core/logging_config.py +162 -18
  47. signalwire_agents/core/mixins/__init__.py +28 -0
  48. signalwire_agents/core/mixins/ai_config_mixin.py +442 -0
  49. signalwire_agents/core/mixins/auth_mixin.py +280 -0
  50. signalwire_agents/core/mixins/prompt_mixin.py +358 -0
  51. signalwire_agents/core/mixins/serverless_mixin.py +460 -0
  52. signalwire_agents/core/mixins/skill_mixin.py +55 -0
  53. signalwire_agents/core/mixins/state_mixin.py +153 -0
  54. signalwire_agents/core/mixins/tool_mixin.py +230 -0
  55. signalwire_agents/core/mixins/web_mixin.py +1142 -0
  56. signalwire_agents/core/security_config.py +333 -0
  57. signalwire_agents/core/skill_base.py +84 -1
  58. signalwire_agents/core/skill_manager.py +62 -20
  59. signalwire_agents/core/swaig_function.py +18 -5
  60. signalwire_agents/core/swml_builder.py +207 -11
  61. signalwire_agents/core/swml_handler.py +27 -21
  62. signalwire_agents/core/swml_renderer.py +123 -312
  63. signalwire_agents/core/swml_service.py +171 -203
  64. signalwire_agents/mcp_gateway/__init__.py +29 -0
  65. signalwire_agents/mcp_gateway/gateway_service.py +564 -0
  66. signalwire_agents/mcp_gateway/mcp_manager.py +513 -0
  67. signalwire_agents/mcp_gateway/session_manager.py +218 -0
  68. signalwire_agents/prefabs/concierge.py +0 -3
  69. signalwire_agents/prefabs/faq_bot.py +0 -3
  70. signalwire_agents/prefabs/info_gatherer.py +0 -3
  71. signalwire_agents/prefabs/receptionist.py +0 -3
  72. signalwire_agents/prefabs/survey.py +0 -3
  73. signalwire_agents/schema.json +9218 -5489
  74. signalwire_agents/search/__init__.py +7 -1
  75. signalwire_agents/search/document_processor.py +490 -31
  76. signalwire_agents/search/index_builder.py +307 -37
  77. signalwire_agents/search/migration.py +418 -0
  78. signalwire_agents/search/models.py +30 -0
  79. signalwire_agents/search/pgvector_backend.py +748 -0
  80. signalwire_agents/search/query_processor.py +162 -31
  81. signalwire_agents/search/search_engine.py +916 -35
  82. signalwire_agents/search/search_service.py +376 -53
  83. signalwire_agents/skills/README.md +452 -0
  84. signalwire_agents/skills/__init__.py +14 -2
  85. signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
  86. signalwire_agents/skills/api_ninjas_trivia/__init__.py +12 -0
  87. signalwire_agents/skills/api_ninjas_trivia/skill.py +237 -0
  88. signalwire_agents/skills/datasphere/README.md +210 -0
  89. signalwire_agents/skills/datasphere/skill.py +84 -3
  90. signalwire_agents/skills/datasphere_serverless/README.md +258 -0
  91. signalwire_agents/skills/datasphere_serverless/__init__.py +9 -0
  92. signalwire_agents/skills/datasphere_serverless/skill.py +82 -1
  93. signalwire_agents/skills/datetime/README.md +132 -0
  94. signalwire_agents/skills/datetime/__init__.py +9 -0
  95. signalwire_agents/skills/datetime/skill.py +20 -7
  96. signalwire_agents/skills/joke/README.md +149 -0
  97. signalwire_agents/skills/joke/__init__.py +9 -0
  98. signalwire_agents/skills/joke/skill.py +21 -0
  99. signalwire_agents/skills/math/README.md +161 -0
  100. signalwire_agents/skills/math/__init__.py +9 -0
  101. signalwire_agents/skills/math/skill.py +18 -4
  102. signalwire_agents/skills/mcp_gateway/README.md +230 -0
  103. signalwire_agents/skills/mcp_gateway/__init__.py +10 -0
  104. signalwire_agents/skills/mcp_gateway/skill.py +421 -0
  105. signalwire_agents/skills/native_vector_search/README.md +210 -0
  106. signalwire_agents/skills/native_vector_search/__init__.py +9 -0
  107. signalwire_agents/skills/native_vector_search/skill.py +569 -101
  108. signalwire_agents/skills/play_background_file/README.md +218 -0
  109. signalwire_agents/skills/play_background_file/__init__.py +12 -0
  110. signalwire_agents/skills/play_background_file/skill.py +242 -0
  111. signalwire_agents/skills/registry.py +395 -40
  112. signalwire_agents/skills/spider/README.md +236 -0
  113. signalwire_agents/skills/spider/__init__.py +13 -0
  114. signalwire_agents/skills/spider/skill.py +598 -0
  115. signalwire_agents/skills/swml_transfer/README.md +395 -0
  116. signalwire_agents/skills/swml_transfer/__init__.py +10 -0
  117. signalwire_agents/skills/swml_transfer/skill.py +359 -0
  118. signalwire_agents/skills/weather_api/README.md +178 -0
  119. signalwire_agents/skills/weather_api/__init__.py +12 -0
  120. signalwire_agents/skills/weather_api/skill.py +191 -0
  121. signalwire_agents/skills/web_search/README.md +163 -0
  122. signalwire_agents/skills/web_search/__init__.py +9 -0
  123. signalwire_agents/skills/web_search/skill.py +586 -112
  124. signalwire_agents/skills/wikipedia_search/README.md +228 -0
  125. signalwire_agents/{core/state → skills/wikipedia_search}/__init__.py +5 -4
  126. signalwire_agents/skills/{wikipedia → wikipedia_search}/skill.py +33 -3
  127. signalwire_agents/web/__init__.py +17 -0
  128. signalwire_agents/web/web_service.py +559 -0
  129. signalwire_agents-1.0.17.dev4.data/data/share/man/man1/sw-agent-init.1 +400 -0
  130. signalwire_agents-1.0.17.dev4.data/data/share/man/man1/sw-search.1 +483 -0
  131. signalwire_agents-1.0.17.dev4.data/data/share/man/man1/swaig-test.1 +308 -0
  132. {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/METADATA +347 -215
  133. signalwire_agents-1.0.17.dev4.dist-info/RECORD +147 -0
  134. signalwire_agents-1.0.17.dev4.dist-info/entry_points.txt +6 -0
  135. signalwire_agents/core/state/file_state_manager.py +0 -219
  136. signalwire_agents/core/state/state_manager.py +0 -101
  137. signalwire_agents/skills/wikipedia/__init__.py +0 -9
  138. signalwire_agents-0.1.13.data/data/schema.json +0 -5611
  139. signalwire_agents-0.1.13.dist-info/RECORD +0 -67
  140. signalwire_agents-0.1.13.dist-info/entry_points.txt +0 -3
  141. {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/WHEEL +0 -0
  142. {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/licenses/LICENSE +0 -0
  143. {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,421 @@
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 json
11
+ import requests
12
+ import logging
13
+ from typing import List, Dict, Any, Optional
14
+ from requests.auth import HTTPBasicAuth
15
+
16
+ from signalwire_agents.core.skill_base import SkillBase
17
+ from signalwire_agents.core.function_result import SwaigFunctionResult
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class MCPGatewaySkill(SkillBase):
23
+ """
24
+ MCP Gateway Skill - Bridge MCP servers with SWAIG functions
25
+
26
+ This skill connects SignalWire agents to MCP (Model Context Protocol) servers
27
+ through a gateway service, dynamically creating SWAIG functions for MCP tools.
28
+ """
29
+
30
+ SKILL_NAME = "mcp_gateway"
31
+ SKILL_DESCRIPTION = "Bridge MCP servers with SWAIG functions"
32
+ SKILL_VERSION = "1.0.0"
33
+ REQUIRED_PACKAGES = ["requests"]
34
+ REQUIRED_ENV_VARS = []
35
+
36
+ @classmethod
37
+ def get_parameter_schema(cls) -> Dict[str, Dict[str, Any]]:
38
+ """Get parameter schema for MCP Gateway skill"""
39
+ schema = super().get_parameter_schema()
40
+ schema.update({
41
+ "gateway_url": {
42
+ "type": "string",
43
+ "description": "URL of the MCP Gateway service",
44
+ "required": True
45
+ },
46
+ "auth_token": {
47
+ "type": "string",
48
+ "description": "Bearer token for authentication (alternative to basic auth)",
49
+ "required": False,
50
+ "hidden": True,
51
+ "env_var": "MCP_GATEWAY_AUTH_TOKEN"
52
+ },
53
+ "auth_user": {
54
+ "type": "string",
55
+ "description": "Username for basic authentication (required if auth_token not provided)",
56
+ "required": False,
57
+ "env_var": "MCP_GATEWAY_AUTH_USER"
58
+ },
59
+ "auth_password": {
60
+ "type": "string",
61
+ "description": "Password for basic authentication (required if auth_token not provided)",
62
+ "required": False,
63
+ "hidden": True,
64
+ "env_var": "MCP_GATEWAY_AUTH_PASSWORD"
65
+ },
66
+ "services": {
67
+ "type": "array",
68
+ "description": "List of MCP services to connect to (empty for all available)",
69
+ "default": [],
70
+ "required": False,
71
+ "items": {
72
+ "type": "object",
73
+ "properties": {
74
+ "name": {
75
+ "type": "string",
76
+ "description": "Service name"
77
+ },
78
+ "tools": {
79
+ "type": ["string", "array"],
80
+ "description": "Tools to expose ('*' for all, or list of tool names)"
81
+ }
82
+ }
83
+ }
84
+ },
85
+ "session_timeout": {
86
+ "type": "integer",
87
+ "description": "Session timeout in seconds",
88
+ "default": 300,
89
+ "required": False
90
+ },
91
+ "tool_prefix": {
92
+ "type": "string",
93
+ "description": "Prefix for registered SWAIG function names",
94
+ "default": "mcp_",
95
+ "required": False
96
+ },
97
+ "retry_attempts": {
98
+ "type": "integer",
99
+ "description": "Number of retry attempts for failed requests",
100
+ "default": 3,
101
+ "required": False
102
+ },
103
+ "request_timeout": {
104
+ "type": "integer",
105
+ "description": "Request timeout in seconds",
106
+ "default": 30,
107
+ "required": False
108
+ },
109
+ "verify_ssl": {
110
+ "type": "boolean",
111
+ "description": "Verify SSL certificates",
112
+ "default": True,
113
+ "required": False
114
+ }
115
+ })
116
+ return schema
117
+
118
+ def setup(self) -> bool:
119
+ """Setup and validate skill configuration"""
120
+ # Check for auth method - either token or basic auth
121
+ self.auth_token = self.params.get('auth_token')
122
+ if not self.auth_token:
123
+ # Require basic auth if no token
124
+ required_params = ['gateway_url', 'auth_user', 'auth_password']
125
+ missing_params = [param for param in required_params if not self.params.get(param)]
126
+ if missing_params:
127
+ self.logger.error(f"Missing required parameters: {missing_params}")
128
+ return False
129
+ self.auth = HTTPBasicAuth(self.params['auth_user'], self.params['auth_password'])
130
+ else:
131
+ # Just need gateway URL with token auth
132
+ if not self.params.get('gateway_url'):
133
+ self.logger.error("Missing required parameter: gateway_url")
134
+ return False
135
+ self.auth = None
136
+
137
+ # Store configuration
138
+ self.gateway_url = self.params['gateway_url'].rstrip('/')
139
+ self.services = self.params.get('services', [])
140
+ self.session_timeout = self.params.get('session_timeout', 300)
141
+ self.tool_prefix = self.params.get('tool_prefix', 'mcp_')
142
+ self.retry_attempts = self.params.get('retry_attempts', 3)
143
+ self.request_timeout = self.params.get('request_timeout', 30)
144
+ self.verify_ssl = self.params.get('verify_ssl', True)
145
+
146
+ # Session ID will be set from call_id when first tool is used
147
+ self.session_id = None
148
+
149
+ # Validate gateway connection
150
+ try:
151
+ response = requests.get(
152
+ f"{self.gateway_url}/health",
153
+ timeout=self.request_timeout,
154
+ verify=self.verify_ssl
155
+ )
156
+ response.raise_for_status()
157
+ self.logger.info(f"Connected to MCP Gateway at {self.gateway_url}")
158
+ except Exception as e:
159
+ self.logger.error(f"Failed to connect to gateway: {e}")
160
+ return False
161
+
162
+ return True
163
+
164
+ def _make_request(self, method: str, url: str, **kwargs) -> requests.Response:
165
+ """Make HTTP request with appropriate authentication"""
166
+ headers = kwargs.get('headers', {})
167
+ if self.auth_token:
168
+ headers['Authorization'] = f'Bearer {self.auth_token}'
169
+ kwargs['headers'] = headers
170
+
171
+ if not self.auth_token:
172
+ kwargs['auth'] = self.auth
173
+
174
+ kwargs['timeout'] = kwargs.get('timeout', self.request_timeout)
175
+ kwargs['verify'] = kwargs.get('verify', self.verify_ssl)
176
+
177
+ return requests.request(method, url, **kwargs)
178
+
179
+ def register_tools(self) -> None:
180
+ """Register SWAIG tools from MCP services"""
181
+ # If no services specified, get all available
182
+ if not self.services:
183
+ try:
184
+ response = self._make_request('GET', f"{self.gateway_url}/services")
185
+ response.raise_for_status()
186
+ all_services = response.json()
187
+ self.services = [{"name": name} for name in all_services.keys()]
188
+ except Exception as e:
189
+ self.logger.error(f"Failed to list services: {e}")
190
+ return
191
+
192
+ # Process each service
193
+ for service_config in self.services:
194
+ service_name = service_config.get('name')
195
+ if not service_name:
196
+ continue
197
+
198
+ # Get tools for this service
199
+ try:
200
+ response = self._make_request('GET', f"{self.gateway_url}/services/{service_name}/tools")
201
+ response.raise_for_status()
202
+ tools_data = response.json()
203
+ tools = tools_data.get('tools', [])
204
+
205
+ # Filter tools if specified
206
+ tool_filter = service_config.get('tools', '*')
207
+ if tool_filter != '*' and isinstance(tool_filter, list):
208
+ tools = [t for t in tools if t['name'] in tool_filter]
209
+
210
+ # Register each tool as a SWAIG function
211
+ for tool in tools:
212
+ self._register_mcp_tool(service_name, tool)
213
+
214
+ except Exception as e:
215
+ self.logger.error(f"Failed to get tools for service '{service_name}': {e}")
216
+
217
+ # Register the hangup hook for session cleanup
218
+ self.define_tool(
219
+ name="_mcp_gateway_hangup",
220
+ description="Internal cleanup function for MCP sessions",
221
+ parameters={},
222
+ handler=self._hangup_handler,
223
+ is_hangup_hook=True
224
+ )
225
+
226
+ def _register_mcp_tool(self, service_name: str, tool_def: Dict[str, Any]):
227
+ """Register a single MCP tool as a SWAIG function"""
228
+ tool_name = tool_def.get('name')
229
+ if not tool_name:
230
+ return
231
+
232
+ # Create SWAIG function name
233
+ swaig_name = f"{self.tool_prefix}{service_name}_{tool_name}"
234
+
235
+ # Build SWAIG parameters from MCP input schema
236
+ input_schema = tool_def.get('inputSchema', {})
237
+ properties = input_schema.get('properties', {})
238
+ required = input_schema.get('required', [])
239
+
240
+ # Convert MCP schema to SWAIG parameters
241
+ swaig_params = {}
242
+ for prop_name, prop_def in properties.items():
243
+ param_def = {
244
+ "type": prop_def.get('type', 'string'),
245
+ "description": prop_def.get('description', '')
246
+ }
247
+
248
+ # Add enum if present
249
+ if 'enum' in prop_def:
250
+ param_def['enum'] = prop_def['enum']
251
+
252
+ # Add default if present and not required
253
+ if 'default' in prop_def and prop_name not in required:
254
+ param_def['default'] = prop_def['default']
255
+
256
+ swaig_params[prop_name] = param_def
257
+
258
+ # Create handler function
259
+ def handler(args, raw_data):
260
+ return self._call_mcp_tool(service_name, tool_name, args, raw_data)
261
+
262
+ # Register the SWAIG function
263
+ self.define_tool(
264
+ name=swaig_name,
265
+ description=f"[{service_name}] {tool_def.get('description', tool_name)}",
266
+ parameters=swaig_params,
267
+ handler=handler
268
+ )
269
+
270
+ self.logger.info(f"Registered SWAIG function: {swaig_name}")
271
+
272
+ def _call_mcp_tool(self, service_name: str, tool_name: str, args: Dict[str, Any],
273
+ raw_data: Dict[str, Any]) -> SwaigFunctionResult:
274
+ """Call an MCP tool through the gateway"""
275
+ # Check for mcp_call_id in global_data first, then fall back to top-level call_id
276
+ global_data = raw_data.get('global_data', {})
277
+ if 'mcp_call_id' in global_data:
278
+ self.session_id = global_data['mcp_call_id']
279
+ self.logger.info(f"Using session ID from global_data.mcp_call_id: {self.session_id}")
280
+ else:
281
+ self.session_id = raw_data.get('call_id', 'unknown')
282
+ self.logger.info(f"Using session ID from call_id: {self.session_id}")
283
+ self.logger.debug(f"Raw data keys: {list(raw_data.keys())}")
284
+ if 'global_data' in raw_data:
285
+ self.logger.debug(f"global_data keys: {list(global_data.keys())}")
286
+
287
+ # Prepare request
288
+ request_data = {
289
+ "tool": tool_name,
290
+ "arguments": args,
291
+ "session_id": self.session_id,
292
+ "timeout": self.session_timeout,
293
+ "metadata": {
294
+ "agent_id": self.agent.name,
295
+ "timestamp": raw_data.get('timestamp'),
296
+ "call_id": raw_data.get('call_id')
297
+ }
298
+ }
299
+
300
+ # Call the gateway with retries
301
+ last_error = None
302
+ for attempt in range(self.retry_attempts):
303
+ try:
304
+ response = self._make_request(
305
+ 'POST',
306
+ f"{self.gateway_url}/services/{service_name}/call",
307
+ json=request_data
308
+ )
309
+
310
+ if response.status_code == 200:
311
+ result_data = response.json()
312
+ result_text = result_data.get('result', 'No response')
313
+
314
+ # Create SWAIG result
315
+ return SwaigFunctionResult(result_text)
316
+
317
+ else:
318
+ error_data = response.json()
319
+ error_msg = error_data.get('error', f'HTTP {response.status_code}')
320
+ last_error = error_msg
321
+
322
+ if response.status_code >= 500:
323
+ # Server error, retry
324
+ self.logger.warning(f"Gateway error (attempt {attempt + 1}): {error_msg}")
325
+ continue
326
+ else:
327
+ # Client error, don't retry
328
+ break
329
+
330
+ except requests.exceptions.Timeout:
331
+ last_error = "Request timeout"
332
+ self.logger.warning(f"Timeout calling MCP tool (attempt {attempt + 1})")
333
+
334
+ except requests.exceptions.ConnectionError:
335
+ last_error = "Connection error"
336
+ self.logger.warning(f"Connection error (attempt {attempt + 1})")
337
+
338
+ except Exception as e:
339
+ last_error = str(e)
340
+ self.logger.error(f"Unexpected error: {e}")
341
+ break
342
+
343
+ # All attempts failed
344
+ error_msg = f"Failed to call {service_name}.{tool_name}: {last_error}"
345
+ self.logger.error(error_msg)
346
+ return SwaigFunctionResult(error_msg)
347
+
348
+ def _hangup_handler(self, args: Dict[str, Any], raw_data: Dict[str, Any]) -> SwaigFunctionResult:
349
+ """Handle call hangup - cleanup MCP session"""
350
+ # Check for mcp_call_id in global_data first, then fall back to top-level call_id
351
+ global_data = raw_data.get('global_data', {})
352
+ if 'mcp_call_id' in global_data:
353
+ session_id = global_data['mcp_call_id']
354
+ self.logger.info(f"Cleanup using session ID from global_data.mcp_call_id: {session_id}")
355
+ else:
356
+ session_id = raw_data.get('call_id', 'unknown')
357
+ self.logger.info(f"Cleanup using session ID from call_id: {session_id}")
358
+
359
+ try:
360
+ response = self._make_request('DELETE', f"{self.gateway_url}/sessions/{session_id}")
361
+
362
+ if response.status_code in [200, 404]:
363
+ self.logger.info(f"Cleaned up MCP session: {session_id}")
364
+ else:
365
+ self.logger.warning(f"Failed to cleanup session: HTTP {response.status_code}")
366
+
367
+ except Exception as e:
368
+ self.logger.error(f"Error cleaning up session: {e}")
369
+
370
+ return SwaigFunctionResult("Session cleanup complete")
371
+
372
+ def get_hints(self) -> List[str]:
373
+ """Return speech recognition hints"""
374
+ hints = ["MCP", "gateway"]
375
+
376
+ # Add service names as hints
377
+ for service in self.services:
378
+ if isinstance(service, dict) and 'name' in service:
379
+ hints.append(service['name'])
380
+
381
+ return hints
382
+
383
+ def get_global_data(self) -> Dict[str, Any]:
384
+ """Return global data for DataMap variables"""
385
+ return {
386
+ "mcp_gateway_url": self.gateway_url,
387
+ "mcp_session_id": self.session_id,
388
+ "mcp_services": [s.get('name') if isinstance(s, dict) else str(s)
389
+ for s in self.services]
390
+ }
391
+
392
+ def get_prompt_sections(self) -> List[Dict[str, Any]]:
393
+ """Return prompt sections to add to agent"""
394
+ sections = []
395
+
396
+ # Build service list for prompt
397
+ service_descriptions = []
398
+ for service in self.services:
399
+ if isinstance(service, dict):
400
+ name = service.get('name', 'Unknown')
401
+ tools = service.get('tools', '*')
402
+ if tools == '*':
403
+ service_descriptions.append(f"{name} (all tools)")
404
+ elif isinstance(tools, list):
405
+ service_descriptions.append(f"{name} ({len(tools)} tools)")
406
+ else:
407
+ service_descriptions.append(str(service))
408
+
409
+ if service_descriptions:
410
+ sections.append({
411
+ "title": "MCP Gateway Integration",
412
+ "body": "You have access to external MCP (Model Context Protocol) services through a gateway.",
413
+ "bullets": [
414
+ f"Connected to gateway at {self.gateway_url}",
415
+ f"Available services: {', '.join(service_descriptions)}",
416
+ f"Functions are prefixed with '{self.tool_prefix}' followed by service name",
417
+ "Each service maintains its own session state throughout the call"
418
+ ]
419
+ })
420
+
421
+ return sections
@@ -0,0 +1,210 @@
1
+ # Native Vector Search Skill
2
+
3
+ The Native Vector Search skill provides document search capabilities using vector similarity and keyword search. It supports multiple storage backends including SQLite (local files) and PostgreSQL with pgvector extension.
4
+
5
+ ## Features
6
+
7
+ - **Hybrid Search**: Combines vector similarity and keyword search for better results
8
+ - **Multiple Backends**: SQLite for local deployment, pgvector for scalable production use
9
+ - **Remote Search**: Connect to remote search servers
10
+ - **Auto-indexing**: Automatically build indexes from source directories
11
+ - **NLP Enhancement**: Query expansion and synonym matching
12
+ - **Tag Filtering**: Filter results by document tags
13
+
14
+ ## Backends
15
+
16
+ ### SQLite Backend (Default)
17
+ - Stores indexes in `.swsearch` files
18
+ - Good for single-agent deployments
19
+ - Portable and self-contained
20
+ - No external dependencies
21
+
22
+ ### pgvector Backend
23
+ - Uses PostgreSQL with pgvector extension
24
+ - Scalable for multi-agent deployments
25
+ - Real-time updates capability
26
+ - Efficient similarity search with specialized indexes
27
+
28
+ ### Remote Search Server
29
+ - Connect to centralized search API
30
+ - Lower memory usage per agent
31
+ - Shared knowledge base
32
+
33
+ ## Configuration Parameters
34
+
35
+ ### Basic Parameters
36
+ - `tool_name`: Name of the search tool (default: "search_knowledge")
37
+ - `description`: Tool description for the AI
38
+ - `count`: Number of results to return (default: 5)
39
+ - `distance_threshold`: Minimum similarity score (default: 0.0)
40
+ - `tags`: Filter results by these tags
41
+
42
+ ### Backend Selection
43
+ - `backend`: Storage backend - "sqlite" or "pgvector" (default: "sqlite")
44
+
45
+ ### SQLite Backend
46
+ - `index_file`: Path to .swsearch index file
47
+ - `build_index`: Auto-build index from source (default: false)
48
+ - `source_dir`: Directory to index if build_index=true
49
+
50
+ ### pgvector Backend
51
+ - `connection_string`: PostgreSQL connection string (required)
52
+ - `collection_name`: Name of the collection to search (required)
53
+
54
+ ### Remote Backend
55
+ - `remote_url`: URL of remote search server
56
+ - `index_name`: Name of index on remote server
57
+
58
+ ### Response Formatting
59
+ - `response_prefix`: Text to prepend to results
60
+ - `response_postfix`: Text to append to results
61
+ - `no_results_message`: Message when no results found
62
+
63
+ ### NLP Configuration
64
+ - `query_nlp_backend`: NLP backend for queries ("nltk" or "spacy")
65
+ - `index_nlp_backend`: NLP backend for indexing ("nltk" or "spacy")
66
+
67
+ ## Usage Examples
68
+
69
+ ### SQLite Backend (Local File)
70
+ ```python
71
+ agent.add_skill("native_vector_search", {
72
+ "tool_name": "search_docs",
73
+ "description": "Search technical documentation",
74
+ "index_file": "docs.swsearch",
75
+ "count": 5
76
+ })
77
+ ```
78
+
79
+ ### pgvector Backend (PostgreSQL)
80
+ ```python
81
+ agent.add_skill("native_vector_search", {
82
+ "tool_name": "search_knowledge",
83
+ "description": "Search the knowledge base",
84
+ "backend": "pgvector",
85
+ "connection_string": "postgresql://user:pass@localhost:5432/knowledge",
86
+ "collection_name": "docs_collection",
87
+ "count": 5
88
+ })
89
+ ```
90
+
91
+ ### Remote Search Server
92
+ ```python
93
+ agent.add_skill("native_vector_search", {
94
+ "tool_name": "search_api",
95
+ "description": "Search API documentation",
96
+ "remote_url": "http://search-server:8001",
97
+ "index_name": "api_docs"
98
+ })
99
+ ```
100
+
101
+ ### Auto-build Index
102
+ ```python
103
+ agent.add_skill("native_vector_search", {
104
+ "tool_name": "search_local",
105
+ "build_index": True,
106
+ "source_dir": "./documentation",
107
+ "file_types": ["md", "txt"],
108
+ "index_file": "auto_docs.swsearch"
109
+ })
110
+ ```
111
+
112
+ ### Multiple Search Instances
113
+ ```python
114
+ # Documentation search
115
+ agent.add_skill("native_vector_search", {
116
+ "tool_name": "search_docs",
117
+ "index_file": "docs.swsearch",
118
+ "description": "Search documentation"
119
+ })
120
+
121
+ # Code examples search
122
+ agent.add_skill("native_vector_search", {
123
+ "tool_name": "search_examples",
124
+ "backend": "pgvector",
125
+ "connection_string": "postgresql://localhost/knowledge",
126
+ "collection_name": "examples",
127
+ "description": "Search code examples"
128
+ })
129
+ ```
130
+
131
+ ## Installation
132
+
133
+ ### For SQLite Backend
134
+ ```bash
135
+ pip install signalwire-agents[search]
136
+ ```
137
+
138
+ ### For pgvector Backend
139
+ ```bash
140
+ pip install signalwire-agents[search,pgvector]
141
+ ```
142
+
143
+ ### For All Features
144
+ ```bash
145
+ pip install signalwire-agents[search-all]
146
+ ```
147
+
148
+ ## Building Indexes
149
+
150
+ ### Using sw-search CLI
151
+
152
+ #### SQLite Backend
153
+ ```bash
154
+ sw-search ./docs --output docs.swsearch
155
+ ```
156
+
157
+ #### pgvector Backend
158
+ ```bash
159
+ sw-search ./docs \
160
+ --backend pgvector \
161
+ --connection-string "postgresql://localhost/knowledge" \
162
+ --output docs_collection
163
+ ```
164
+
165
+ ## Performance Considerations
166
+
167
+ ### SQLite
168
+ - Fast for small to medium datasets (<100k documents)
169
+ - Linear search for vector similarity
170
+ - Single-file deployment
171
+
172
+ ### pgvector
173
+ - Efficient for large datasets
174
+ - Uses IVFFlat or HNSW indexes
175
+ - Handles concurrent access well
176
+ - Requires PostgreSQL server
177
+
178
+ ### NLP Backends
179
+ - `nltk`: Fast, good for most use cases (~50-100ms)
180
+ - `spacy`: Better quality, slower (~150-300ms)
181
+
182
+ ## Environment Variables
183
+
184
+ None required - all configuration comes through skill parameters.
185
+
186
+ ## Troubleshooting
187
+
188
+ ### "Search dependencies not available"
189
+ Install the search extras:
190
+ ```bash
191
+ pip install signalwire-agents[search]
192
+ ```
193
+
194
+ ### "pgvector dependencies not available"
195
+ Install pgvector support:
196
+ ```bash
197
+ pip install signalwire-agents[pgvector]
198
+ ```
199
+
200
+ ### "Failed to connect to pgvector"
201
+ 1. Ensure PostgreSQL is running
202
+ 2. Check connection string
203
+ 3. Verify pgvector extension is installed
204
+ 4. Check collection exists
205
+
206
+ ### Poor Search Results
207
+ 1. Try different NLP backends
208
+ 2. Adjust distance_threshold
209
+ 3. Check document preprocessing
210
+ 4. Verify index quality
@@ -1 +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
+
1
10
  # Native Vector Search Skill