signalwire-agents 0.1.47__py3-none-any.whl → 0.1.48__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.
@@ -31,7 +31,7 @@ try:
31
31
  except ImportError:
32
32
  SentenceTransformer = None
33
33
 
34
- from .query_processor import preprocess_query
34
+ from .query_processor import preprocess_query, set_global_model
35
35
  from .search_engine import SearchEngine
36
36
  from signalwire_agents.core.security_config import SecurityConfig
37
37
  from signalwire_agents.core.config_loader import ConfigLoader
@@ -39,6 +39,11 @@ from signalwire_agents.core.logging_config import get_logger
39
39
 
40
40
  logger = get_logger("search_service")
41
41
 
42
+ # Simple LRU cache for query results
43
+ from functools import lru_cache
44
+ import hashlib
45
+ import json
46
+
42
47
  # Pydantic models for API
43
48
  if BaseModel:
44
49
  class SearchRequest(BaseModel):
@@ -81,6 +86,17 @@ else:
81
86
  self.results = results
82
87
  self.query_analysis = query_analysis
83
88
 
89
+ def _cache_key(query: str, index_name: str, count: int, tags: Optional[List[str]] = None) -> str:
90
+ """Generate cache key for query results"""
91
+ key_data = {
92
+ 'query': query.lower().strip(),
93
+ 'index': index_name,
94
+ 'count': count,
95
+ 'tags': sorted(tags) if tags else []
96
+ }
97
+ key_str = json.dumps(key_data, sort_keys=True)
98
+ return hashlib.md5(key_str.encode()).hexdigest()
99
+
84
100
  class SearchService:
85
101
  """Local search service with HTTP API supporting both SQLite and pgvector backends"""
86
102
 
@@ -102,6 +118,8 @@ class SearchService:
102
118
 
103
119
  self.search_engines = {}
104
120
  self.model = None
121
+ self._query_cache = {} # Simple query result cache
122
+ self._cache_size = 100 # Max number of cached queries
105
123
 
106
124
  # Load security configuration with optional config file
107
125
  self.security = SecurityConfig(config_file=config_file, service_name="search")
@@ -270,7 +288,11 @@ class SearchService:
270
288
  else:
271
289
  # SQLite backend
272
290
  self.indexes[index_name] = index_path
273
- self.search_engines[index_name] = SearchEngine(index_path, self.model)
291
+ self.search_engines[index_name] = SearchEngine(
292
+ backend='sqlite',
293
+ index_path=index_path,
294
+ model=self.model
295
+ )
274
296
  return {"status": "reloaded", "index": index_name, "backend": "sqlite"}
275
297
 
276
298
  def _load_resources(self):
@@ -298,6 +320,8 @@ class SearchService:
298
320
  model_name = self._get_model_name(sample_index)
299
321
  try:
300
322
  self.model = SentenceTransformer(model_name)
323
+ # Set the global model for query processor to avoid reloading
324
+ set_global_model(self.model)
301
325
  except Exception as e:
302
326
  logger.warning(f"Could not load sentence transformer model: {e}")
303
327
  self.model = None
@@ -305,7 +329,11 @@ class SearchService:
305
329
  # Load search engines for each index
306
330
  for index_name, index_path in self.indexes.items():
307
331
  try:
308
- self.search_engines[index_name] = SearchEngine(index_path, self.model)
332
+ self.search_engines[index_name] = SearchEngine(
333
+ backend='sqlite',
334
+ index_path=index_path,
335
+ model=self.model
336
+ )
309
337
  except Exception as e:
310
338
  logger.error(f"Error loading search engine for {index_name}: {e}")
311
339
 
@@ -330,21 +358,34 @@ class SearchService:
330
358
  return 'sentence-transformers/all-mpnet-base-v2'
331
359
 
332
360
  async def _handle_search(self, request: SearchRequest) -> SearchResponse:
