Skip to content
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
c7ad71f
Senamtic Search on action in Python AI SDK
shashi-stackone Feb 18, 2026
0210c1f
Filter tools based on the SDK auth config and connector
shashi-stackone Feb 18, 2026
b1105fa
Use the local benchmark from the ai-generations
shashi-stackone Feb 18, 2026
d49f52b
Add Semantinc search bench mark with local benchmarks
shashi-stackone Feb 18, 2026
680fa8e
Fix CI lint errors
shashi-stackone Feb 18, 2026
1ee842b
Fix the lint in the benchmark file
shashi-stackone Feb 18, 2026
d6fba69
Formalise the docs and code
shashi-stackone Feb 18, 2026
3eb0641
Keep semantic search minimal in the README
shashi-stackone Feb 18, 2026
fd37d93
Remove the old benchmark data
shashi-stackone Feb 18, 2026
f5ef955
implement PR feedback suggestions from cubic
shashi-stackone Feb 18, 2026
b7b522f
fix nullable in the semantic tool schema
shashi-stackone Feb 18, 2026
e9c6b86
limit override
shashi-stackone Feb 18, 2026
34e1ca6
handle per connector calls to avoid the guesswork
shashi-stackone Feb 18, 2026
82082cb
simplify utility_tools API by inferring semantic search from client p…
shashi-stackone Feb 18, 2026
8a74517
Benchmark update and PR suggestions
shashi-stackone Feb 18, 2026
85b0395
update the README gst
shashi-stackone Feb 18, 2026
79c762a
Note on the fetch tools for actions that user expect to discover
shashi-stackone Feb 18, 2026
6ee1adf
Update examples and improve the semantic seach
shashi-stackone Feb 18, 2026
7a65367
Fix ruff issues
shashi-stackone Feb 18, 2026
64a0a60
Document the semantic search feature in the python files and example
shashi-stackone Feb 18, 2026
4083642
Respect the backend results unless top_k specified explicitly, add py…
shashi-stackone Feb 18, 2026
b926db1
move the crewAI tools conversation back in the example
shashi-stackone Feb 18, 2026
d2dd2f5
CI Trigger
shashi-stackone Feb 18, 2026
719b391
Fix unit tests with updated top_k behavior
shashi-stackone Feb 18, 2026
b360b00
Update PR with correct approach mentioned in the PR comments
shashi-stackone Feb 18, 2026
7b77f33
Update example and remove unwated crewai examples
shashi-stackone Feb 18, 2026
bab931b
Remove the crewai reference from the README
shashi-stackone Feb 19, 2026
d62943d
fix(semantic-search): scope tool_search to user's linked connectors
shashi-stackone Feb 19, 2026
5eaa3c5
Fix the Ruff CI issue
shashi-stackone Feb 19, 2026
173121d
Add back creai intefration and test integration
shashi-stackone Feb 19, 2026
1e4cc9a
Remove the sematic search example from the tools
shashi-stackone Feb 19, 2026
f1db9f2
Merge branch 'main' into semantic_search
shashi-stackone Feb 19, 2026
a87fa00
Semantic Search
shashi-stackone Feb 19, 2026
c9c0358
Cubic suggestions
shashi-stackone Feb 19, 2026
71457af
Optinally support project_ids in the SDK search
shashi-stackone Feb 19, 2026
5bf1cc6
Update the client to use PR suggested client and use min_similarity f…
shashi-stackone Feb 23, 2026
9fe1e40
CI Fix
shashi-stackone Feb 23, 2026
010a275
Implement PR sugggestions and use the search and execute tools as sta…
shashi-stackone Feb 24, 2026
8137538
update example docs
shashi-stackone Feb 24, 2026
90d8aa3
Update SDK as per PR suggestions
shashi-stackone Feb 25, 2026
3d6000f
Fix available connector early return
shashi-stackone Feb 25, 2026
a0dd833
Fix semantic search creation in fetch tools
shashi-stackone Feb 26, 2026
86c9c64
Fix semantic search creation in fetch tools revert back to lazy
shashi-stackone Feb 26, 2026
ce3443c
get rid of the utility tools completely as discussed
shashi-stackone Feb 26, 2026
53828e5
Remove the reference of the semantic search
shashi-stackone Feb 27, 2026
b8b331a
Fix CI and lint issues
shashi-stackone Feb 27, 2026
f6920c8
Pass semantic Client to the toolset
shashi-stackone Feb 27, 2026
4785d87
Add the search modes for local, semantic and auto with example
shashi-stackone Mar 2, 2026
34c8bc9
Impement PR suggetion and add the salesforce example rather than hris…
shashi-stackone Mar 2, 2026
0bc6ee0
Remove unified categoried from the README and docs
shashi-stackone Mar 2, 2026
6017b44
Remove the unified category reference from CLAUDE.md
shashi-stackone Mar 2, 2026
bf508b0
Refactor duplicate use Stackone API url and update tests
shashi-stackone Mar 3, 2026
ea74fe2
Fix CI issues
shashi-stackone Mar 3, 2026
1f51bf0
CI Only: skip guard fix and the timeout handling
shashi-stackone Mar 3, 2026
d930406
CI Only: skip guard fix and the timeout handling
shashi-stackone Mar 3, 2026
741fdf6
CI Only: ruff E501
shashi-stackone Mar 3, 2026
4109108
refactor search to make it aligned to the defender and future
shashi-stackone Mar 5, 2026
4eddc74
Fix CI
shashi-stackone Mar 5, 2026
ff01d34
CI Only: Ruff fix on toolset.py
shashi-stackone Mar 5, 2026
8e8e2e3
Fix ty checks
shashi-stackone Mar 5, 2026
532ac23
Add topk example and add search default
shashi-stackone Mar 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 33 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ StackOne AI provides a unified interface for accessing various SaaS tools throug
- Glob pattern filtering with patterns like `"hris_*"` and exclusions `"!hris_delete_*"`
- Provider and action filtering
- Multi-account support
- **Utility Tools** (Beta): Dynamic tool discovery and execution based on natural language queries
- **Semantic Search**: AI-powered tool discovery using natural language queries
- **Search Tool**: Callable tool discovery for agent loops via `get_search_tool()`
Comment thread
shashi-stackone marked this conversation as resolved.
Outdated
- Integration with popular AI frameworks:
- OpenAI Functions
- LangChain Tools
Expand Down Expand Up @@ -305,26 +306,44 @@ result = feedback_tool.call(
- "Are you ok with sending feedback to StackOne? The LLM will take care of sending it."
- Only call the tool after the user explicitly agrees.

## Utility Tools (Beta)
## Search Tool

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

### Basic Usage

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

# Search for relevant tools — returns a Tools collection
tools = search_tool("manage employees", top_k=5)

# Execute a discovered tool directly
tools[0](limit=10)
```

## Semantic Search

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 `hris_create_employee`.
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new Semantic Search section says the action is called hris_create_employee, but the rest of the repo (examples + semantic normalization/tests) uses connector-prefixed tool names like bamboohr_create_employee. This is likely to confuse users trying the feature; update the example text to match the actual tool naming used by the SDK, or explicitly explain the difference if both naming schemes exist.

Suggested change
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 `hris_create_employee`.
Discover tools using natural language instead of exact names. Queries like "onboard new hire" resolve to the right actions even when the actual tool is named `bamboohr_create_employee` (rather than a generic `hris_create_employee`).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shashi-stackone that looks like a unified action, the copilot comment is accurate hire


# Search for relevant tools using natural language
filter_tool = utility_tools.get_tool("tool_search")
results = filter_tool.call(query="manage employees", limit=5)
```python
from stackone_ai import StackOneToolSet

toolset = StackOneToolSet()

# Search by intent — returns Tools collection ready for any framework
tools = toolset.search_tools("manage employee records", account_ids=["your-account-id"], top_k=5)
openai_tools = tools.to_openai()

# Execute discovered tools dynamically
execute_tool = utility_tools.get_tool("tool_execute")
result = execute_tool.call(toolName="hris_list_employees", params={"limit": 10})
# Lightweight: inspect results without fetching full tool definitions
results = toolset.search_action_names("time off requests", top_k=5)
Comment thread
shashi-stackone marked this conversation as resolved.
```

Results are automatically scoped to connectors in your linked accounts. See [Semantic Search Example](examples/semantic_search_example.py) for utility tools integration, OpenAI, and LangChain patterns.
Comment thread
shashi-stackone marked this conversation as resolved.
Outdated

## Examples

For more examples, check out the [examples/](examples/) directory:
Expand All @@ -334,7 +353,8 @@ For more examples, check out the [examples/](examples/) directory:
- [OpenAI Integration](examples/openai_integration.py)
- [LangChain Integration](examples/langchain_integration.py)
- [CrewAI Integration](examples/crewai_integration.py)
- [Utility Tools](examples/utility_tools_example.py)
- [Search Tool](examples/search_tool_example.py)
- [Semantic Search](examples/semantic_search_example.py)

## Development

Expand Down
271 changes: 271 additions & 0 deletions examples/search_tool_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
#!/usr/bin/env python
"""
Example demonstrating dynamic tool discovery using search_tool.

The search tool allows AI agents to discover relevant tools based on natural language
queries without hardcoding tool names.

Prerequisites:
- STACKONE_API_KEY environment variable set
- STACKONE_ACCOUNT_ID environment variable set (comma-separated for multiple)
- At least one linked account in StackOne (this example uses BambooHR)

This example is runnable with the following command:
```bash
uv run examples/search_tool_example.py
```
"""

import os

from stackone_ai import StackOneToolSet

try:
from dotenv import load_dotenv

load_dotenv()
except ModuleNotFoundError:
pass

# Read account IDs from environment — supports comma-separated values
_account_ids = [aid.strip() for aid in os.getenv("STACKONE_ACCOUNT_ID", "").split(",") if aid.strip()]


def example_search_tool_basic():
"""Basic example of using the search tool for tool discovery"""
print("Example 1: Dynamic tool discovery\n")

# Initialize StackOne toolset
toolset = StackOneToolSet()

# Get all available tools using MCP-backed fetch_tools()
all_tools = toolset.fetch_tools(account_ids=_account_ids)
print(f"Total tools available: {len(all_tools)}")

if not all_tools:
print("No tools found. Check your linked accounts.")
return

# Get a search tool for dynamic discovery
search_tool = toolset.get_search_tool()

# Search for employee management tools — returns a Tools collection
tools = search_tool("manage employees create update list", top_k=5, account_ids=_account_ids)

print(f"Found {len(tools)} relevant tools:")
for tool in tools:
print(f" - {tool.name}: {tool.description}")

print()


def example_search_modes():
"""Comparing semantic vs local search modes.

The search parameter controls which backend search_tools() uses:
- "semantic": cloud-based semantic vector search (higher accuracy for natural language)
- "local": local BM25+TF-IDF hybrid search (no network call to semantic API)
- "auto" (default): tries semantic first, falls back to local on failure
"""
print("Example 2: Semantic vs local search modes\n")

toolset = StackOneToolSet()
query = "manage employee time off"

# Semantic search — uses StackOne's semantic search API
print('search="semantic": cloud-based semantic vector search')
try:
tools_semantic = toolset.search_tools(query, account_ids=_account_ids, top_k=5, search="semantic")
print(f" Found {len(tools_semantic)} tools:")
for tool in tools_semantic:
print(f" - {tool.name}")
except Exception as e:
print(f" Semantic search unavailable: {e}")
print()

# Local search — BM25+TF-IDF, no semantic API call
print('search="local": local BM25+TF-IDF hybrid search')
tools_local = toolset.search_tools(query, account_ids=_account_ids, top_k=5, search="local")
print(f" Found {len(tools_local)} tools:")
for tool in tools_local:
print(f" - {tool.name}")
print()

# Auto (default) — tries semantic, falls back to local
print('search="auto" (default): semantic with local fallback')
tools_auto = toolset.search_tools(query, account_ids=_account_ids, top_k=5, search="auto")
print(f" Found {len(tools_auto)} tools:")
for tool in tools_auto:
print(f" - {tool.name}")
print()


def example_search_tool_with_execution():
"""Example of discovering and executing tools dynamically"""
print("Example 2: Dynamic tool execution\n")
Comment thread
shashi-stackone marked this conversation as resolved.
Outdated

# Initialize toolset
toolset = StackOneToolSet()

# Get all tools using MCP-backed fetch_tools()
all_tools = toolset.fetch_tools(account_ids=_account_ids)

if not all_tools:
print("No tools found. Check your linked accounts.")
return

search_tool = toolset.get_search_tool()

# Step 1: Search for relevant tools
tools = search_tool("list all employees", top_k=1, account_ids=_account_ids)

if tools:
best_tool = tools[0]
print(f"Best matching tool: {best_tool.name}")
print(f"Description: {best_tool.description}")

# Step 2: Execute the found tool directly
try:
print(f"\nExecuting {best_tool.name}...")
result = best_tool(limit=5)
print(f"Execution result: {result}")
except Exception as e:
print(f"Execution failed (expected in example): {e}")

print()


def example_with_openai():
"""Example of using search tool with OpenAI"""
print("Example 3: Using search tool with OpenAI\n")

try:
from openai import OpenAI

# Initialize OpenAI client
client = OpenAI()

# Initialize StackOne toolset
toolset = StackOneToolSet()

# Search for BambooHR employee tools
tools = toolset.search_tools("manage employees", account_ids=_account_ids, top_k=5)

# Convert to OpenAI format
openai_tools = tools.to_openai()

# Create a chat completion with discovered tools
response = client.chat.completions.create(
model="gpt-4",
messages=[
{
"role": "system",
"content": "You are an HR assistant with access to employee management tools.",
},
{"role": "user", "content": "Can you help me find tools for managing employee records?"},
],
tools=openai_tools,
tool_choice="auto",
)

print("OpenAI Response:", response.choices[0].message.content)

if response.choices[0].message.tool_calls:
print("\nTool calls made:")
for tool_call in response.choices[0].message.tool_calls:
print(f" - {tool_call.function.name}")

except ImportError:
print("OpenAI library not installed. Install with: pip install openai")
except Exception as e:
print(f"OpenAI example failed: {e}")

print()


def example_with_langchain():
"""Example of using tools with LangChain"""
print("Example 4: Using tools with LangChain\n")

try:
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

# Initialize StackOne toolset
toolset = StackOneToolSet()

# Get tools and convert to LangChain format using MCP-backed fetch_tools()
tools = toolset.search_tools("list employees", account_ids=_account_ids, top_k=5)
langchain_tools = list(tools.to_langchain())

print(f"Available tools for LangChain: {len(langchain_tools)}")
for tool in langchain_tools:
print(f" - {tool.name}: {tool.description}")

# Create LangChain agent
llm = ChatOpenAI(model="gpt-4", temperature=0)

prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are an HR assistant. Use the available tools to help the user.",
),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
]
)

agent = create_tool_calling_agent(llm, langchain_tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=langchain_tools, verbose=True)

# Run the agent
result = agent_executor.invoke({"input": "Find tools that can list employee data"})

print(f"\nAgent result: {result['output']}")

except ImportError as e:
print(f"LangChain dependencies not installed: {e}")
print("Install with: pip install langchain-openai")
except Exception as e:
print(f"LangChain example failed: {e}")

print()


def main():
"""Run all examples"""
print("=" * 60)
print("StackOne AI SDK - Search Tool Examples")
print("=" * 60)
print()

if not os.getenv("STACKONE_API_KEY"):
print("Set STACKONE_API_KEY to run these examples.")
return

if not _account_ids:
print("Set STACKONE_ACCOUNT_ID to run these examples.")
print("(Comma-separated for multiple accounts)")
return

# Basic examples that work without external APIs
example_search_tool_basic()
example_search_modes()
example_search_tool_with_execution()

# Examples that require OpenAI API
if os.getenv("OPENAI_API_KEY"):
example_with_openai()
example_with_langchain()
else:
print("Set OPENAI_API_KEY to run OpenAI and LangChain examples\n")

print("=" * 60)
print("Examples completed!")
print("=" * 60)


if __name__ == "__main__":
main()
Comment thread
shashi-stackone marked this conversation as resolved.
Loading