Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0e0f7f3
Senamtic Search on action in Python AI SDK
Feb 5, 2026
0b0e9e0
Filter tools based on the SDK auth config and connector
Shashikant86 Feb 5, 2026
736e68f
Use the local benchmark from the ai-generations
Shashikant86 Feb 5, 2026
4d3deca
Add Semantinc search bench mark with local benchmarks
Shashikant86 Feb 6, 2026
981f912
Fix CI lint errors
Shashikant86 Feb 6, 2026
be6db2a
Fix the lint in the benchmark file
Shashikant86 Feb 6, 2026
bcb0b87
Formalise the docs and code
Shashikant86 Feb 6, 2026
0a26c57
Keep semantic search minimal in the README
Shashikant86 Feb 6, 2026
e6ab80b
Remove the old benchmark data
Shashikant86 Feb 6, 2026
96270d6
implement PR feedback suggestions from cubic
Shashikant86 Feb 6, 2026
901d5af
fix nullable in the semantic tool schema
Shashikant86 Feb 6, 2026
94bb25d
limit override
Shashikant86 Feb 6, 2026
2c5619f
handle per connector calls to avoid the guesswork
Shashikant86 Feb 6, 2026
4c5726d
ci: trigger rebuild
Shashikant86 Feb 6, 2026
06d6a9a
simplify utility_tools API by inferring semantic search from client p…
Shashikant86 Feb 9, 2026
bf45364
Benchmark update and PR suggestions
Shashikant86 Feb 9, 2026
2ae1e77
update the README gst
Shashikant86 Feb 9, 2026
e1fb3dd
Note on the fetch tools for actions that user expect to discover
Shashikant86 Feb 10, 2026
4e19479
Update examples and improve the semantic seach
Shashikant86 Feb 10, 2026
521339b
Fix ruff issues
Shashikant86 Feb 10, 2026
4d704ab
Document the semantic search feature in the python files and example
Shashikant86 Feb 12, 2026
fbd9c79
Respect the backend results unless top_k specified explicitly, add py…
Shashikant86 Feb 12, 2026
76bedac
move the crewAI tools conversation back in the example
Shashikant86 Feb 12, 2026
893f6d4
CI Trigger
Shashikant86 Feb 12, 2026
c4f8f34
Fix unit tests with updated top_k behavior
Shashikant86 Feb 12, 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
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ 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
- **Semantic Search**: AI-powered tool discovery using natural language queries
- **Utility Tools** (Beta): Dynamic tool discovery and execution based on natural language queries
- Integration with popular AI frameworks:
- OpenAI Functions
Expand Down Expand Up @@ -325,6 +326,57 @@ execute_tool = utility_tools.get_tool("tool_execute")
result = execute_tool.call(toolName="hris_list_employees", params={"limit": 10})
```

## Semantic Search

Semantic search enables tool discovery using natural language instead of exact keyword matching. It understands intent and synonyms, so queries like "onboard new hire" or "check my to-do list" resolve to the right StackOne actions.

**How it works:** Your query is matched against all StackOne actions using semantic vector search. Results are automatically filtered to only the connectors available in your linked accounts, so you only get tools you can actually use.

### `search_tools()` — Recommended

High-level method that returns a `Tools` collection ready for any framework:

```python
from stackone_ai import StackOneToolSet

toolset = StackOneToolSet()

# Natural language search — no need to know exact tool names
tools = toolset.search_tools("manage employee records", top_k=5)

# Use with any framework
langchain_tools = tools.to_langchain()

# Filter by connector
tools = toolset.search_tools("create time off request", connector="bamboohr", top_k=3)
```

### `search_action_names()` — Lightweight

Returns action names and similarity scores without fetching full tool definitions. Useful for inspecting results before committing:

```python
results = toolset.search_action_names("time off requests", top_k=5)
for r in results:
print(f"{r.action_name} ({r.connector_key}): {r.similarity_score:.2f}")
```

### Utility Tools with Semantic Search

For agent loops using `tool_search` / `tool_execute`, pass `semantic_client` to upgrade from local keyword matching to semantic search:

```python
tools = toolset.fetch_tools()
utility = tools.utility_tools(semantic_client=toolset.semantic_client)