333
- """Handle search request"""
361
+ """Handle search request with caching"""
334
362
  if request.index_name not in self.search_engines:
335
363
  if HTTPException:
336
364
  raise HTTPException(status_code=404, detail=f"Index '{request.index_name}' not found")
337
365
  else:
338
366
  raise ValueError(f"Index '{request.index_name}' not found")
339
367
 
368
+ # Check cache first
369
+ cache_key = _cache_key(request.query, request.index_name, request.count, request.tags)
370
+ if cache_key in self._query_cache:
371
+ logger.info(f"Cache hit for query: {request.query[:50]}...")
372
+ return self._query_cache[cache_key]
373
+
340
374
  search_engine = self.search_engines[request.index_name]
341
375
 
376
+ # Get model name from the search engine config
377
+ model_name = None
378
+ if hasattr(search_engine, 'config') and search_engine.config:
379
+ # pgvector uses 'model_name', sqlite uses 'embedding_model'
380
+ model_name = search_engine.config.get('model_name') or search_engine.config.get('embedding_model')
381
+
342
382
  # Enhance query
343
383
  try:
344
384
  enhanced = preprocess_query(
345
385
  request.query,
346
386
  language=request.language or 'auto',
347
- vector=True
387
+ vector=True,
388
+ model_name=model_name # Pass the correct model!
348
389
  )
349
390
  except Exception as e:
350
391
  logger.error(f"Error preprocessing query: {e}")
@@ -377,7 +418,7 @@ class SearchService:
377
418
  for result in results
378
419
  ]
379
420
 
380
- return SearchResponse(
421
+ response = SearchResponse(
381
422
  results=search_results,
382
423
  query_analysis={
383
424
  'original_query': request.query,
@@ -386,6 +427,15 @@ class SearchService:
386
427
  'pos_analysis': enhanced.get('POS')
387
428
  }
388
429
  )
430
+
431
+ # Cache the result
432
+ if len(self._query_cache) >= self._cache_size:
433
+ # Simple FIFO eviction
434
+ first_key = next(iter(self._query_cache))
435
+ del self._query_cache[first_key]
436
+ self._query_cache[cache_key] = response
437
+
438
+ return response
389
439
 
390
440
  def search_direct(self, query: str, index_name: str = "default", count: int = 3,
391
441
  distance: float = 0.0, tags: Optional[List[str]] = None,
@@ -136,6 +136,11 @@ class NativeVectorSearchSkill(SkillBase):
136
136
  "default": "",
137
137
  "required": False
138
138
  },
139
+ "response_format_callback": {
140
+ "type": "callable",
141
+ "description": "Optional callback function to format/transform the response. Called with (response, agent, query, results, args). Must return a string.",
142
+ "required": False
143
+ },
139
144
  "description": {
140
145
  "type": "string",
141
146
  "description": "Tool description",
@@ -192,6 +197,26 @@ class NativeVectorSearchSkill(SkillBase):
192
197
  "description": "Enable verbose logging",
193
198
  "default": False,
194
199
  "required": False
200
+ },
201
+ "keyword_weight": {
202
+ "type": "number",
203
+ "description": "Manual keyword weight (0.0-1.0). Overrides automatic weight detection",
204
+ "default": None,
205
+ "required": False,
206
+ "minimum": 0.0,
207
+ "maximum": 1.0
208
+ },
209
+ "model_name": {
210
+ "type": "string",
211
+ "description": "Embedding model to use. Options: 'mini' (fastest, 384 dims), 'base' (balanced, 768 dims), 'large' (same as base). Or specify full model name like 'sentence-transformers/all-MiniLM-L6-v2'",
212
+ "default": "mini",
213
+ "required": False
214
+ },
215
+ "overwrite": {
216
+ "type": "boolean",
217
+ "description": "Overwrite existing pgvector collection when building index (pgvector backend only)",
218
+ "default": False,
219
+ "required": False
195
220
  }
196
221
  })
197
222
  return schema
