signalwire-agents 0.1.10__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.
Files changed (46) hide show
  1. signalwire_agents/__init__.py +43 -4
  2. signalwire_agents/agent_server.py +268 -15
  3. signalwire_agents/cli/__init__.py +9 -0
  4. signalwire_agents/cli/build_search.py +457 -0
  5. signalwire_agents/cli/test_swaig.py +2609 -0
  6. signalwire_agents/core/agent_base.py +691 -82
  7. signalwire_agents/core/contexts.py +289 -0
  8. signalwire_agents/core/data_map.py +499 -0
  9. signalwire_agents/core/function_result.py +57 -10
  10. signalwire_agents/core/logging_config.py +232 -0
  11. signalwire_agents/core/skill_base.py +27 -37
  12. signalwire_agents/core/skill_manager.py +89 -23
  13. signalwire_agents/core/swaig_function.py +13 -1
  14. signalwire_agents/core/swml_handler.py +37 -13
  15. signalwire_agents/core/swml_service.py +37 -28
  16. signalwire_agents/search/__init__.py +131 -0
  17. signalwire_agents/search/document_processor.py +764 -0
  18. signalwire_agents/search/index_builder.py +534 -0
  19. signalwire_agents/search/query_processor.py +371 -0
  20. signalwire_agents/search/search_engine.py +383 -0
  21. signalwire_agents/search/search_service.py +251 -0
  22. signalwire_agents/skills/datasphere/__init__.py +12 -0
  23. signalwire_agents/skills/datasphere/skill.py +229 -0
  24. signalwire_agents/skills/datasphere_serverless/__init__.py +1 -0
  25. signalwire_agents/skills/datasphere_serverless/skill.py +156 -0
  26. signalwire_agents/skills/datetime/skill.py +9 -5
  27. signalwire_agents/skills/joke/__init__.py +1 -0
  28. signalwire_agents/skills/joke/skill.py +88 -0
  29. signalwire_agents/skills/math/skill.py +9 -6
  30. signalwire_agents/skills/native_vector_search/__init__.py +1 -0
  31. signalwire_agents/skills/native_vector_search/skill.py +352 -0
  32. signalwire_agents/skills/registry.py +10 -4
  33. signalwire_agents/skills/web_search/skill.py +57 -21
  34. signalwire_agents/skills/wikipedia/__init__.py +9 -0
  35. signalwire_agents/skills/wikipedia/skill.py +180 -0
  36. signalwire_agents/utils/__init__.py +14 -0
  37. signalwire_agents/utils/schema_utils.py +111 -44
  38. signalwire_agents-0.1.12.dist-info/METADATA +863 -0
  39. signalwire_agents-0.1.12.dist-info/RECORD +67 -0
  40. {signalwire_agents-0.1.10.dist-info → signalwire_agents-0.1.12.dist-info}/WHEEL +1 -1
  41. signalwire_agents-0.1.12.dist-info/entry_points.txt +3 -0
  42. signalwire_agents-0.1.10.dist-info/METADATA +0 -319
  43. signalwire_agents-0.1.10.dist-info/RECORD +0 -44
  44. {signalwire_agents-0.1.10.data → signalwire_agents-0.1.12.data}/data/schema.json +0 -0
  45. {signalwire_agents-0.1.10.dist-info → signalwire_agents-0.1.12.dist-info}/licenses/LICENSE +0 -0
  46. {signalwire_agents-0.1.10.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
@@ -11,18 +11,19 @@ import os
11
11
  import importlib
12
12
  import importlib.util
13
13
  import inspect
14
+ import sys
14
15
  from typing import Dict, List, Type, Optional
15
16
  from pathlib import Path
16
- import logging
17
17
 
18
18
  from signalwire_agents.core.skill_base import SkillBase
19
+ from signalwire_agents.core.logging_config import get_logger
19
20
 
20
21
  class SkillRegistry:
21
22
  """Global registry for discovering and managing skills"""
22
23
 
23
24
  def __init__(self):
24
25
  self._skills: Dict[str, Type[SkillBase]] = {}
25
- self.logger = logging.getLogger("skill_registry")
26
+ self.logger = get_logger("skill_registry")
26
27
  self._discovered = False
27
28
 
28
29
  def discover_skills(self) -> None:
@@ -39,7 +40,11 @@ class SkillRegistry:
39
40
  self._load_skill_from_directory(item)
40
41
 
41
42
  self._discovered = True
42
- self.logger.info(f"Discovered {len(self._skills)} skills")
43
+
44
+ # Check if we're in raw mode (used by swaig-test --raw) and suppress logging
45
+ is_raw_mode = "--raw" in sys.argv
46
+ if not is_raw_mode:
47
+ self.logger.info(f"Discovered {len(self._skills)} skills")
43
48
 
44
49
  def _load_skill_from_directory(self, skill_dir: Path) -> None:
45
50
  """Load a skill from a directory"""
@@ -89,7 +94,8 @@ class SkillRegistry:
89
94
  "description": skill_class.SKILL_DESCRIPTION,
90
95
  "version": skill_class.SKILL_VERSION,
91
96
  "required_packages": skill_class.REQUIRED_PACKAGES,
92
- "required_env_vars": skill_class.REQUIRED_ENV_VARS
97
+ "required_env_vars": skill_class.REQUIRED_ENV_VARS,
98
+ "supports_multiple_instances": skill_class.SUPPORTS_MULTIPLE_INSTANCES
93
99
  }