search_tool = utility.get_tool("tool_search")
results = search_tool.call(query="onboard a new team member", limit=5)
```

> `tool_search` queries the full backend catalog, so make sure `fetch_tools()` covers the actions you expect to discover.

See [Semantic Search Example](examples/semantic_search_example.py) for complete patterns including OpenAI and LangChain integration.

## Examples

For more examples, check out the [examples/](examples/) directory:
Expand All @@ -335,6 +387,7 @@ For more examples, check out the [examples/](examples/) directory:
- [LangChain Integration](examples/langchain_integration.py)
- [CrewAI Integration](examples/crewai_integration.py)
- [Utility Tools](examples/utility_tools_example.py)
- [Semantic Search](examples/semantic_search_example.py)

## Development

Expand Down
3 changes: 3 additions & 0 deletions examples/crewai_integration.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"""
This example demonstrates how to use StackOne tools with CrewAI.

Note: This example is Python only. CrewAI does not have an official
TypeScript/Node.js library.

CrewAI uses LangChain tools natively.

```bash
Expand Down
145 changes: 145 additions & 0 deletions examples/crewai_semantic_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
"""
CrewAI meeting booking agent powered by semantic search.

Note: This example is Python only. CrewAI does not have an official
TypeScript/Node.js library.

Instead of hardcoding tool names, this example uses semantic search to discover
scheduling tools (e.g., Calendly) from natural language queries like "book a
meeting" or "check availability".

Prerequisites:
- STACKONE_API_KEY environment variable set
- STACKONE_ACCOUNT_ID environment variable set (Calendly-linked account)
- OPENAI_API_KEY environment variable set (for CrewAI's LLM)

```bash
uv run examples/crewai_semantic_search.py
```
"""

import os
from typing import Any

from crewai import Agent, Crew, Task
from crewai.tools.base_tool import BaseTool as CrewAIBaseTool
from dotenv import load_dotenv
from pydantic import BaseModel, Field

from stackone_ai import StackOneToolSet
from stackone_ai.models import StackOneTool

load_dotenv()

_account_ids = [aid.strip() for aid in os.getenv("STACKONE_ACCOUNT_ID", "").split(",") if aid.strip()]


def _to_crewai_tool(tool: StackOneTool) -> CrewAIBaseTool:
"""Wrap a StackOneTool as a CrewAI BaseTool.

CrewAI has its own BaseTool (not LangChain's), so we create a
lightweight wrapper that delegates execution to the StackOne tool.
"""
schema_props: dict[str, Any] = {}
annotations: dict[str, Any] = {}

for name, details in tool.parameters.properties.items():
python_type: type = str
if isinstance(details, dict):
type_str = details.get("type", "string")
if type_str == "number":
python_type = float
elif type_str == "integer":
python_type = int
elif type_str == "boolean":
python_type = bool
field = Field(description=details.get("description", ""))
else:
field = Field(description="")

schema_props[name] = field
annotations[name] = python_type

_schema = type(
f"{tool.name.title().replace('_', '')}Args",
(BaseModel,),
{"__annotations__": annotations, "__module__": __name__, **schema_props},
)

_parent = tool
_name = tool.name
_description = tool.description

class WrappedTool(CrewAIBaseTool):
name: str = _name
description: str = _description
args_schema: type[BaseModel] = _schema

def _run(self, **kwargs: Any) -> Any:
return _parent.execute(kwargs)

return WrappedTool()


def crewai_semantic_search() -> None:
toolset = StackOneToolSet()

# Step 1: Preview — lightweight search returning action names and scores
# search_action_names() queries the semantic API without fetching full
# tool definitions. Useful for inspecting what's available before committing.
preview = toolset.search_action_names(
"book a meeting or check availability",
account_ids=_account_ids,
)
print("Semantic search preview (action names only):")
for r in preview:
print(f" [{r.similarity_score:.2f}] {r.action_name} ({r.connector_key})")
print()

# Step 2: Full discovery — fetch matching tools ready for framework use
# search_tools() fetches tools from linked accounts, runs semantic search,
# and returns only tools the user has access to.
tools = toolset.search_tools(
"schedule meetings, check availability, list events",
connector="calendly",
account_ids=_account_ids,
)
assert len(tools) > 0, "Expected at least one scheduling tool"

print(f"Discovered {len(tools)} scheduling tools:")
for tool in tools:
print(f" - {tool.name}: {tool.description[:80]}...")
print()

# Step 3: Convert to CrewAI format
crewai_tools = [_to_crewai_tool(t) for t in tools]

# Step 4: Create a CrewAI meeting booking agent
agent = Agent(
role="Meeting Booking Agent",
goal="Help users manage their calendar by discovering and booking meetings, "
"checking availability, and listing upcoming events.",
backstory="You are an AI assistant specialized in calendar management. "
"You have access to scheduling tools discovered via semantic search "
"and can help users with all meeting-related tasks.",
llm="gpt-4o-mini",
tools=crewai_tools,
max_iter=2,
verbose=True,
)

task = Task(
description="List upcoming scheduled events to give an overview of the calendar.",
agent=agent,
expected_output="A summary of upcoming events or a confirmation that events were retrieved.",
)

crew = Crew(agents=[agent], tasks=[task])

result = crew.kickoff()
assert result is not None, "Expected result to be returned"
print(f"\nCrew result: {result}")


if __name__ == "__main__":
crewai_semantic_search()
Loading