@@ -226,11 +251,29 @@ class NativeVectorSearchSkill(SkillBase):
226
251
  )
227
252
  self.response_prefix = self.params.get('response_prefix', '')
228
253
  self.response_postfix = self.params.get('response_postfix', '')
254
+ self.response_format_callback = self.params.get('response_format_callback')
255
+ self.keyword_weight = self.params.get('keyword_weight')
256
+ self.model_name = self.params.get('model_name', 'mini')
229
257
 
230
258
  # Remote search server configuration
231
- self.remote_url = self.params.get('remote_url') # e.g., "http://localhost:8001"
259
+ self.remote_url = self.params.get('remote_url') # e.g., "http://user:pass@localhost:8001"
232
260
  self.index_name = self.params.get('index_name', 'default') # For remote searches
233
261
 
262
+ # Parse auth from URL if present
263
+ self.remote_auth = None
264
+ self.remote_base_url = self.remote_url
265
+ if self.remote_url:
266
+ from urllib.parse import urlparse
267
+ parsed = urlparse(self.remote_url)
268
+ if parsed.username and parsed.password:
269
+ self.remote_auth = (parsed.username, parsed.password)
270
+ # Reconstruct URL without auth for display
271
+ self.remote_base_url = f"{parsed.scheme}://{parsed.hostname}"
272
+ if parsed.port:
273
+ self.remote_base_url += f":{parsed.port}"
274
+ if parsed.path:
275
+ self.remote_base_url += parsed.path
276
+
234
277
  # SWAIG fields for function fillers
235
278
  self.swaig_fields = self.params.get('swaig_fields', {})
236
279
 
@@ -244,11 +287,20 @@ class NativeVectorSearchSkill(SkillBase):
244
287
  # Test remote connection (lightweight check)
245
288
  try:
246
289
  import requests
247
- response = requests.get(f"{self.remote_url}/health", timeout=5)
290
+ # Use parsed base URL and auth
291
+ response = requests.get(
292
+ f"{self.remote_base_url}/health",
293
+ auth=self.remote_auth,
294
+ timeout=5
295
+ )
248
296
  if response.status_code == 200:
249
- self.logger.info("Remote search server is available")
297
+ self.logger.info(f"Remote search server is available at {self.remote_base_url}")
250
298
  self.search_available = True
251
299
  return True # Success - skip all local setup
300
+ elif response.status_code == 401:
301
+ self.logger.error("Authentication failed for remote search server. Check credentials.")
302
+ self.search_available = False
303
+ return False
252
304
  else:
253
305
  self.logger.error(f"Remote search server returned status {response.status_code}")
254
306
  self.search_available = False
@@ -295,32 +347,72 @@ class NativeVectorSearchSkill(SkillBase):
295
347
 
296
348
  # Auto-build index if requested and search is available
297
349
  if self.build_index and self.source_dir and self.search_available:
298
- if not self.index_file:
299
- # Generate index filename from source directory
300
- source_name = Path(self.source_dir).name
301
- self.index_file = f"{source_name}.swsearch"
302
-
303
- # Build index if it doesn't exist
304
- if not os.path.exists(self.index_file):
305
- try:
306
- self.logger.info(f"Building search index from {self.source_dir}...")
307
- from signalwire_agents.search import IndexBuilder
308
-
309
- builder = IndexBuilder(
310
- verbose=self.params.get('verbose', False),
311
- index_nlp_backend=self.index_nlp_backend
312
- )
313
- builder.build_index(
314
- source_dir=self.source_dir,
315
- output_file=self.index_file,
316
- file_types=self.params.get('file_types', ['md', 'txt']),
317
- exclude_patterns=self.params.get('exclude_patterns'),
318
- tags=self.params.get('global_tags')
319
- )
320
- self.logger.info(f"Search index created: {self.index_file}")
321
- except Exception as e:
322
- self.logger.error(f"Failed to build search index: {e}")
323
- self.search_available = False
350
+ # Handle auto-build for different backends
351
+ if self.backend == 'sqlite':
352
+ if not self.index_file:
353
+ # Generate index filename from source directory
354
+ source_name = Path(self.source_dir).name
355
+ self.index_file = f"{source_name}.swsearch"
356
+
357
+ # Build index if it doesn't exist
358
+ if not os.path.exists(self.index_file):
359
+ try:
360
+ self.logger.info(f"Building search index from {self.source_dir}...")
361
+ from signalwire_agents.search import IndexBuilder
362
+
363
+ # Resolve model alias if needed
364
+ from signalwire_agents.search.models import resolve_model_alias
365
+ model_to_use = resolve_model_alias(self.model_name)
366
+
367
+ builder = IndexBuilder(
368
+ model_name=model_to_use,
369
+ verbose=self.params.get('verbose', False),
370
+ index_nlp_backend=self.index_nlp_backend
371
+ )
372
+ builder.build_index(
373
+ source_dir=self.source_dir,
374
+ output_file=self.index_file,
375
+ file_types=self.params.get('file_types', ['md', 'txt']),
376
+ exclude_patterns=self.params.get('exclude_patterns'),
377
+ tags=self.params.get('global_tags')
378
+ )
379
+ self.logger.info(f"Search index created: {self.index_file}")
380
+ except Exception as e:
381
+ self.logger.error(f"Failed to build search index: {e}")
382
+ self.search_available = False
383
+
384
+ elif self.backend == 'pgvector':
385
+ # Auto-build for pgvector
386
+ if self.connection_string and self.collection_name:
387
+ try:
388
+ self.logger.info(f"Building pgvector index from {self.source_dir}...")
389
+ from signalwire_agents.search import IndexBuilder
390
+ from signalwire_agents.search.models import resolve_model_alias
391
+
392
+ model_to_use = resolve_model_alias(self.model_name)
393
+
394
+ builder = IndexBuilder(
395
+ backend='pgvector',
396
+ connection_string=self.connection_string,
397
+ model_name=model_to_use,
398
+ verbose=self.params.get('verbose', False),
399
+ index_nlp_backend=self.index_nlp_backend
400
+ )
401
+
402
+ builder.build_index(
403
+ source_dir=self.source_dir,
404
+ output_file=self.collection_name, # pgvector uses this as collection name
405
+ file_types=self.params.get('file_types', ['md', 'txt']),
406
+ exclude_patterns=self.params.get('exclude_patterns'),
407
+ tags=self.params.get('global_tags'),
408
+ overwrite=self.params.get('overwrite', False)
409
+ )
410
+ self.logger.info(f"pgvector collection created: {self.collection_name}")
411
+ except Exception as e:
412
+ self.logger.error(f"Failed to build pgvector index: {e}")
413
+ # Don't set search_available to False - we might be connecting to existing collection
414
+ else:
415
+ self.logger.warning("pgvector auto-build requires connection_string and collection_name")
324
416
 
325
417
  # Initialize local search engine
326
418
  self.search_engine = None
@@ -347,6 +439,12 @@ class NativeVectorSearchSkill(SkillBase):
347
439
  try:
348
440
  from signalwire_agents.search import SearchEngine
349
441
  self.search_engine = SearchEngine(backend='sqlite', index_path=self.index_file)
442
+ # The SearchEngine will auto-detect the model from the index
443
+ # Get the model name from config for query processing
444
+ if hasattr(self.search_engine, 'config'):
445
+ index_model = self.search_engine.config.get('embedding_model')
446
+ if index_model:
447
+ self.logger.info(f"Using model from index: {index_model}")
350
448
  except Exception as e:
351
449
  self.logger.error(f"Failed to load search index {self.index_file}: {e}")
352
450
  self.search_available = False
@@ -451,13 +549,29 @@ class NativeVectorSearchSkill(SkillBase):
451
549
  else:
452
550
  # For local searches, preprocess the query locally
453
551
  from signalwire_agents.search.query_processor import preprocess_query
454
- enhanced = preprocess_query(query, language='en', vector=True, query_nlp_backend=self.query_nlp_backend)
552
+
553
+ # Get model name from index config if available
554
+ model_for_query = None
555
+ if hasattr(self.search_engine, 'config'):
556
+ model_for_query = self.search_engine.config.get('embedding_model')
557
+
558
+ enhanced = preprocess_query(
559
+ query,
560
+ language='en',
561
+ vector=True,
562
+ query_nlp_backend=self.query_nlp_backend,
563
+ model_name=model_for_query, # Use model from index
564
+ preserve_original=True, # Keep original query terms
565
+ max_synonyms=2 # Reduce synonym expansion
566
+ )
455
567
  results = self.search_engine.search(
456
568
  query_vector=enhanced.get('vector', []),
457
569
  enhanced_text=enhanced['enhanced_text'],
458
570
  count=count,
459
571
  distance_threshold=self.distance_threshold,
460
- tags=self.tags
572
+ tags=self.tags,
573
+ keyword_weight=self.keyword_weight,
574
+ original_query=query # Pass original for exact match boosting
461
575
  )
462
576
 
463
577
  if not results:
@@ -466,6 +580,25 @@ class NativeVectorSearchSkill(SkillBase):
466
580
  no_results_msg = f"{self.response_prefix} {no_results_msg}"
467
581
  if self.response_postfix:
468
582
  no_results_msg = f"{no_results_msg} {self.response_postfix}"
583
+
584
+ # Apply custom formatting callback for no results case
585
+ if self.response_format_callback and callable(self.response_format_callback):
586
+ try:
587
+ callback_context = {
588
+ 'response': no_results_msg,
589
+ 'agent': self.agent,
590
+ 'query': query,
591
+ 'results': [], # Empty results
592
+ 'args': args,
593
+ 'count': count,
594
+ 'skill': self
595
+ }
596
+ formatted_response = self.response_format_callback(**callback_context)
597
+ if isinstance(formatted_response, str):
598
+ no_results_msg = formatted_response
599
+ except Exception as e:
600
+ self.logger.error(f"Error in response_format_callback (no results): {e}", exc_info=True)
601
+
469
602
  return SwaigFunctionResult(no_results_msg)
470
603
 
471
604
  # Format results
@@ -483,9 +616,20 @@ class NativeVectorSearchSkill(SkillBase):
483
616
  score = result['score']
484
617
  content = result['content']
485
618
 
619
+ # Get tags from either top level or metadata
620
+ tags = result.get('tags', [])
621
+ if not tags and 'metadata' in result['metadata'] and 'tags' in result['metadata']['metadata']:
622
+ # Handle double-nested metadata from older indexes
623
+ tags = result['metadata']['metadata']['tags']
624
+ elif not tags and 'tags' in result['metadata']:
625
+ # Check in metadata directly
626
+ tags = result['metadata']['tags']
627
+
486
628
  result_text = f"**Result {i}** (from {filename}"
487
629
  if section:
488
630
  result_text += f", section: {section}"
631
+ if tags:
632
+ result_text += f", tags: {', '.join(tags)}"
489
633
  result_text += f", relevance: {score:.2f})\n{content}\n"
490
634
 
491
635
  response_parts.append(result_text)
@@ -494,7 +638,37 @@ class NativeVectorSearchSkill(SkillBase):
494
638
  if self.response_postfix:
495
639
  response_parts.append(self.response_postfix)
496
640
 
