Skip to content

Commit ac76d1b

Browse files
feat(search): Semantic Tool Search (#149)
* Senamtic Search on action in Python AI SDK * Filter tools based on the SDK auth config and connector * Use the local benchmark from the ai-generations * Add Semantinc search bench mark with local benchmarks * Fix CI lint errors * Fix the lint in the benchmark file * Formalise the docs and code * Keep semantic search minimal in the README * Remove the old benchmark data * implement PR feedback suggestions from cubic * fix nullable in the semantic tool schema * limit override * handle per connector calls to avoid the guesswork * simplify utility_tools API by inferring semantic search from client presence * Benchmark update and PR suggestions * update the README gst * Note on the fetch tools for actions that user expect to discover * Update examples and improve the semantic seach * Fix ruff issues * Document the semantic search feature in the python files and example * Respect the backend results unless top_k specified explicitly, add python only crewAI example * move the crewAI tools conversation back in the example * CI Trigger * Fix unit tests with updated top_k behavior * Update PR with correct approach mentioned in the PR comments * Update example and remove unwated crewai examples * Remove the crewai reference from the README * fix(semantic-search): scope tool_search to user's linked connectors When utility_tools(semantic_client=...) is used, tool_search now searches only the connectors available in the fetched tools collection instead of the full StackOne catalog. This prevents agents from discovering tools they cannot execute. - Add available_connectors param to create_semantic_tool_search - Pass connectors from Tools.utility_tools() to scope searches - Update docs, examples, and README to reflect scoping - Add 4 new tests for scoping behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix the Ruff CI issue * Add back creai intefration and test integration * Remove the sematic search example from the tools * Semantic Search * Cubic suggestions * Optinally support project_ids in the SDK search * Update the client to use PR suggested client and use min_similarity from the server * CI Fix * Implement PR sugggestions and use the search and execute tools as standard way * update example docs * Update SDK as per PR suggestions * Fix available connector early return * Fix semantic search creation in fetch tools * Fix semantic search creation in fetch tools revert back to lazy * get rid of the utility tools completely as discussed * Remove the reference of the semantic search * Fix CI and lint issues * Pass semantic Client to the toolset * Add the search modes for local, semantic and auto with example * Impement PR suggetion and add the salesforce example rather than hris. Brif document the search modes * Remove unified categoried from the README and docs * Remove the unified category reference from CLAUDE.md * Refactor duplicate use Stackone API url and update tests * Fix CI issues * CI Only: skip guard fix and the timeout handling * CI Only: skip guard fix and the timeout handling * CI Only: ruff E501 * refactor search to make it aligned to the defender and future * Fix CI * CI Only: Ruff fix on toolset.py * Fix ty checks * Add topk example and add search default ---------
1 parent 576e2fb commit ac76d1b

21 files changed

Lines changed: 2771 additions & 923 deletions

CLAUDE.md

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,24 +51,13 @@ StackOne AI SDK is a Python library that provides a unified interface for access
5151
- `Tools`: Container for managing multiple tools
5252
- Format converters for different AI frameworks
5353

54-
3. **OpenAPI Parser** (`stackone_ai/specs/parser.py`): Spec conversion
55-
- Converts OpenAPI specs to tool definitions
56-
- Handles file upload detection (`format: binary``type: file`)
57-
- Resolves schema references
58-
59-
### OpenAPI Specifications
60-
61-
All tool definitions are generated from OpenAPI specs in `stackone_ai/oas/`:
62-
63-
- `core.json`, `ats.json`, `crm.json`, `documents.json`, `hris.json`, `iam.json`, `lms.json`, `marketing.json`
64-
6554
## Key Development Patterns
6655

6756
### Tool Filtering
6857

6958
```python
7059
# Use glob patterns for tool selection
71-
tools = StackOneToolSet(include_tools=["hris_*", "!hris_create_*"])
60+
tools = StackOneToolSet(include_tools=["bamboohr_*", "!bamboohr_create_*"])
7261
```
7362

7463
### Authentication

README.md

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ StackOne AI provides a unified interface for accessing various SaaS tools throug
1616
- **Tool Calling**: Direct method calling with `tool.call()` for intuitive usage
1717
- **MCP-backed Dynamic Discovery**: Fetch tools at runtime via `fetch_tools()` with provider, action, and account filtering
1818
- **Advanced Tool Filtering**:
19-
- Glob pattern filtering with patterns like `"hris_*"` and exclusions `"!hris_delete_*"`
19+
- Glob pattern filtering with patterns like `"salesforce_*"` and exclusions `"!*_delete_*"`
2020
- Provider and action filtering
2121
- Multi-account support
22-
- **Utility Tools** (Beta): Dynamic tool discovery and execution based on natural language queries
22+
- **Semantic Search**: AI-powered tool discovery using natural language queries
23+
- **Search Tool**: Callable tool discovery for agent loops via `get_search_tool()`
2324
- Integration with popular AI frameworks:
2425
- OpenAI Functions
2526
- LangChain Tools
@@ -58,10 +59,10 @@ toolset = StackOneToolSet() # Uses STACKONE_API_KEY env var
5859
# Or explicitly: toolset = StackOneToolSet(api_key="your-api-key")
5960

6061
# Get HRIS-related tools with glob patterns
61-
tools = toolset.fetch_tools(actions=["hris_*"], account_ids=["your-account-id"])
62+
tools = toolset.fetch_tools(actions=["bamboohr_*"], account_ids=["your-account-id"])
6263

6364
# Use a specific tool with the call method
64-
employee_tool = tools.get_tool("hris_get_employee")
65+
employee_tool = tools.get_tool("bamboohr_get_employee")
6566
# Call with keyword arguments
6667
employee = employee_tool.call(id="employee-id")
6768
# Or with traditional execute method
@@ -107,9 +108,9 @@ tools = toolset.fetch_tools(providers=["hibob"])
107108
- **`account_ids`**: Filter tools by account IDs. Tools will be loaded for each specified account.
108109
- **`providers`**: Filter by provider names (e.g., `["hibob", "bamboohr"]`). Case-insensitive matching.
109110
- **`actions`**: Filter by action patterns with glob support:
110-
- Exact match: `["hris_list_employees"]`
111+
- Exact match: `["bamboohr_list_employees"]`
111112
- Glob pattern: `["*_list_employees"]` matches all tools ending with `_list_employees`
112-
- Provider prefix: `["hris_*"]` matches all HRIS tools
113+
- Provider prefix: `["bamboohr_*"]` matches all BambooHR tools
113114

114115
## Implicit Feedback (Beta)
115116

@@ -169,7 +170,7 @@ from stackone_ai import StackOneToolSet
169170

170171
# Initialize StackOne tools
171172
toolset = StackOneToolSet()
172-
tools = toolset.fetch_tools(actions=["hris_*"], account_ids=["your-account-id"])
173+
tools = toolset.fetch_tools(actions=["bamboohr_*"], account_ids=["your-account-id"])
173174

174175
# Convert to LangChain format
175176
langchain_tools = tools.to_langchain()
@@ -216,7 +217,7 @@ from stackone_ai.integrations.langgraph import to_tool_node, bind_model_with_too
216217

217218
# Prepare tools
218219
toolset = StackOneToolSet()
219-
tools = toolset.fetch_tools(actions=["hris_*"], account_ids=["your-account-id"])
220+
tools = toolset.fetch_tools(actions=["bamboohr_*"], account_ids=["your-account-id"])
220221
langchain_tools = tools.to_langchain()
221222

222223
class State(TypedDict):
@@ -254,7 +255,7 @@ from stackone_ai import StackOneToolSet
254255

255256
# Get tools and convert to LangChain format
256257
toolset = StackOneToolSet()
257-
tools = toolset.fetch_tools(actions=["hris_*"], account_ids=["your-account-id"])
258+
tools = toolset.fetch_tools(actions=["bamboohr_*"], account_ids=["your-account-id"])
258259
langchain_tools = tools.to_langchain()
259260

260261
# Create CrewAI agent with StackOne tools
@@ -296,7 +297,7 @@ feedback_tool = tools.get_tool("tool_feedback")
296297
result = feedback_tool.call(
297298
feedback="The HRIS tools are working great! Very fast response times.",
298299
account_id="acc_123456",
299-
tool_names=["hris_list_employees", "hris_get_employee"]
300+
tool_names=["bamboohr_list_employees", "bamboohr_get_employee"]
300301
)
301302
```
302303

@@ -305,26 +306,59 @@ result = feedback_tool.call(
305306
- "Are you ok with sending feedback to StackOne? The LLM will take care of sending it."
306307
- Only call the tool after the user explicitly agrees.
307308

308-
## Utility Tools (Beta)
309+
## Search Tool
309310

310-
Utility tools enable dynamic tool discovery and execution without hardcoding tool names.
311+
Search for tools using natural language queries. Works with both semantic (cloud) and local BM25+TF-IDF search.
311312

312313
### Basic Usage
313314

314315
```python
315-
# Get utility tools for dynamic discovery
316-
tools = toolset.fetch_tools(actions=["hris_*"])
317-
utility_tools = tools.utility_tools()
316+
# Get a callable search tool
317+
toolset = StackOneToolSet()
318+
all_tools = toolset.fetch_tools(account_ids=["your-account-id"])
319+
search_tool = toolset.get_search_tool()
318320

319-
# Search for relevant tools using natural language
320-
filter_tool = utility_tools.get_tool("tool_search")
321-
results = filter_tool.call(query="manage employees", limit=5)
321+
# Search for relevant tools — returns a Tools collection
322+
tools = search_tool("manage employees", top_k=5)
322323

323-
# Execute discovered tools dynamically
324-
execute_tool = utility_tools.get_tool("tool_execute")
325-
result = execute_tool.call(toolName="hris_list_employees", params={"limit": 10})
324+
# Execute a discovered tool directly
325+
tools[0](limit=10)
326326
```
327327

328+
## Semantic Search
329+
330+
Discover tools using natural language instead of exact names. Queries like "onboard new hire" resolve to the right actions even when the tool is called `bamboohr_create_employee`.
331+
332+
```python
333+
from stackone_ai import StackOneToolSet
334+
335+
toolset = StackOneToolSet()
336+
337+
# Search by intent — returns Tools collection ready for any framework
338+
tools = toolset.search_tools("manage employee records", account_ids=["your-account-id"], top_k=5)
339+
openai_tools = tools.to_openai()
340+
341+
# Lightweight: inspect results without fetching full tool definitions
342+
results = toolset.search_action_names("time off requests", top_k=5)
343+
```
344+
345+
### Search Modes
346+
347+
Control which search backend `search_tools()` uses via the `search` parameter:
348+
349+
```python
350+
# "auto" (default) — tries semantic search first, falls back to local
351+
tools = toolset.search_tools("manage employees", search="auto")
352+
353+
# "semantic" — semantic API only, raises if unavailable
354+
tools = toolset.search_tools("manage employees", search="semantic")
355+
356+
# "local" — local BM25+TF-IDF only, no semantic API call
357+
tools = toolset.search_tools("manage employees", search="local")
358+
```
359+
360+
Results are automatically scoped to connectors in your linked accounts. See [Semantic Search Example](examples/semantic_search_example.py) for `SearchTool` (`get_search_tool`) integration, OpenAI, and LangChain patterns.
361+
328362
## Examples
329363

330364
For more examples, check out the [examples/](examples/) directory:
@@ -334,7 +368,8 @@ For more examples, check out the [examples/](examples/) directory:
334368
- [OpenAI Integration](examples/openai_integration.py)
335369
- [LangChain Integration](examples/langchain_integration.py)
336370
- [CrewAI Integration](examples/crewai_integration.py)
337-
- [Utility Tools](examples/utility_tools_example.py)
371+
- [Search Tool](examples/search_tool_example.py)
372+
- [Semantic Search](examples/semantic_search_example.py)
338373

339374
## Development
340375

0 commit comments

Comments
 (0)