Skip to content

Commit 544f41e

Browse files
feat:include available connectors in search/execute tool descriptions (#165)
* include available connectors in search/execute tool descriptions * Fix th timeout and account ID issue in the SDK for the toolset.execute path * Update old examples to new SDK API * Fix the CI issue * Address copilot review about the timeout and trim th search tool example * Add working Workday Example * Add workday example in the test_examples * Add the STACKONE_API_KEY too in example
1 parent 9643f60 commit 544f41e

6 files changed

Lines changed: 162 additions & 294 deletions

File tree

examples/search_tool_example.py

Lines changed: 34 additions & 282 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,19 @@
1-
#!/usr/bin/env python
2-
"""
3-
Example demonstrating dynamic tool discovery using search_tool.
1+
"""Search tool patterns: callable wrapper and config overrides.
42
5-
The search tool allows AI agents to discover relevant tools based on natural language
6-
queries without hardcoding tool names.
3+
For semantic search basics, see semantic_search_example.py.
4+
For full agent execution, see agent_tool_search.py.
75
86
Prerequisites:
9-
- STACKONE_API_KEY environment variable set
10-
- STACKONE_ACCOUNT_ID environment variable set (comma-separated for multiple)
11-
- At least one linked account in StackOne (this example uses BambooHR)
7+
- STACKONE_API_KEY environment variable
8+
- STACKONE_ACCOUNT_ID environment variable
129
13-
This example is runnable with the following command:
14-
```bash
15-
uv run examples/search_tool_example.py
16-
```
10+
Run with:
11+
uv run python examples/search_tool_example.py
1712
"""
1813

19-
import os
14+
from __future__ import annotations
2015

21-
from stackone_ai import StackOneToolSet
16+
import os
2217

2318
try:
2419
from dotenv import load_dotenv
@@ -27,287 +22,44 @@
2722
except ModuleNotFoundError:
2823
pass
2924

30-
# Read account IDs from environment — supports comma-separated values
31-
_account_ids = [aid.strip() for aid in os.getenv("STACKONE_ACCOUNT_ID", "").split(",") if aid.strip()]
32-
33-
34-
def example_search_tool_basic():
35-
"""Basic example of using the search tool for tool discovery"""
36-
print("Example 1: Dynamic tool discovery\n")
25+
from stackone_ai import StackOneToolSet
3726

38-
# Initialize StackOne toolset
39-
toolset = StackOneToolSet()
4027

41-
# Get all available tools using MCP-backed fetch_tools()
42-
all_tools = toolset.fetch_tools(account_ids=_account_ids)
43-
print(f"Total tools available: {len(all_tools)}")
28+
def main() -> None:
29+
api_key = os.getenv("STACKONE_API_KEY")
30+
account_id = os.getenv("STACKONE_ACCOUNT_ID")
4431

45-
if not all_tools:
46-
print("No tools found. Check your linked accounts.")
32+
if not api_key:
33+
print("Set STACKONE_API_KEY to run this example.")
4734
return
48-
49-
# Get a search tool for dynamic discovery
50-
search_tool = toolset.get_search_tool()
51-
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)
54-
55-
print(f"Found {len(tools)} relevant tools:")
56-
for tool in tools:
57-
print(f" - {tool.name}: {tool.description}")
58-
59-
print()
60-
61-
62-
def example_search_modes():
63-
"""Comparing semantic vs local search modes.
64-
65-
Search config can be set at the constructor level or overridden per call:
66-
- Constructor: StackOneToolSet(search={"method": "semantic"})
67-
- Per-call: toolset.search_tools(query, search="local")
68-
69-
The search method controls which backend search_tools() uses:
70-
- "semantic": cloud-based semantic vector search (higher accuracy for natural language)
71-
- "local": local BM25+TF-IDF hybrid search (no network call to semantic API)
72-
- "auto" (default): tries semantic first, falls back to local on failure
73-
"""
74-
print("Example 2: Semantic vs local search modes\n")
75-
76-
query = "manage employee time off"
77-
78-
# Constructor-level config — semantic search as the default for this toolset
79-
print('Constructor config: StackOneToolSet(search={"method": "semantic"})')
80-
toolset_semantic = StackOneToolSet(search={"method": "semantic"})
81-
try:
82-
tools_semantic = toolset_semantic.search_tools(query, account_ids=_account_ids, top_k=5)
83-
print(f" Found {len(tools_semantic)} tools:")
84-
for tool in tools_semantic:
85-
print(f" - {tool.name}")
86-
except Exception as e:
87-
print(f" Semantic search unavailable: {e}")
88-
print()
89-
90-
# Constructor-level config — local search (no network call to semantic API)
91-
print('Constructor config: StackOneToolSet(search={"method": "local"})')
92-
toolset_local = StackOneToolSet(search={"method": "local"})
93-
tools_local = toolset_local.search_tools(query, account_ids=_account_ids, top_k=5)
94-
print(f" Found {len(tools_local)} tools:")
95-
for tool in tools_local:
96-
print(f" - {tool.name}")
97-
print()
98-
99-
# Per-call override — constructor defaults can be overridden on each call
100-
print("Per-call override: constructor uses semantic, but this call uses local")
101-
tools_override = toolset_semantic.search_tools(query, account_ids=_account_ids, top_k=5, search="local")
102-
print(f" Found {len(tools_override)} tools:")
103-
for tool in tools_override:
104-
print(f" - {tool.name}")
105-
print()
106-
107-
# Auto (default) — tries semantic, falls back to local
108-
print('Default: StackOneToolSet() uses search="auto" (semantic with local fallback)')
109-
toolset_auto = StackOneToolSet()
110-
tools_auto = toolset_auto.search_tools(query, account_ids=_account_ids, top_k=5)
111-
print(f" Found {len(tools_auto)} tools:")
112-
for tool in tools_auto:
113-
print(f" - {tool.name}")
114-
print()
115-
116-
117-
def example_top_k_config():
118-
"""Configuring top_k at the constructor level vs per-call.
119-
120-
Constructor-level top_k applies to all search_tools() and search_action_names()
121-
calls. Per-call top_k overrides the constructor default for that single call.
122-
"""
123-
print("Example 3: top_k at constructor vs per-call\n")
124-
125-
# Constructor-level top_k — all calls default to returning 3 results
126-
toolset = StackOneToolSet(search={"top_k": 3})
127-
128-
query = "manage employee records"
129-
print(f'Constructor top_k=3: searching for "{query}"')
130-
tools_default = toolset.search_tools(query, account_ids=_account_ids)
131-
print(f" Got {len(tools_default)} tools (constructor default)")
132-
for tool in tools_default:
133-
print(f" - {tool.name}")
134-
print()
135-
136-
# Per-call override — this single call returns up to 10 results
137-
print("Per-call top_k=10: overriding constructor default")
138-
tools_override = toolset.search_tools(query, account_ids=_account_ids, top_k=10)
139-
print(f" Got {len(tools_override)} tools (per-call override)")
140-
for tool in tools_override:
141-
print(f" - {tool.name}")
142-
print()
143-
144-
145-
def example_search_tool_with_execution():
146-
"""Example of discovering and executing tools dynamically"""
147-
print("Example 4: Dynamic tool execution\n")
148-
149-
# Initialize toolset
150-
toolset = StackOneToolSet()
151-
152-
# Get all tools using MCP-backed fetch_tools()
153-
all_tools = toolset.fetch_tools(account_ids=_account_ids)
154-
155-
if not all_tools:
156-
print("No tools found. Check your linked accounts.")
35+
if not account_id:
36+
print("Set STACKONE_ACCOUNT_ID to run this example.")
15737
return
15838

159-
search_tool = toolset.get_search_tool()
160-
161-
# Step 1: Search for relevant tools
162-
tools = search_tool("list all employees", top_k=1, account_ids=_account_ids)
163-
164-
if tools:
165-
best_tool = tools[0]
166-
print(f"Best matching tool: {best_tool.name}")
167-
print(f"Description: {best_tool.description}")
168-
169-
# Step 2: Execute the found tool directly
170-
try:
171-
print(f"\nExecuting {best_tool.name}...")
172-
result = best_tool(limit=5)
173-
print(f"Execution result: {result}")
174-
except Exception as e:
175-
print(f"Execution failed (expected in example): {e}")
176-
177-
print()
178-
179-
180-
def example_with_openai():
181-
"""Example of using search tool with OpenAI"""
182-
print("Example 5: Using search tool with OpenAI\n")
183-
184-
try:
185-
from openai import OpenAI
186-
187-
# Initialize OpenAI client
188-
client = OpenAI()
189-
190-
# Initialize StackOne toolset
191-
toolset = StackOneToolSet()
192-
193-
# Search for BambooHR employee tools
194-
tools = toolset.search_tools("manage employees", account_ids=_account_ids, top_k=5)
195-
196-
# Convert to OpenAI format
197-
openai_tools = tools.to_openai()
198-
199-
# Create a chat completion with discovered tools
200-
response = client.chat.completions.create(
201-
model="gpt-5.4",
202-
messages=[
203-
{
204-
"role": "system",
205-
"content": "You are an HR assistant with access to employee management tools.",
206-
},
207-
{"role": "user", "content": "Can you help me find tools for managing employee records?"},
208-
],
209-
tools=openai_tools,
210-
tool_choice="auto",
211-
)
212-
213-
print("OpenAI Response:", response.choices[0].message.content)
214-
215-
if response.choices[0].message.tool_calls:
216-
print("\nTool calls made:")
217-
for tool_call in response.choices[0].message.tool_calls:
218-
print(f" - {tool_call.function.name}")
219-
220-
except ImportError:
221-
print("OpenAI library not installed. Install with: pip install openai")
222-
except Exception as e:
223-
print(f"OpenAI example failed: {e}")
224-
225-
print()
226-
227-
228-
def example_with_langchain():
229-
"""Example of using tools with LangChain"""
230-
print("Example 6: Using tools with LangChain\n")
231-
232-
try:
233-
from langchain.agents import AgentExecutor, create_tool_calling_agent
234-
from langchain_core.prompts import ChatPromptTemplate
235-
from langchain_openai import ChatOpenAI
236-
237-
# Initialize StackOne toolset
238-
toolset = StackOneToolSet()
239-
240-
# Get tools and convert to LangChain format using MCP-backed fetch_tools()
241-
tools = toolset.search_tools("list employees", account_ids=_account_ids, top_k=5)
242-
langchain_tools = list(tools.to_langchain())
243-
244-
print(f"Available tools for LangChain: {len(langchain_tools)}")
245-
for tool in langchain_tools:
246-
print(f" - {tool.name}: {tool.description}")
247-
248-
# Create LangChain agent
249-
llm = ChatOpenAI(model="gpt-5.4", temperature=0)
250-
251-
prompt = ChatPromptTemplate.from_messages(
252-
[
253-
(
254-
"system",
255-
"You are an HR assistant. Use the available tools to help the user.",
256-
),
257-
("human", "{input}"),
258-
("placeholder", "{agent_scratchpad}"),
259-
]
260-
)
261-
262-
agent = create_tool_calling_agent(llm, langchain_tools, prompt)
263-
agent_executor = AgentExecutor(agent=agent, tools=langchain_tools, verbose=True)
264-
265-
# Run the agent
266-
result = agent_executor.invoke({"input": "Find tools that can list employee data"})
267-
268-
print(f"\nAgent result: {result['output']}")
269-
270-
except ImportError as e:
271-
print(f"LangChain dependencies not installed: {e}")
272-
print("Install with: pip install langchain-openai")
273-
except Exception as e:
274-
print(f"LangChain example failed: {e}")
275-
276-
print()
39+
# --- Example 1: get_search_tool() callable ---
40+
print("=== get_search_tool() callable ===\n")
27741

42+
toolset = StackOneToolSet(api_key=api_key, account_id=account_id, search={})
43+
search_tool = toolset.get_search_tool()
27844

279-
def main():
280-
"""Run all examples"""
281-
print("=" * 60)
282-
print("StackOne AI SDK - Search Tool Examples")
283-
print("=" * 60)
284-
print()
45+
queries = ["cancel an event", "list employees", "send a message"]
46+
for query in queries:
47+
tools = search_tool(query, top_k=3)
48+
names = [t.name for t in tools]
49+
print(f' "{query}" -> {", ".join(names) or "(none)"}')
28550

286-
if not os.getenv("STACKONE_API_KEY"):
287-
print("Set STACKONE_API_KEY to run these examples.")
288-
return
51+
# --- Example 2: Constructor top_k vs per-call override ---
52+
print("\n=== Constructor top_k vs per-call override ===\n")
28953

290-
if not _account_ids:
291-
print("Set STACKONE_ACCOUNT_ID to run these examples.")
292-
print("(Comma-separated for multiple accounts)")
293-
return
54+
toolset_3 = StackOneToolSet(api_key=api_key, account_id=account_id, search={"top_k": 3})
29455

295-
# Basic examples that work without external APIs
296-
example_search_tool_basic()
297-
example_search_modes()
298-
example_top_k_config()
299-
example_search_tool_with_execution()
56+
query = "manage employee records"
30057

301-
# Examples that require OpenAI API
302-
if os.getenv("OPENAI_API_KEY"):
303-
example_with_openai()
304-
example_with_langchain()
305-
else:
306-
print("Set OPENAI_API_KEY to run OpenAI and LangChain examples\n")
58+
tools_3 = toolset_3.search_tools(query)
59+
print(f"Constructor top_k=3: got {len(tools_3)} tools")
30760

308-
print("=" * 60)
309-
print("Examples completed!")
310-
print("=" * 60)
61+
tools_override = toolset_3.search_tools(query, top_k=10)
62+
print(f"Per-call top_k=10 (overrides constructor 3): got {len(tools_override)} tools")
31163

31264

31365
if __name__ == "__main__":

examples/semantic_search_example.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ def example_search_action_names():
133133
print(f"Top {len(results_limited)} matches from the full catalog:")
134134
for r in results_limited:
135135
print(f" [{r.similarity_score:.2f}] {r.id}")
136-
print(f" {r.description}")
137136
print()
138137

139138
# Show filtering effect when account_ids are available

examples/test_examples.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def get_example_files() -> list[str]:
3333
"search_tool_example.py": ["mcp"],
3434
"semantic_search_example.py": ["mcp"],
3535
"mcp_server.py": ["mcp"],
36+
"workday_integration.py": ["openai", "mcp"],
3637
}
3738

3839

0 commit comments

Comments
 (0)