497
- return SwaigFunctionResult("\n".join(response_parts))
641
+ # Build the initial response
642
+ response = "\n".join(response_parts)
643
+
644
+ # Apply custom formatting callback if provided
645
+ if self.response_format_callback and callable(self.response_format_callback):
646
+ try:
647
+ # Prepare callback context
648
+ callback_context = {
649
+ 'response': response,
650
+ 'agent': self.agent,
651
+ 'query': query,
652
+ 'results': results,
653
+ 'args': args,
654
+ 'count': count,
655
+ 'skill': self
656
+ }
657
+
658
+ # Call the callback
659
+ formatted_response = self.response_format_callback(**callback_context)
660
+
661
+ # Validate callback returned a string
662
+ if isinstance(formatted_response, str):
663
+ response = formatted_response
664
+ else:
665
+ self.logger.warning(f"response_format_callback returned non-string type: {type(formatted_response)}")
666
+
667
+ except Exception as e:
668
+ self.logger.error(f"Error in response_format_callback: {e}", exc_info=True)
669
+ # Continue with original response if callback fails
670
+
671
+ return SwaigFunctionResult(response)
498
672
 
499
673
  except Exception as e:
500
674
  # Log the full error details for debugging
@@ -531,8 +705,9 @@ class NativeVectorSearchSkill(SkillBase):
531
705
  }
532
706
 
533
707
  response = requests.post(
534
- f"{self.remote_url}/search",
708
+ f"{self.remote_base_url}/search",
535
709
  json=search_request,
710
+ auth=self.remote_auth,
536
711
  timeout=30
537
712
  )
538
713
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: signalwire_agents
3
- Version: 0.1.47
3
+ Version: 0.1.48
4
4
  Summary: SignalWire AI Agents SDK
5
5
  Author-email: SignalWire Team <info@signalwire.com>
6
6
  License: MIT
@@ -1,9 +1,9 @@
1
- signalwire_agents/__init__.py,sha256=VjQ0bhIJ_R2wvxwnWiIyZzzGP7hVFpdeOoAgEuhZ_gg,5031
1
+ signalwire_agents/__init__.py,sha256=Y3xbhZP88Av8BKn1LIevDlrYWfFDg773OjLTcMBxAvo,5031
2
2
  signalwire_agents/agent_server.py,sha256=x9HyWia8D3r6KMqY-Q4DtNVivfJWLTx8B-KzUI8okuA,26880
3
3
  signalwire_agents/schema.json,sha256=6-7ccbt39iM1CO36dOfvupRPfd0gnQ0XoAdyo-EFyjo,238042
4
4
  signalwire_agents/agents/bedrock.py,sha256=J582gooNtxtep4xdVOfyDzRtHp_XrurPMS93xf2Xod0,10836
5
5
  signalwire_agents/cli/__init__.py,sha256=XbxAQFaCIdGXIXJiriVBWoFPOJsC401u21588nO4TG8,388
6
- signalwire_agents/cli/build_search.py,sha256=UXKyW27Xr-gGO7ooztMLb3YD_tEdJO6ptDSIUGgwZ0w,32035
6
+ signalwire_agents/cli/build_search.py,sha256=Yh5hNM0ur88UMuKo5ZDoN_bAzBGpj2RG1Ys1_3xlfUc,54144
7
7
  signalwire_agents/cli/config.py,sha256=2i4e0BArdKsaXxjeueYYRNke7GWicHPYC2wuitVrP7A,2541
8
8
  signalwire_agents/cli/swaig_test_wrapper.py,sha256=t63HQpEc1Up5AcysEHP1OsEQcgSMKH-9H1L2IhFso18,1533
9
9
  signalwire_agents/cli/test_swaig.py,sha256=-v-XjTUWZNxmMJuOF5_cB1Jz8x8emJoqgqS_8jLeT4Y,31487
@@ -69,13 +69,15 @@ signalwire_agents/prefabs/faq_bot.py,sha256=cUuHhnDB8S4aVg-DiQe4jBmCAPrYQrND_Mff
69
69
  signalwire_agents/prefabs/info_gatherer.py,sha256=0LpYTaU7C76Efp3yUIdNX6xzWH7mj5pBYewilpkCL5g,15189
