signalwire-agents 0.1.11__py3-none-any.whl → 0.1.12__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 +5 -1
- signalwire_agents/agent_server.py +222 -13
- signalwire_agents/cli/build_search.py +457 -0
- signalwire_agents/cli/test_swaig.py +177 -113
- signalwire_agents/core/agent_base.py +1 -1
- signalwire_agents/core/logging_config.py +232 -0
- signalwire_agents/search/__init__.py +131 -0
- signalwire_agents/search/document_processor.py +764 -0
- signalwire_agents/search/index_builder.py +534 -0
- signalwire_agents/search/query_processor.py +371 -0
- signalwire_agents/search/search_engine.py +383 -0
- signalwire_agents/search/search_service.py +251 -0
- signalwire_agents/skills/native_vector_search/__init__.py +1 -0
- signalwire_agents/skills/native_vector_search/skill.py +352 -0
- signalwire_agents/skills/registry.py +2 -15
- signalwire_agents/utils/__init__.py +13 -1
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.12.dist-info}/METADATA +110 -3
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.12.dist-info}/RECORD +23 -14
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.12.dist-info}/entry_points.txt +1 -0
- signalwire_agents/utils/serverless.py +0 -38
- {signalwire_agents-0.1.11.data → signalwire_agents-0.1.12.data}/data/schema.json +0 -0
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.12.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.12.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.11.dist-info → signalwire_agents-0.1.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,352 @@
|
|
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 os
|
11
|
+
import tempfile
|
12
|
+
import shutil
|
13
|
+
from typing import Dict, Any, Optional, List
|
14
|
+
from pathlib import Path
|
15
|
+
|
16
|
+
from signalwire_agents.core.skill_base import SkillBase
|
17
|
+
from signalwire_agents.core.function_result import SwaigFunctionResult
|
18
|
+
|
19
|
+
class NativeVectorSearchSkill(SkillBase):
|
20
|
+
"""Native vector search capability using local document indexes or remote search servers"""
|
21
|
+
|
22
|
+
SKILL_NAME = "native_vector_search"
|
23
|
+
SKILL_DESCRIPTION = "Search document indexes using vector similarity and keyword search (local or remote)"
|
24
|
+
SKILL_VERSION = "1.0.0"
|
25
|
+
REQUIRED_PACKAGES = [] # Optional packages checked at runtime
|
26
|
+
REQUIRED_ENV_VARS = [] # No required env vars since all config comes from params
|
27
|
+
|
28
|
+
# Enable multiple instances support
|
29
|
+
SUPPORTS_MULTIPLE_INSTANCES = True
|
30
|
+
|
31
|
+
def get_instance_key(self) -> str:
|
32
|
+
"""
|
33
|
+
Get the key used to track this skill instance
|
34
|
+
|
35
|
+
For native vector search, we use the tool name to differentiate instances
|
36
|
+
"""
|
37
|
+
tool_name = self.params.get('tool_name', 'search_knowledge')
|
38
|
+
index_file = self.params.get('index_file', 'default')
|
39
|
+
return f"{self.SKILL_NAME}_{tool_name}_{index_file}"
|
40
|
+
|
41
|
+
def setup(self) -> bool:
|
42
|
+
"""Setup the native vector search skill"""
|
43
|
+
|
44
|
+
# Check if search functionality is available
|
45
|
+
try:
|
46
|
+
from signalwire_agents.search import IndexBuilder, SearchEngine
|
47
|
+
from signalwire_agents.search.query_processor import preprocess_query
|
48
|
+
self.search_available = True
|
49
|
+
except ImportError as e:
|
50
|
+
self.search_available = False
|
51
|
+
self.import_error = str(e)
|
52
|
+
self.logger.warning(f"Search dependencies not available: {e}")
|
53
|
+
# Don't fail setup - we'll provide helpful error messages at runtime
|
54
|
+
|
55
|
+
# Get configuration
|
56
|
+
self.tool_name = self.params.get('tool_name', 'search_knowledge')
|
57
|
+
self.index_file = self.params.get('index_file')
|
58
|
+
self.build_index = self.params.get('build_index', False)
|
59
|
+
self.source_dir = self.params.get('source_dir')
|
60
|
+
self.count = self.params.get('count', 5)
|
61
|
+
self.distance_threshold = self.params.get('distance_threshold', 0.0)
|
62
|
+
self.tags = self.params.get('tags', [])
|
63
|
+
self.no_results_message = self.params.get(
|
64
|
+
'no_results_message',
|
65
|
+
"No information found for '{query}'"
|
66
|
+
)
|
67
|
+
self.response_prefix = self.params.get('response_prefix', '')
|
68
|
+
self.response_postfix = self.params.get('response_postfix', '')
|
69
|
+
|
70
|
+
# Remote search server configuration
|
71
|
+
self.remote_url = self.params.get('remote_url') # e.g., "http://localhost:8001"
|
72
|
+
self.index_name = self.params.get('index_name', 'default') # For remote searches
|
73
|
+
|
74
|
+
# SWAIG fields for function fillers
|
75
|
+
self.swaig_fields = self.params.get('swaig_fields', {})
|
76
|
+
|
77
|
+
# NLP backend configuration
|
78
|
+
self.nlp_backend = self.params.get('nlp_backend', 'nltk') # Default to faster NLTK
|
79
|
+
if self.nlp_backend not in ['nltk', 'spacy']:
|
80
|
+
self.logger.warning(f"Invalid nlp_backend '{self.nlp_backend}', using 'nltk'")
|
81
|
+
self.nlp_backend = 'nltk'
|
82
|
+
|
83
|
+
# Auto-build index if requested and search is available
|
84
|
+
if self.build_index and self.source_dir and self.search_available:
|
85
|
+
if not self.index_file:
|
86
|
+
# Generate index filename from source directory
|
87
|
+
source_name = Path(self.source_dir).name
|
88
|
+
self.index_file = f"{source_name}.swsearch"
|
89
|
+
|
90
|
+
# Build index if it doesn't exist
|
91
|
+
if not os.path.exists(self.index_file):
|
92
|
+
try:
|
93
|
+
self.logger.info(f"Building search index from {self.source_dir}...")
|
94
|
+
from signalwire_agents.search import IndexBuilder
|
95
|
+
|
96
|
+
builder = IndexBuilder(verbose=self.params.get('verbose', False))
|
97
|
+
builder.build_index(
|
98
|
+
source_dir=self.source_dir,
|
99
|
+
output_file=self.index_file,
|
100
|
+
file_types=self.params.get('file_types', ['md', 'txt']),
|
101
|
+
exclude_patterns=self.params.get('exclude_patterns'),
|
102
|
+
tags=self.params.get('global_tags')
|
103
|
+
)
|
104
|
+
self.logger.info(f"Search index created: {self.index_file}")
|
105
|
+
except Exception as e:
|
106
|
+
self.logger.error(f"Failed to build search index: {e}")
|
107
|
+
self.search_available = False
|
108
|
+
|
109
|
+
# Initialize search engine
|
110
|
+
self.search_engine = None
|
111
|
+
if self.search_available and self.index_file and os.path.exists(self.index_file):
|
112
|
+
try:
|
113
|
+
from signalwire_agents.search import SearchEngine
|
114
|
+
self.search_engine = SearchEngine(self.index_file)
|
115
|
+
except Exception as e:
|
116
|
+
self.logger.error(f"Failed to load search index {self.index_file}: {e}")
|
117
|
+
self.search_available = False
|
118
|
+
|
119
|
+
# Check if we should use remote search mode
|
120
|
+
self.use_remote = bool(self.remote_url)
|
121
|
+
if self.use_remote:
|
122
|
+
self.logger.info(f"Using remote search server: {self.remote_url}")
|
123
|
+
# Test remote connection
|
124
|
+
try:
|
125
|
+
import requests
|
126
|
+
response = requests.get(f"{self.remote_url}/health", timeout=5)
|
127
|
+
if response.status_code == 200:
|
128
|
+
self.logger.info("Remote search server is available")
|
129
|
+
self.search_available = True
|
130
|
+
else:
|
131
|
+
self.logger.error(f"Remote search server returned status {response.status_code}")
|
132
|
+
self.search_available = False
|
133
|
+
except Exception as e:
|
134
|
+
self.logger.error(f"Failed to connect to remote search server: {e}")
|
135
|
+
self.search_available = False
|
136
|
+
|
137
|
+
return True
|
138
|
+
|
139
|
+
def register_tools(self) -> None:
|
140
|
+
"""Register native vector search tool with the agent"""
|
141
|
+
|
142
|
+
# Get description from params or use default
|
143
|
+
description = self.params.get(
|
144
|
+
'description',
|
145
|
+
'Search the local knowledge base for information'
|
146
|
+
)
|
147
|
+
|
148
|
+
self.agent.define_tool(
|
149
|
+
name=self.tool_name,
|
150
|
+
description=description,
|
151
|
+
parameters={
|
152
|
+
"query": {
|
153
|
+
"type": "string",
|
154
|
+
"description": "Search query or question"
|
155
|
+
},
|
156
|
+
"count": {
|
157
|
+
"type": "integer",
|
158
|
+
"description": f"Number of results to return (default: {self.count})",
|
159
|
+
"default": self.count
|
160
|
+
}
|
161
|
+
},
|
162
|
+
handler=self._search_handler,
|
163
|
+
**self.swaig_fields
|
164
|
+
)
|
165
|
+
|
166
|
+
def _search_handler(self, args, raw_data):
|
167
|
+
"""Handle search requests"""
|
168
|
+
|
169
|
+
if not self.search_available:
|
170
|
+
return SwaigFunctionResult(
|
171
|
+
f"Search functionality is not available. {getattr(self, 'import_error', '')}\n"
|
172
|
+
f"Install with: pip install signalwire-agents[search]"
|
173
|
+
)
|
174
|
+
|
175
|
+
if not self.use_remote and not self.search_engine:
|
176
|
+
return SwaigFunctionResult(
|
177
|
+
f"Search index not available. "
|
178
|
+
f"{'Index file not found: ' + (self.index_file or 'not specified') if self.index_file else 'No index file configured'}"
|
179
|
+
)
|
180
|
+
|
181
|
+
query = args.get('query', '').strip()
|
182
|
+
if not query:
|
183
|
+
return SwaigFunctionResult("Please provide a search query.")
|
184
|
+
|
185
|
+
count = args.get('count', self.count)
|
186
|
+
|
187
|
+
try:
|
188
|
+
# Preprocess the query
|
189
|
+
from signalwire_agents.search.query_processor import preprocess_query
|
190
|
+
enhanced = preprocess_query(query, language='en', vector=True, nlp_backend=self.nlp_backend)
|
191
|
+
|
192
|
+
# Perform search (local or remote)
|
193
|
+
if self.use_remote:
|
194
|
+
results = self._search_remote(query, enhanced, count)
|
195
|
+
else:
|
196
|
+
results = self.search_engine.search(
|
197
|
+
query_vector=enhanced.get('vector', []),
|
198
|
+
enhanced_text=enhanced['enhanced_text'],
|
199
|
+
count=count,
|
200
|
+
distance_threshold=self.distance_threshold,
|
201
|
+
tags=self.tags
|
202
|
+
)
|
203
|
+
|
204
|
+
if not results:
|
205
|
+
no_results_msg = self.no_results_message.format(query=query)
|
206
|
+
if self.response_prefix:
|
207
|
+
no_results_msg = f"{self.response_prefix} {no_results_msg}"
|
208
|
+
if self.response_postfix:
|
209
|
+
no_results_msg = f"{no_results_msg} {self.response_postfix}"
|
210
|
+
return SwaigFunctionResult(no_results_msg)
|
211
|
+
|
212
|
+
# Format results
|
213
|
+
response_parts = []
|
214
|
+
|
215
|
+
# Add response prefix if configured
|
216
|
+
if self.response_prefix:
|
217
|
+
response_parts.append(self.response_prefix)
|
218
|
+
|
219
|
+
response_parts.append(f"Found {len(results)} relevant results for '{query}':\n")
|
220
|
+
|
221
|
+
for i, result in enumerate(results, 1):
|
222
|
+
filename = result['metadata']['filename']
|
223
|
+
section = result['metadata'].get('section', '')
|
224
|
+
score = result['score']
|
225
|
+
content = result['content']
|
226
|
+
|
227
|
+
result_text = f"**Result {i}** (from {filename}"
|
228
|
+
if section:
|
229
|
+
result_text += f", section: {section}"
|
230
|
+
result_text += f", relevance: {score:.2f})\n{content}\n"
|
231
|
+
|
232
|
+
response_parts.append(result_text)
|
233
|
+
|
234
|
+
# Add response postfix if configured
|
235
|
+
if self.response_postfix:
|
236
|
+
response_parts.append(self.response_postfix)
|
237
|
+
|
238
|
+
return SwaigFunctionResult("\n".join(response_parts))
|
239
|
+
|
240
|
+
except Exception as e:
|
241
|
+
return SwaigFunctionResult(f"Search error: {str(e)}")
|
242
|
+
|
243
|
+
def _search_remote(self, query: str, enhanced: dict, count: int) -> list:
|
244
|
+
"""Perform search using remote search server"""
|
245
|
+
try:
|
246
|
+
import requests
|
247
|
+
|
248
|
+
search_request = {
|
249
|
+
"query": query,
|
250
|
+
"index_name": self.index_name,
|
251
|
+
"count": count,
|
252
|
+
"distance": self.distance_threshold,
|
253
|
+
"tags": self.tags,
|
254
|
+
"language": "en"
|
255
|
+
}
|
256
|
+
|
257
|
+
response = requests.post(
|
258
|
+
f"{self.remote_url}/search",
|
259
|
+
json=search_request,
|
260
|
+
timeout=30
|
261
|
+
)
|
262
|
+
|
263
|
+
if response.status_code == 200:
|
264
|
+
data = response.json()
|
265
|
+
# Convert remote response format to local format
|
266
|
+
results = []
|
267
|
+
for result in data.get('results', []):
|
268
|
+
results.append({
|
269
|
+
'content': result['content'],
|
270
|
+
'score': result['score'],
|
271
|
+
'metadata': result['metadata']
|
272
|
+
})
|
273
|
+
return results
|
274
|
+
else:
|
275
|
+
self.logger.error(f"Remote search failed with status {response.status_code}: {response.text}")
|
276
|
+
return []
|
277
|
+
|
278
|
+
except Exception as e:
|
279
|
+
self.logger.error(f"Remote search error: {e}")
|
280
|
+
return []
|
281
|
+
|
282
|
+
def get_hints(self) -> List[str]:
|
283
|
+
"""Return speech recognition hints for this skill"""
|
284
|
+
hints = [
|
285
|
+
"search",
|
286
|
+
"find",
|
287
|
+
"look up",
|
288
|
+
"documentation",
|
289
|
+
"knowledge base"
|
290
|
+
]
|
291
|
+
|
292
|
+
# Add custom hints from params
|
293
|
+
custom_hints = self.params.get('hints', [])
|
294
|
+
hints.extend(custom_hints)
|
295
|
+
|
296
|
+
return hints
|
297
|
+
|
298
|
+
def get_global_data(self) -> Dict[str, Any]:
|
299
|
+
"""Return data to add to agent's global context"""
|
300
|
+
global_data = {}
|
301
|
+
|
302
|
+
if self.search_engine:
|
303
|
+
try:
|
304
|
+
stats = self.search_engine.get_stats()
|
305
|
+
global_data['search_stats'] = stats
|
306
|
+
except:
|
307
|
+
pass
|
308
|
+
|
309
|
+
return global_data
|
310
|
+
|
311
|
+
def get_prompt_sections(self) -> List[Dict[str, Any]]:
|
312
|
+
"""Return prompt sections to add to agent"""
|
313
|
+
search_mode = "remote search server" if self.use_remote else "local document indexes"
|
314
|
+
return [
|
315
|
+
{
|
316
|
+
"title": "Document Search",
|
317
|
+
"body": f"You can search {search_mode} using the {self.tool_name} tool.",
|
318
|
+
"bullets": [
|
319
|
+
f"Use the {self.tool_name} tool when users ask questions about topics that might be in the indexed documents",
|
320
|
+
"Search for relevant information using clear, specific queries",
|
321
|
+
"Provide helpful summaries of the search results",
|
322
|
+
"If no results are found, suggest the user try rephrasing their question or ask about different topics"
|
323
|
+
]
|
324
|
+
}
|
325
|
+
]
|
326
|
+
|
327
|
+
def _add_prompt_section(self, agent):
|
328
|
+
"""Add prompt section to agent (called during skill loading)"""
|
329
|
+
try:
|
330
|
+
agent.prompt_add_section(
|
331
|
+
title="Local Document Search",
|
332
|
+
body=f"You can search local document indexes using the {self.tool_name} tool.",
|
333
|
+
bullets=[
|
334
|
+
f"Use the {self.tool_name} tool when users ask questions about topics that might be in the indexed documents",
|
335
|
+
"Search for relevant information using clear, specific queries",
|
336
|
+
"Provide helpful summaries of the search results",
|
337
|
+
"If no results are found, suggest the user try rephrasing their question or ask about different topics"
|
338
|
+
]
|
339
|
+
)
|
340
|
+
except Exception as e:
|
341
|
+
self.logger.error(f"Failed to add prompt section: {e}")
|
342
|
+
# Continue without the prompt section
|
343
|
+
|
344
|
+
def cleanup(self) -> None:
|
345
|
+
"""Cleanup when skill is removed or agent shuts down"""
|
346
|
+
# Clean up any temporary files if we created them
|
347
|
+
if hasattr(self, '_temp_dirs'):
|
348
|
+
for temp_dir in self._temp_dirs:
|
349
|
+
try:
|
350
|
+
shutil.rmtree(temp_dir)
|
351
|
+
except:
|
352
|
+
pass
|
@@ -15,28 +15,15 @@ import sys
|
|
15
15
|
from typing import Dict, List, Type, Optional
|
16
16
|
from pathlib import Path
|
17
17
|
|
18
|
-
try:
|
19
|
-
import structlog
|
20
|
-
logger_available = True
|
21
|
-
except ImportError:
|
22
|
-
import logging
|
23
|
-
logger_available = False
|
24
|
-
|
25
18
|
from signalwire_agents.core.skill_base import SkillBase
|
19
|
+
from signalwire_agents.core.logging_config import get_logger
|
26
20
|
|
27
21
|
class SkillRegistry:
|
28
22
|
"""Global registry for discovering and managing skills"""
|
29
23
|
|
30
24
|
def __init__(self):
|
31
25
|
self._skills: Dict[str, Type[SkillBase]] = {}
|
32
|
-
|
33
|
-
# Use structlog if available, fallback to logging
|
34
|
-
if logger_available:
|
35
|
-
self.logger = structlog.get_logger("skill_registry")
|
36
|
-
else:
|
37
|
-
import logging
|
38
|
-
self.logger = logging.getLogger("skill_registry")
|
39
|
-
|
26
|
+
self.logger = get_logger("skill_registry")
|
40
27
|
self._discovered = False
|
41
28
|
|
42
29
|
def discover_skills(self) -> None:
|
@@ -7,5 +7,17 @@ Licensed under the MIT License.
|
|
7
7
|
See LICENSE file in the project root for full license information.
|
8
8
|
"""
|
9
9
|
|
10
|
-
from .
|
10
|
+
from .schema_utils import SchemaUtils
|
11
|
+
from signalwire_agents.core.logging_config import get_execution_mode
|
12
|
+
|
13
|
+
def is_serverless_mode() -> bool:
|
14
|
+
"""
|
15
|
+
Check if running in any serverless environment.
|
16
|
+
|
17
|
+
Returns:
|
18
|
+
bool: True if in serverless mode, False if in server mode
|
19
|
+
"""
|
20
|
+
return get_execution_mode() != 'server'
|
21
|
+
|
22
|
+
__all__ = ["SchemaUtils", "get_execution_mode", "is_serverless_mode"]
|
11
23
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: signalwire_agents
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.12
|
4
4
|
Summary: SignalWire AI Agents SDK
|
5
5
|
Author-email: SignalWire Team <info@signalwire.com>
|
6
6
|
Project-URL: Homepage, https://github.com/signalwire/signalwire-ai-agents
|
@@ -26,6 +26,42 @@ Requires-Dist: structlog==25.3.0
|
|
26
26
|
Requires-Dist: uvicorn==0.34.2
|
27
27
|
Requires-Dist: beautifulsoup4==4.12.3
|
28
28
|
Requires-Dist: pytz==2023.3
|
29
|
+
Provides-Extra: search
|
30
|
+
Requires-Dist: sentence-transformers>=2.2.0; extra == "search"
|
31
|
+
Requires-Dist: scikit-learn>=1.3.0; extra == "search"
|
32
|
+
Requires-Dist: nltk>=3.8; extra == "search"
|
33
|
+
Requires-Dist: numpy>=1.24.0; extra == "search"
|
34
|
+
Provides-Extra: search-full
|
35
|
+
Requires-Dist: sentence-transformers>=2.2.0; extra == "search-full"
|
36
|
+
Requires-Dist: scikit-learn>=1.3.0; extra == "search-full"
|
37
|
+
Requires-Dist: nltk>=3.8; extra == "search-full"
|
38
|
+
Requires-Dist: numpy>=1.24.0; extra == "search-full"
|
39
|
+
Requires-Dist: pdfplumber>=0.9.0; extra == "search-full"
|
40
|
+
Requires-Dist: python-docx>=0.8.11; extra == "search-full"
|
41
|
+
Requires-Dist: markdown>=3.4.0; extra == "search-full"
|
42
|
+
Requires-Dist: striprtf>=0.0.26; extra == "search-full"
|
43
|
+
Requires-Dist: openpyxl>=3.1.0; extra == "search-full"
|
44
|
+
Requires-Dist: python-pptx>=0.6.21; extra == "search-full"
|
45
|
+
Requires-Dist: python-magic>=0.4.27; extra == "search-full"
|
46
|
+
Provides-Extra: search-nlp
|
47
|
+
Requires-Dist: sentence-transformers>=2.2.0; extra == "search-nlp"
|
48
|
+
Requires-Dist: scikit-learn>=1.3.0; extra == "search-nlp"
|
49
|
+
Requires-Dist: nltk>=3.8; extra == "search-nlp"
|
50
|
+
Requires-Dist: numpy>=1.24.0; extra == "search-nlp"
|
51
|
+
Requires-Dist: spacy>=3.6.0; extra == "search-nlp"
|
52
|
+
Provides-Extra: search-all
|
53
|
+
Requires-Dist: sentence-transformers>=2.2.0; extra == "search-all"
|
54
|
+
Requires-Dist: scikit-learn>=1.3.0; extra == "search-all"
|
55
|
+
Requires-Dist: nltk>=3.8; extra == "search-all"
|
56
|
+
Requires-Dist: numpy>=1.24.0; extra == "search-all"
|
57
|
+
Requires-Dist: spacy>=3.6.0; extra == "search-all"
|
58
|
+
Requires-Dist: pdfplumber>=0.9.0; extra == "search-all"
|
59
|
+
Requires-Dist: python-docx>=0.8.11; extra == "search-all"
|
60
|
+
Requires-Dist: markdown>=3.4.0; extra == "search-all"
|
61
|
+
Requires-Dist: striprtf>=0.0.26; extra == "search-all"
|
62
|
+
Requires-Dist: openpyxl>=3.1.0; extra == "search-all"
|
63
|
+
Requires-Dist: python-pptx>=0.6.21; extra == "search-all"
|
64
|
+
Requires-Dist: python-magic>=0.4.27; extra == "search-all"
|
29
65
|
Dynamic: license-file
|
30
66
|
|
31
67
|
# SignalWire AI Agent SDK
|
@@ -45,6 +81,7 @@ A Python SDK for creating, hosting, and securing SignalWire AI agents as microse
|
|
45
81
|
- **Prefab Archetypes**: Ready-to-use agent types for common scenarios
|
46
82
|
- **Multi-Agent Support**: Host multiple agents on a single server
|
47
83
|
- **Modular Skills System**: Add capabilities to agents with simple one-liner calls
|
84
|
+
- **Local Search System**: Offline document search with vector similarity and keyword search
|
48
85
|
|
49
86
|
## Skills System
|
50
87
|
|
@@ -124,6 +161,7 @@ agent.serve()
|
|
124
161
|
- **datetime**: Current date and time with timezone support
|
125
162
|
- **math**: Safe mathematical expression evaluation
|
126
163
|
- **datasphere**: SignalWire DataSphere knowledge search (supports multiple instances)
|
164
|
+
- **native_vector_search**: Offline document search with vector similarity and keyword search
|
127
165
|
|
128
166
|
### Benefits
|
129
167
|
|
@@ -424,10 +462,73 @@ For detailed documentation and advanced examples, see [Contexts and Steps Guide]
|
|
424
462
|
|
425
463
|
## Installation
|
426
464
|
|
465
|
+
### Basic Installation
|
466
|
+
|
427
467
|
```bash
|
428
468
|
pip install signalwire-agents
|
429
469
|
```
|
430
470
|
|
471
|
+
### Optional Search Functionality
|
472
|
+
|
473
|
+
The SDK includes optional local search capabilities that can be installed separately to avoid adding large dependencies to the base installation:
|
474
|
+
|
475
|
+
#### Search Installation Options
|
476
|
+
|
477
|
+
```bash
|
478
|
+
# Basic search (vector search + keyword search)
|
479
|
+
pip install signalwire-agents[search]
|
480
|
+
|
481
|
+
# Full search with document processing (PDF, DOCX, etc.)
|
482
|
+
pip install signalwire-agents[search-full]
|
483
|
+
|
484
|
+
# Advanced NLP features (includes spaCy)
|
485
|
+
pip install signalwire-agents[search-nlp]
|
486
|
+
|
487
|
+
# All search features
|
488
|
+
pip install signalwire-agents[search-all]
|
489
|
+
```
|
490
|
+
|
491
|
+
#### What Each Option Includes
|
492
|
+
|
493
|
+
| Option | Size | Features |
|
494
|
+
|--------|------|----------|
|
495
|
+
| `search` | ~500MB | Vector embeddings, keyword search, basic text processing |
|
496
|
+
| `search-full` | ~600MB | + PDF, DOCX, Excel, PowerPoint, HTML, Markdown processing |
|
497
|
+
| `search-nlp` | ~600MB | + Advanced spaCy NLP features |
|
498
|
+
| `search-all` | ~700MB | All search features combined |
|
499
|
+
|
500
|
+
#### Search Features
|
501
|
+
|
502
|
+
- **Local/Offline Search**: No external API dependencies
|
503
|
+
- **Hybrid Search**: Vector similarity + keyword search
|
504
|
+
- **Smart Document Processing**: Markdown, Python, PDF, DOCX, etc.
|
505
|
+
- **Multiple Languages**: English, Spanish, with extensible framework
|
506
|
+
- **CLI Tools**: Build search indexes from document directories
|
507
|
+
- **HTTP API**: Standalone or embedded search service
|
508
|
+
|
509
|
+
#### Usage Example
|
510
|
+
|
511
|
+
```python
|
512
|
+
# Only available with search extras installed
|
513
|
+
from signalwire_agents.search import IndexBuilder, SearchEngine
|
514
|
+
|
515
|
+
# Build search index
|
516
|
+
builder = IndexBuilder()
|
517
|
+
builder.build_index(
|
518
|
+
source_dir="./docs",
|
519
|
+
output_file="knowledge.swsearch",
|
520
|
+
file_types=['md', 'txt', 'pdf']
|
521
|
+
)
|
522
|
+
|
523
|
+
# Search documents
|
524
|
+
engine = SearchEngine("knowledge.swsearch")
|
525
|
+
results = engine.search(
|
526
|
+
query_vector=embeddings,
|
527
|
+
enhanced_text="search query",
|
528
|
+
count=5
|
529
|
+
)
|
530
|
+
```
|
531
|
+
|
431
532
|
## Quick Start
|
432
533
|
|
433
534
|
```python
|
@@ -634,9 +735,12 @@ To enable HTTPS directly (without a reverse proxy), set `SWML_SSL_ENABLED` to "t
|
|
634
735
|
|
635
736
|
## Testing
|
636
737
|
|
637
|
-
The SDK includes
|
738
|
+
The SDK includes powerful CLI tools for development and testing:
|
739
|
+
|
740
|
+
- **`swaig-test`**: Comprehensive local testing and serverless environment simulation
|
741
|
+
- **`sw-search`**: Build local search indexes from document directories and search within them
|
638
742
|
|
639
|
-
### Local Testing
|
743
|
+
### Local Testing with swaig-test
|
640
744
|
|
641
745
|
Test your agents locally without deployment:
|
642
746
|
|
@@ -748,6 +852,9 @@ The package includes comprehensive documentation in the `docs/` directory:
|
|
748
852
|
- [Agent Guide](docs/agent_guide.md) - Detailed guide to creating and customizing agents, including dynamic configuration
|
749
853
|
- [Architecture](docs/architecture.md) - Overview of the SDK architecture and core concepts
|
750
854
|
- [SWML Service Guide](docs/swml_service_guide.md) - Guide to the underlying SWML service
|
855
|
+
- [Local Search System](docs/search-system.md) - Complete guide to the local search system with vector similarity and keyword search
|
856
|
+
- [Skills System](docs/skills_system.md) - Detailed documentation on the modular skills system
|
857
|
+
- [CLI Tools](docs/cli.md) - Command-line interface tools for development and testing
|
751
858
|
|
752
859
|
These documents provide in-depth explanations of the features, APIs, and usage patterns.
|
753
860
|
|
@@ -1,13 +1,15 @@
|
|
1
|
-
signalwire_agents/__init__.py,sha256=
|
2
|
-
signalwire_agents/agent_server.py,sha256=
|
1
|
+
signalwire_agents/__init__.py,sha256=5iXBCyEKoudhgH9zYcR3L7Je4n5NpUd4q1qgQ7F94uo,2093
|
2
|
+
signalwire_agents/agent_server.py,sha256=3Or8rIMAqW750V-XitBUMgOpW9BAIXmKXoGq7LkejAA,24988
|
3
3
|
signalwire_agents/schema.json,sha256=M8Mn6pQda2P9jhbmkALrLr1wt-fRuhYRqdmEi9Rbhqk,178075
|
4
4
|
signalwire_agents/cli/__init__.py,sha256=Iy2BfWDWBEZoA1cyHTDsooBSVMx4vH5Ddhr3sEuFe8c,197
|
5
|
-
signalwire_agents/cli/
|
5
|
+
signalwire_agents/cli/build_search.py,sha256=FXEgZEDn_2-7sJGQ07LYUxT9MGySUN-kpUbCCPBYxWk,15966
|
6
|
+
signalwire_agents/cli/test_swaig.py,sha256=tBl2r-MqxKDiAVMfaDOntR9OMFE3I0ff51oGMlRQsl8,100237
|
6
7
|
signalwire_agents/core/__init__.py,sha256=mVDLbpq1pg_WwiqsQR28NNZwJ6-VUXFIfg-vN7pk0ew,806
|
7
|
-
signalwire_agents/core/agent_base.py,sha256=
|
8
|
+
signalwire_agents/core/agent_base.py,sha256=XTDlVsmgvtaO8696Dtsw5s9pk2TJz2TyySrurJpndJs,141704
|
8
9
|
signalwire_agents/core/contexts.py,sha256=h7hra4xoiKAUdVyJhcKggl8X9EoqwTVWBmNMp-sEsuc,9598
|
9
10
|
signalwire_agents/core/data_map.py,sha256=U-HLEZQomWf-UI0-nLAE8g1oyRdE5bU_WxQpboI2YI4,17695
|
10
11
|
signalwire_agents/core/function_result.py,sha256=SP46vPAliEzwh3JeeSxmLD_f7_ixRxMBtpSl3t7jieU,45370
|
12
|
+
signalwire_agents/core/logging_config.py,sha256=ATO_Zo8Kc6Ts1wU39ewliF_dKAy6M91I9q-KiilsLyk,7507
|
11
13
|
signalwire_agents/core/pom_builder.py,sha256=ywuiIfP8BeLBPo_G4X1teZlG6zTCMkW71CZnmyoDTAQ,6636
|
12
14
|
signalwire_agents/core/skill_base.py,sha256=lOpVTLhD9NjStF7Lxh6bAQUGa3DpNYV4agXJRakRuX0,4258
|
13
15
|
signalwire_agents/core/skill_manager.py,sha256=6vAzkWYeycilX-5T1B039sf8s11BfRh6ASstt70wWsw,8074
|
@@ -27,8 +29,14 @@ signalwire_agents/prefabs/faq_bot.py,sha256=NrUn5AGmtdzYTyxTHPt8BZ14ZN1sh4xKA2SV
|
|
27
29
|
signalwire_agents/prefabs/info_gatherer.py,sha256=dr9UUgNGX7MIKdCMth3jDVLf6OrHov5G6_zIuNvnrOY,15468
|
28
30
|
signalwire_agents/prefabs/receptionist.py,sha256=8EyQ6M0Egs3g7KKWukHFiO9UPoVUxT4MLkvyT3V8o64,10585
|
29
31
|
signalwire_agents/prefabs/survey.py,sha256=IIIQfgvMlfVNjEEEdWUn4lAJqVsCDlBsIAkOJ1ckyAE,14796
|
32
|
+
signalwire_agents/search/__init__.py,sha256=x7saU_MDbhoOIzcvCT1-gnqyH2rrMpzB4ZUqk-av-lI,3958
|
33
|
+
signalwire_agents/search/document_processor.py,sha256=uqBvL8EGCou81ack7T49H0MNC7NkIRbciPtQecb1Jjo,31204
|
34
|
+
signalwire_agents/search/index_builder.py,sha256=wAfDtK4YgnbHiG-syvEzbNGUHN92PMy1gMnhG_rXAUM,21143
|
35
|
+
signalwire_agents/search/query_processor.py,sha256=1o8dEHVG52ci1e1PQxm5a0_1FV4zdqYBex_qcyJabRs,13926
|
36
|
+
signalwire_agents/search/search_engine.py,sha256=KFF33aPsBdwb2N1aTJmAA0xo3KPANoXjwV53uSxih9o,14798
|
37
|
+
signalwire_agents/search/search_service.py,sha256=Src_REgjkcddHBDCaLv2BMJnDSxcE4XV_1U8YStJOh8,8719
|
30
38
|
signalwire_agents/skills/__init__.py,sha256=xfxrQ0i-aTRomHiCsqelU4RlNlHPJFPgPu-UBDaBOqA,340
|
31
|
-
signalwire_agents/skills/registry.py,sha256=
|
39
|
+
signalwire_agents/skills/registry.py,sha256=UrFhZJdc_8Y9k9KCSRZtt7QemafDNtuvyuheAXShjuU,3821
|
32
40
|
signalwire_agents/skills/datasphere/__init__.py,sha256=SJJlmeMSeezjINPgkuWN1XzDPN_Z3GzZ_StzO1BtxQs,257
|
33
41
|
signalwire_agents/skills/datasphere/skill.py,sha256=L6GrGwej3sKPcHljKBNf4it5g4DaGzR18KlQx65_XKg,9598
|
34
42
|
signalwire_agents/skills/datasphere_serverless/__init__.py,sha256=65hu8_0eqiczLSZ-aJgASpMQqTUjzTQUI1fC8GI7qTI,70
|
@@ -39,20 +47,21 @@ signalwire_agents/skills/joke/__init__.py,sha256=R-iS9UMMvOdpkxL9aooVik16eCddJw1
|
|
39
47
|
signalwire_agents/skills/joke/skill.py,sha256=AFaf6fMy0sxUPJHvcnf3CWMuPqpJP4ODscUexMadEcU,3381
|
40
48
|
signalwire_agents/skills/math/__init__.py,sha256=lGAFWEmJH2fuwkuZUdDTY5dmucrIwtjfNT8bE2hOSP8,39
|
41
49
|
signalwire_agents/skills/math/skill.py,sha256=5sErd5x1rFHJg2GlmdJB3LvrmvTNOrZsA2jRnG67Zw8,3342
|
50
|
+
signalwire_agents/skills/native_vector_search/__init__.py,sha256=buvncVoH5u8MJA0SLlz1JQgIuyBTQW5aql-ydnc7Wh8,29
|
51
|
+
signalwire_agents/skills/native_vector_search/skill.py,sha256=jpXah7teIljgTfl2mW73VMlBwEKXS-s4Cws6XCS2JxU,14846
|
42
52
|
signalwire_agents/skills/web_search/__init__.py,sha256=wJlptYDExYw-nxZJVzlTLOgkKkDOLUUt1ZdoLt44ixs,45
|
43
53
|
signalwire_agents/skills/web_search/skill.py,sha256=GkfhG3Vz2HxOv91TvVuA4e_4b5cuswUpnJDLdaZW37k,10304
|
44
54
|
signalwire_agents/skills/wikipedia/__init__.py,sha256=8Db_aE0ly7QoXg7n2RDvCqKupkyR-UYlK9uFUnGNCE8,184
|
45
55
|
signalwire_agents/skills/wikipedia/skill.py,sha256=Q_HWJoG2RkQuZMgWxD9czuzEor79Gy1tjo6ywVzec84,6877
|
46
|
-
signalwire_agents/utils/__init__.py,sha256=
|
56
|
+
signalwire_agents/utils/__init__.py,sha256=1KVsHzwgfktSXHe3vqSRGImjtIE58szwD2FHHoFBtvY,601
|
47
57
|
signalwire_agents/utils/pom_utils.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663YjFX_PumJ35Xds,191
|
48
58
|
signalwire_agents/utils/schema_utils.py,sha256=i4okv_O9bUApwT_jJf4Yoij3bLCrGrW3DC-vzSy2RuY,16392
|
49
|
-
signalwire_agents/utils/serverless.py,sha256=5kEdoN8Sngv-x4pyk4wRrn8SafJEm6LWqkM2wfO7v2k,979
|
50
59
|
signalwire_agents/utils/token_generators.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663YjFX_PumJ35Xds,191
|
51
60
|
signalwire_agents/utils/validators.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663YjFX_PumJ35Xds,191
|
52
|
-
signalwire_agents-0.1.
|
53
|
-
signalwire_agents-0.1.
|
54
|
-
signalwire_agents-0.1.
|
55
|
-
signalwire_agents-0.1.
|
56
|
-
signalwire_agents-0.1.
|
57
|
-
signalwire_agents-0.1.
|
58
|
-
signalwire_agents-0.1.
|
61
|
+
signalwire_agents-0.1.12.data/data/schema.json,sha256=M8Mn6pQda2P9jhbmkALrLr1wt-fRuhYRqdmEi9Rbhqk,178075
|
62
|
+
signalwire_agents-0.1.12.dist-info/licenses/LICENSE,sha256=NYvAsB-rTcSvG9cqHt9EUHAWLiA9YzM4Qfz-mPdvDR0,1067
|
63
|
+
signalwire_agents-0.1.12.dist-info/METADATA,sha256=cKKKnqlvhw4V0CJwtl_6xQtCVS1kVKReuAVbfxK6VuA,34572
|
64
|
+
signalwire_agents-0.1.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
65
|
+
signalwire_agents-0.1.12.dist-info/entry_points.txt,sha256=7RBxC9wwFdsqP7g1F4JzsJ3AIHsjGtb3h0mxEGmANlI,151
|
66
|
+
signalwire_agents-0.1.12.dist-info/top_level.txt,sha256=kDGS6ZYv84K9P5Kyg9_S8P_pbUXoHkso0On_DB5bbWc,18
|
67
|
+
signalwire_agents-0.1.12.dist-info/RECORD,,
|