Skip to content

Commit ce3443c

Browse files
get rid of the utility tools completely as discussed
1 parent 86c9c64 commit ce3443c

File tree

12 files changed

+343
-1307
lines changed

12 files changed

+343
-1307
lines changed

README.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -306,22 +306,23 @@ result = feedback_tool.call(
306306
- "Are you ok with sending feedback to StackOne? The LLM will take care of sending it."
307307
- Only call the tool after the user explicitly agrees.
308308

309-
## Utility Tools (Beta)
309+
## Search Tool
310310

311-
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.
312312

313313
### Basic Usage
314314

315315
```python
316-
# Get utility tools for dynamic discovery
317-
tools = toolset.fetch_tools(actions=["hris_*"])
318-
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()
319320

320-
# Search for relevant tools using natural language
321-
results = utility_tools.get_search_tool()(query="manage employees", top_k=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-
result = utility_tools.get_execute_tool()(toolName="hris_list_employees", params={"limit": 10})
324+
# Execute a discovered tool directly
325+
tools[0](limit=10)
325326
```
326327

327328
## Semantic Search
@@ -352,7 +353,7 @@ For more examples, check out the [examples/](examples/) directory:
352353
- [OpenAI Integration](examples/openai_integration.py)
353354
- [LangChain Integration](examples/langchain_integration.py)
354355
- [CrewAI Integration](examples/crewai_integration.py)
355-
- [Utility Tools](examples/utility_tools_example.py)
356+
- [Search Tool](examples/search_tool_example.py)
356357
- [Semantic Search](examples/semantic_search_example.py)
357358

358359
## Development
Lines changed: 40 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#!/usr/bin/env python
22
"""
3-
Example demonstrating utility tools for dynamic tool discovery and execution.
3+
Example demonstrating dynamic tool discovery using search_tool.
44
5-
Utility tools allow AI agents to search for relevant tools based on natural language queries
6-
and execute them dynamically without hardcoding tool names.
5+
The search tool allows AI agents to discover relevant tools based on natural language
6+
queries without hardcoding tool names.
77
88
Prerequisites:
99
- STACKONE_API_KEY environment variable set
@@ -12,7 +12,7 @@
1212
1313
This example is runnable with the following command:
1414
```bash
15-
uv run examples/utility_tools_example.py
15+
uv run examples/search_tool_example.py
1616
```
1717
"""
1818

@@ -31,8 +31,8 @@
3131
_account_ids = [aid.strip() for aid in os.getenv("STACKONE_ACCOUNT_ID", "").split(",") if aid.strip()]
3232

3333

34-
def example_utility_tools_basic():
35-
"""Basic example of using utility tools for tool discovery"""
34+
def example_search_tool_basic():
35+
"""Basic example of using the search tool for tool discovery"""
3636
print("Example 1: Dynamic tool discovery\n")
3737

3838
# Initialize StackOne toolset
@@ -46,20 +46,20 @@ def example_utility_tools_basic():
4646
print("No tools found. Check your linked accounts.")
4747
return
4848

49-
# Get utility tools for dynamic discovery
50-
utility_tools = all_tools.utility_tools()
49+
# Get a search tool for dynamic discovery
50+
search_tool = toolset.get_search_tool()
5151

52-
# Search for employee management tools
53-
result = utility_tools.get_search_tool()(query="manage employees create update list", top_k=5)
52+
# Search for employee management tools — returns a Tools collection
53+
tools = search_tool("manage employees create update list", top_k=5, account_ids=_account_ids)
5454

55-
print("Found relevant tools:")
56-
for tool in result.get("tools", []):
57-
print(f" - {tool['name']} (score: {tool['score']:.2f}): {tool['description']}")
55+
print(f"Found {len(tools)} relevant tools:")
56+
for tool in tools:
57+
print(f" - {tool.name}: {tool.description}")
5858

5959
print()
6060

6161

62-
def example_utility_tools_with_execution():
62+
def example_search_tool_with_execution():
6363
"""Example of discovering and executing tools dynamically"""
6464
print("Example 2: Dynamic tool execution\n")
6565

@@ -73,22 +73,20 @@ def example_utility_tools_with_execution():
7373
print("No tools found. Check your linked accounts.")
7474
return
7575

76-
utility_tools = all_tools.utility_tools()
76+
search_tool = toolset.get_search_tool()
7777

7878
# Step 1: Search for relevant tools
79-
search_result = utility_tools.get_search_tool()(query="list all employees", top_k=1)
79+
tools = search_tool("list all employees", top_k=1, account_ids=_account_ids)
8080

81-
tools_found = search_result.get("tools", [])
82-
if tools_found:
83-
best_tool = tools_found[0]
84-
print(f"Best matching tool: {best_tool['name']}")
85-
print(f"Description: {best_tool['description']}")
86-
print(f"Relevance score: {best_tool['score']:.2f}")
81+
if tools:
82+
best_tool = tools[0]
83+
print(f"Best matching tool: {best_tool.name}")
84+
print(f"Description: {best_tool.description}")
8785

88-
# Step 2: Execute the found tool
86+
# Step 2: Execute the found tool directly
8987
try:
90-
print(f"\nExecuting {best_tool['name']}...")
91-
result = utility_tools.get_execute_tool()(toolName=best_tool["name"], params={"limit": 5})
88+
print(f"\nExecuting {best_tool.name}...")
89+
result = best_tool(limit=5)
9290
print(f"Execution result: {result}")
9391
except Exception as e:
9492
print(f"Execution failed (expected in example): {e}")
@@ -97,8 +95,8 @@ def example_utility_tools_with_execution():
9795

9896

9997
def example_with_openai():
100-
"""Example of using utility tools with OpenAI"""
101-
print("Example 3: Using utility tools with OpenAI\n")
98+
"""Example of using search tool with OpenAI"""
99+
print("Example 3: Using search tool with OpenAI\n")
102100

103101
try:
104102
from openai import OpenAI
@@ -109,20 +107,19 @@ def example_with_openai():
109107
# Initialize StackOne toolset
110108
toolset = StackOneToolSet()
111109

112-
# Get BambooHR tools and their utility tools using MCP-backed fetch_tools()
113-
bamboohr_tools = toolset.fetch_tools(account_ids=_account_ids, actions=["bamboohr_*"])
114-
utility_tools = bamboohr_tools.utility_tools()
110+
# Search for BambooHR employee tools
111+
tools = toolset.search_tools("manage employees", account_ids=_account_ids, top_k=5)
115112

116113
# Convert to OpenAI format
117-
openai_tools = utility_tools.to_openai()
114+
openai_tools = tools.to_openai()
118115

119-
# Create a chat completion with utility tools
116+
# Create a chat completion with discovered tools
120117
response = client.chat.completions.create(
121118
model="gpt-4",
122119
messages=[
123120
{
124121
"role": "system",
125-
"content": "You are an HR assistant. Use tool_search to find appropriate tools, then tool_execute to execute them.",
122+
"content": "You are an HR assistant with access to employee management tools.",
126123
},
127124
{"role": "user", "content": "Can you help me find tools for managing employee records?"},
128125
],
@@ -158,18 +155,11 @@ def example_with_langchain():
158155
toolset = StackOneToolSet()
159156

160157
# Get tools and convert to LangChain format using MCP-backed fetch_tools()
161-
tools = toolset.fetch_tools(account_ids=_account_ids, actions=["bamboohr_list_*"])
162-
langchain_tools = tools.to_langchain()
158+
tools = toolset.search_tools("list employees", account_ids=_account_ids, top_k=5)
159+
langchain_tools = list(tools.to_langchain())
163160

164-
# Get utility tools as well
165-
utility_tools = tools.utility_tools()
166-
langchain_utility_tools = utility_tools.to_langchain()
167-
168-
# Combine all tools
169-
all_langchain_tools = list(langchain_tools) + list(langchain_utility_tools)
170-
171-
print(f"Available tools for LangChain: {len(all_langchain_tools)}")
172-
for tool in all_langchain_tools:
161+
print(f"Available tools for LangChain: {len(langchain_tools)}")
162+
for tool in langchain_tools:
173163
print(f" - {tool.name}: {tool.description}")
174164

175165
# Create LangChain agent
@@ -179,15 +169,15 @@ def example_with_langchain():
179169
[
180170
(
181171
"system",
182-
"You are an HR assistant. Use the utility tools to discover and execute relevant tools.",
172+
"You are an HR assistant. Use the available tools to help the user.",
183173
),
184174
("human", "{input}"),
185175
("placeholder", "{agent_scratchpad}"),
186176
]
187177
)
188178

189-
agent = create_tool_calling_agent(llm, all_langchain_tools, prompt)
190-
agent_executor = AgentExecutor(agent=agent, tools=all_langchain_tools, verbose=True)
179+
agent = create_tool_calling_agent(llm, langchain_tools, prompt)
180+
agent_executor = AgentExecutor(agent=agent, tools=langchain_tools, verbose=True)
191181

192182
# Run the agent
193183
result = agent_executor.invoke({"input": "Find tools that can list employee data"})
@@ -206,7 +196,7 @@ def example_with_langchain():
206196
def main():
207197
"""Run all examples"""
208198
print("=" * 60)
209-
print("StackOne AI SDK - Utility Tools Examples")
199+
print("StackOne AI SDK - Search Tool Examples")
210200
print("=" * 60)
211201
print()
212202

@@ -220,8 +210,8 @@ def main():
220210
return
221211

222212
# Basic examples that work without external APIs
223-
example_utility_tools_basic()
224-
example_utility_tools_with_execution()
213+
example_search_tool_basic()
214+
example_search_tool_with_execution()
225215

226216
# Examples that require OpenAI API
227217
if os.getenv("OPENAI_API_KEY"):

examples/semantic_search_example.py

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,11 @@
5353
inspecting results before committing to a full fetch. When account_ids are
5454
provided, each connector is searched in parallel (same as search_tools).
5555
56-
3. utility_tools() — Agent-loop pattern
56+
3. get_search_tool() — Agent-loop pattern
5757
58-
Creates tool_search and tool_execute utility tools that agents can call
59-
inside an agentic loop. Pass search_method="semantic" to enable
60-
cloud-based semantic search; without it, local BM25+TF-IDF is used.
61-
When created via utility_tools(), tool_search is automatically scoped
62-
to the user's linked connectors. The agent searches, inspects, and
63-
executes tools dynamically.
58+
Returns a callable SearchTool that wraps search_tools(). Call it
59+
with a natural language query to get a Tools collection back.
60+
Designed for agent loops where the LLM decides what to search for.
6461
6562
6663
This example is runnable with the following command:
@@ -213,43 +210,38 @@ def example_search_tools_with_connector():
213210
print()
214211

215212

216-
def example_utility_tools_semantic():
217-
"""Using utility tools with semantic search for agent loops.
213+
def example_search_tool_agent_loop():
214+
"""Using get_search_tool() for agent loops.
218215
219-
Pass search_method="semantic" to utility_tools() to enable cloud-based
220-
semantic search. Without it, utility_tools() uses local BM25+TF-IDF
221-
search instead.
222-
223-
When created via utility_tools(), tool_search is automatically scoped to
224-
the connectors available in your fetched tools collection.
216+
get_search_tool() returns a callable that wraps search_tools().
217+
Call it with a query to get a Tools collection back — designed
218+
for agent loops where the LLM decides what to search for.
225219
"""
226220
print("=" * 60)
227-
print("Example 4: Utility tools with semantic search")
221+
print("Example 4: Search tool for agent loops")
228222
print("=" * 60)
229223
print()
230224

231225
toolset = StackOneToolSet()
232226

233227
print("Step 1: Fetching tools from your linked accounts via MCP...")
234-
tools = toolset.fetch_tools(account_ids=_account_ids)
235-
print(f"Loaded {len(tools)} tools.")
228+
all_tools = toolset.fetch_tools(account_ids=_account_ids)
229+
print(f"Loaded {len(all_tools)} tools.")
236230
print()
237231

238-
print("Step 2: Creating utility tools with semantic search enabled...")
239-
print(' Pass search_method="semantic" to enable cloud-based semantic search.')
240-
utility = tools.utility_tools(search_method="semantic")
232+
print("Step 2: Getting a callable search tool...")
233+
search_tool = toolset.get_search_tool()
241234

242235
query = "cancel an event or meeting"
243236
print()
244-
print(f'Step 3: Calling tool_search with query="{query}"...')
237+
print(f'Step 3: Calling search_tool("{query}")...')
245238
print(" (Searches are scoped to your linked connectors)")
246239
print()
247-
result = utility.get_search_tool()(query=query, top_k=5)
248-
tools_data = result.get("tools", [])
249-
print(f"tool_search returned {len(tools_data)} results:")
250-
for tool_info in tools_data:
251-
print(f" [{tool_info['score']:.2f}] {tool_info['name']}")
252-
print(f" {tool_info['description']}")
240+
tools = search_tool(query, top_k=5, account_ids=_account_ids)
241+
print(f"search_tool returned {len(tools)} tools:")
242+
for tool in tools:
243+
print(f" - {tool.name}")
244+
print(f" {tool.description}")
253245

254246
print()
255247

@@ -409,7 +401,7 @@ def main():
409401

410402
example_search_tools()
411403
example_search_tools_with_connector()
412-
example_utility_tools_semantic()
404+
example_search_tool_agent_loop()
413405

414406
# Framework integration patterns
415407
example_openai_agent_loop()

examples/test_examples.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def get_example_files() -> list[str]:
3030
"index.py": ["mcp"],
3131
"file_uploads.py": ["mcp"],
3232
"stackone_account_ids.py": ["mcp"],
33-
"utility_tools_example.py": ["mcp"],
33+
"search_tool_example.py": ["mcp"],
3434
"semantic_search_example.py": ["mcp"],
3535
"mcp_server.py": ["mcp"],
3636
}

stackone_ai/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
"""StackOne AI SDK"""
22

3-
from stackone_ai.models import StackOneTool, Tools, UtilityTools
3+
from stackone_ai.models import StackOneTool, Tools
44
from stackone_ai.semantic_search import (
55
SemanticSearchClient,
66
SemanticSearchError,
77
SemanticSearchResponse,
88
SemanticSearchResult,
99
)
10-
from stackone_ai.toolset import StackOneToolSet
10+
from stackone_ai.toolset import SearchTool, StackOneToolSet
1111

1212
__all__ = [
1313
"StackOneToolSet",
1414
"StackOneTool",
1515
"Tools",
16-
"UtilityTools",
16+
"SearchTool",
1717
# Semantic search
1818
"SemanticSearchClient",
1919
"SemanticSearchResult",

0 commit comments

Comments
 (0)