70
70
  signalwire_agents/prefabs/receptionist.py,sha256=em0uk_F0tmePvzE6Hi9HFlL3MHChH0RaHHqSvww9pK0,10323
71
71
  signalwire_agents/prefabs/survey.py,sha256=a-0-xAnQYhdX4Lzgyna14lpNfaCV-rLUFkQF6QOCQAY,14534
72
- signalwire_agents/search/__init__.py,sha256=x7saU_MDbhoOIzcvCT1-gnqyH2rrMpzB4ZUqk-av-lI,3958
73
- signalwire_agents/search/document_processor.py,sha256=Q6EDRu9GKJkWLeJREvppAuxuZ5wSiZXoolp7CgKK2f4,47941
74
- signalwire_agents/search/index_builder.py,sha256=bjctP8SVG8QMIJyiqRnjZEi_OKohx2jyuk2vneERslk,29276
75
- signalwire_agents/search/pgvector_backend.py,sha256=7OerJvzCGQigbb_RnV2M5PEOHR2EUMBn4n2bHML08I0,19172
76
- signalwire_agents/search/query_processor.py,sha256=WMm_jjArQ6-Jpy0Cc0sUI4saidOtDRKx_XLv0qi3N3k,16739
77
- signalwire_agents/search/search_engine.py,sha256=rGRTs8qRX4biXhsOg7jnt6YvoetoN_KG3ByKwtX7h6o,16635
78
- signalwire_agents/search/search_service.py,sha256=lni6tRFonv0EHNpSI2gsdoCrSi9D3Khxi_wLy9X3JyY,18310
72
+ signalwire_agents/search/__init__.py,sha256=cb8Rtg4Jut9ZhuSbiaHl79G0iWgMhJkLu84urkY0lRc,4215
73
+ signalwire_agents/search/document_processor.py,sha256=zDih-xBWq2kEQkQblh4xXZE1uIQ9aBBFLjjsi3CYfRU,48196
74
+ signalwire_agents/search/index_builder.py,sha256=v1LGhbzzKlCilO4g6nqQJVYEAWvInP2j5B1QrAEj4V8,33772
75
+ signalwire_agents/search/migration.py,sha256=UZPrpUOMZeLVNO1cEDp3tnZYG6ys8-VCFlZXmzig_E0,16582
76
+ signalwire_agents/search/models.py,sha256=isYOYwQT0eWCVdcYSSd8w6z2gFYUobtC8BAUhV7FUVI,840
77
+ signalwire_agents/search/pgvector_backend.py,sha256=waneT_cBmWGE79kpN5Ie4ax6VDuyWb7QmjzXzGFjY7w,28400
78
+ signalwire_agents/search/query_processor.py,sha256=qpUFQqxobx8IymcXBTdPUirfawZVCKtRdSlMHw3nA0s,19656
79
+ signalwire_agents/search/search_engine.py,sha256=dHwhAknIw0paAVQx3ccgVOkaiFqMpDOAriH8lQrBYm8,55671
80
+ signalwire_agents/search/search_service.py,sha256=KV0luon18gPBcoyQ8L_Lagi1CgkJSOwGWshpCjvj1ks,20360
79
81
  signalwire_agents/skills/README.md,sha256=sM1_08IsKdRDCzYHPLzppJbaK5MvRelsVL6Kd9A9Ubo,12193
80
82
  signalwire_agents/skills/__init__.py,sha256=9AMEcyk2tDaGiUjwVIson_tVWxV4oU_2NnGGNTbHuyQ,533
81
83
  signalwire_agents/skills/registry.py,sha256=zURdeAaccZyUSwLRl8K4ILXICMV3ouYQIA4rUBq6b5E,20792
@@ -102,7 +104,7 @@ signalwire_agents/skills/mcp_gateway/__init__.py,sha256=zLgOa7s0sIQphTNJjvasIAW7
102
104
  signalwire_agents/skills/mcp_gateway/skill.py,sha256=rtXs8CayjWH8WOrpjGMbbG11dJCNK2RUA06Ysc1cK8g,17167