94
100
  for skill_class in self._skills.values()
95
101
  ]
@@ -21,9 +21,10 @@ from signalwire_agents.core.function_result import SwaigFunctionResult
21
21
  class GoogleSearchScraper:
22
22
  """Google Search and Web Scraping functionality"""
23
23
 
24
- def __init__(self, api_key: str, search_engine_id: str):
24
+ def __init__(self, api_key: str, search_engine_id: str, max_content_length: int = 2000):
25
25
  self.api_key = api_key
26
26
  self.search_engine_id = search_engine_id
27
+ self.max_content_length = max_content_length
27
28
  self.session = requests.Session()
28
29
  self.session.headers.update({
29
30
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
@@ -81,8 +82,8 @@ class GoogleSearchScraper:
81
82
  text = ' '.join(chunk for chunk in chunks if chunk)
82
83
 
83
84
  # Limit text length
84
- if len(text) > 2000:
85
- text = text[:2000] + "... [Content truncated]"
85
+ if len(text) > self.max_content_length:
86
+ text = text[:self.max_content_length] + "... [Content truncated]"
86
87
 
87
88
  return text
88
89
 
@@ -128,29 +129,63 @@ class WebSearchSkill(SkillBase):
128
129
  SKILL_DESCRIPTION = "Search the web for information using Google Custom Search API"
129
130
  SKILL_VERSION = "1.0.0"
130
131
  REQUIRED_PACKAGES = ["bs4", "requests"]
131
- REQUIRED_ENV_VARS = ["GOOGLE_SEARCH_API_KEY", "GOOGLE_SEARCH_ENGINE_ID"]
132
+ REQUIRED_ENV_VARS = [] # No required env vars since all config comes from params
133
+
134
+ # Enable multiple instances support
135
+ SUPPORTS_MULTIPLE_INSTANCES = True
136
+
137
+ def get_instance_key(self) -> str:
138
+ """
139
+ Get the key used to track this skill instance
140
+
141
+ For web search, we use the search_engine_id to differentiate instances
142
+ """
143
+ search_engine_id = self.params.get('search_engine_id', 'default')
144
+ tool_name = self.params.get('tool_name', 'web_search')
145
+ return f"{self.SKILL_NAME}_{search_engine_id}_{tool_name}"
132
146
 
133
147
  def setup(self) -> bool:
134
148
  """Setup the web search skill"""
135
- if not self.validate_env_vars() or not self.validate_packages():
149
+ # Validate required parameters
150
+ required_params = ['api_key', 'search_engine_id']
151
+ missing_params = [param for param in required_params if not self.params.get(param)]
152
+ if missing_params:
153
+ self.logger.error(f"Missing required parameters: {missing_params}")
154
+ return False
155
+
156
+ if not self.validate_packages():
136
157
  return False
137
158
 
159
+ # Set parameters from config
160
+ self.api_key = self.params['api_key']
161
+ self.search_engine_id = self.params['search_engine_id']
162
+
138
163
  # Set default parameters
139
164
  self.default_num_results = self.params.get('num_results', 1)
140
165
  self.default_delay = self.params.get('delay', 0)
166
+ self.max_content_length = self.params.get('max_content_length', 2000)
167
+ self.no_results_message = self.params.get('no_results_message',
168
+ "I couldn't find any results for '{query}'. "
169
+ "This might be due to a very specific query or temporary issues. "
170
+ "Try rephrasing your search or asking about a different topic."
171
+ )
172
+
173
+ # Tool name (for multiple instances)
174
+ self.tool_name = self.params.get('tool_name', 'web_search')
141
175
 
142
176
  # Initialize the search scraper
143
177
  self.search_scraper = GoogleSearchScraper(
144
- api_key=os.getenv('GOOGLE_SEARCH_API_KEY'),
145
- search_engine_id=os.getenv('GOOGLE_SEARCH_ENGINE_ID')
178
+ api_key=self.api_key,
179
+ search_engine_id=self.search_engine_id,
180
+ max_content_length=self.max_content_length
146
181
  )
147
182
 
148
183
  return True
149
184
 
150
185
  def register_tools(self) -> None:
151
186
  """Register web search tool with the agent"""
152
- self.define_tool_with_swaig_fields(
153
- name="web_search",
187
+ self.agent.define_tool(
188
+ name=self.tool_name,
154
189
  description="Search the web for information on any topic and return detailed results with content from multiple sources",
155
190
  parameters={
156
191
  "query": {
@@ -158,7 +193,8 @@ class WebSearchSkill(SkillBase):
158
193
  "description": "The search query - what you want to find information about"
159
194
  }
160
195
  },
161
- handler=self._web_search_handler
196
+ handler=self._web_search_handler,
197
+ **self.swaig_fields
162
198
  )
163
199
 
164
200
  def _web_search_handler(self, args, raw_data):
@@ -184,11 +220,9 @@ class WebSearchSkill(SkillBase):
184
220
  )
185
221
 
186
222
  if not search_results or "No search results found" in search_results:
187
- return SwaigFunctionResult(
188
- f"I couldn't find any results for '{query}'. "
189
- "This might be due to a very specific query or temporary issues. "
190
- "Try rephrasing your search or asking about a different topic."
191
- )
223
+ # Format the no results message with the query if it contains a placeholder
224
+ formatted_message = self.no_results_message.format(query=query) if '{query}' in self.no_results_message else self.no_results_message
225
+ return SwaigFunctionResult(formatted_message)
192
226
 
193
227
  response = f"I found {num_results} results for '{query}':\n\n{search_results}"
194
228
  return SwaigFunctionResult(response)
@@ -201,10 +235,12 @@ class WebSearchSkill(SkillBase):
201
235
 
202
236
  def get_hints(self) -> List[str]:
203
237
  """Return speech recognition hints"""
204
- return [
205
- "Google", "search", "internet", "web", "information",
206
- "find", "look up", "research", "query", "results"
207
- ]
238
+ # Currently no hints provided, but you could add them like:
239
+ # return [
240
+ # "Google", "search", "internet", "web", "information",
241
+ # "find", "look up", "research", "query", "results"
242
+ # ]
243
+ return []
208
244
 
209
245
  def get_global_data(self) -> Dict[str, Any]:
210
246
  """Return global data for agent context"""
@@ -218,9 +254,9 @@ class WebSearchSkill(SkillBase):
218
254
  return [
219
255
  {
220
256
  "title": "Web Search Capability",
221
- "body": "You can search the internet for current, accurate information on any topic.",
257
+ "body": f"You can search the internet for current, accurate information on any topic using the {self.tool_name} tool.",
222
258
  "bullets": [
223
- "Use the web_search tool when users ask for information you need to look up",
259
+ f"Use the {self.tool_name} tool when users ask for information you need to look up",
224
260
  "Search for news, current events, product information, or any current data",
225
261
  "Summarize search results in a clear, helpful way",
226
262
  "Include relevant URLs so users can read more if interested"
@@ -0,0 +1,9 @@
1
+ """
2
+ Wikipedia Search Skill
3
+
4
+ This skill provides Wikipedia search capabilities using the Wikipedia API.
5
+ """
6
+
7
+ from .skill import WikipediaSearchSkill
8
+
9
+ __all__ = ['WikipediaSearchSkill']