103
105
  signalwire_agents/skills/native_vector_search/README.md,sha256=eFVRoDwZlZwbBXUKyKrvfC6AL4T8MXj0B-IgIdBZF70,5526
104
106
  signalwire_agents/skills/native_vector_search/__init__.py,sha256=RofpN3Sd-vyWeUCTYH2dRVrl7h6YuyG5OK772UQ-KFk,220
105
- signalwire_agents/skills/native_vector_search/skill.py,sha256=OK1dMRG04iaqKS-wuOJh4ye8Vkw4gmrU7aHGLUOtl_M,26890
107
+ signalwire_agents/skills/native_vector_search/skill.py,sha256=ltIFXwxxyqYkTSG2exUsSuJB4tOAiTwYrG7PYtVDCJw,36208
106
108
  signalwire_agents/skills/play_background_file/README.md,sha256=omJ_jY5Co6Mk-gJt_hoSl40wemmTbzae3DBll6HL0B4,7026
107
109
  signalwire_agents/skills/play_background_file/__init__.py,sha256=iETc6e-0Cai3RUTQWhg9BieWi3NF3_DWWBKdYXcd4ok,273
108
110
  signalwire_agents/skills/play_background_file/skill.py,sha256=HgPc2FIvXKJHZ7gO2QEzQe6-uUBPrw_6sRJJpU83GTY,8822
@@ -128,9 +130,9 @@ signalwire_agents/utils/token_generators.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663
128
130
  signalwire_agents/utils/validators.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663YjFX_PumJ35Xds,191
129
131
  signalwire_agents/web/__init__.py,sha256=XE_pSTY9Aalzr7J7wqFth1Zr3cccQHPPcF5HWNrOpz8,383
130
132
  signalwire_agents/web/web_service.py,sha256=a2PSHJgX1tlZr0Iz1A1UouZjXEePJAZL632evvLVM38,21071
131
- signalwire_agents-0.1.47.dist-info/licenses/LICENSE,sha256=NYvAsB-rTcSvG9cqHt9EUHAWLiA9YzM4Qfz-mPdvDR0,1067
132
- signalwire_agents-0.1.47.dist-info/METADATA,sha256=-QAbmvCBQGH41IiHCUu8OKpWp3prVNguEf0xeITLPwY,41596
133
- signalwire_agents-0.1.47.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
134
- signalwire_agents-0.1.47.dist-info/entry_points.txt,sha256=ZDT65zfTO_YyDzi_hwQbCxIhrUfu_t8RpNXMMXlUPWI,144
135
- signalwire_agents-0.1.47.dist-info/top_level.txt,sha256=kDGS6ZYv84K9P5Kyg9_S8P_pbUXoHkso0On_DB5bbWc,18
136
- signalwire_agents-0.1.47.dist-info/RECORD,,
133
+ signalwire_agents-0.1.48.dist-info/licenses/LICENSE,sha256=NYvAsB-rTcSvG9cqHt9EUHAWLiA9YzM4Qfz-mPdvDR0,1067
134
+ signalwire_agents-0.1.48.dist-info/METADATA,sha256=OA3_K04wu-qQvWV6uhzRnfAn7HWuf_lvIE_R_7y_fI8,41596
135
+ signalwire_agents-0.1.48.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
136
+ signalwire_agents-0.1.48.dist-info/entry_points.txt,sha256=ZDT65zfTO_YyDzi_hwQbCxIhrUfu_t8RpNXMMXlUPWI,144
137
+ signalwire_agents-0.1.48.dist-info/top_level.txt,sha256=kDGS6ZYv84K9P5Kyg9_S8P_pbUXoHkso0On_DB5bbWc,18
138
+ signalwire_agents-0.1.48.dist-info/RECORD,,