-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch_tool_example.py
More file actions
314 lines (238 loc) · 10.5 KB
/
search_tool_example.py
File metadata and controls
314 lines (238 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
#!/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.
Search config can be set at the constructor level or overridden per call:
- Constructor: StackOneToolSet(search={"method": "semantic"})
- Per-call: toolset.search_tools(query, search="local")
The search method 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")
query = "manage employee time off"
# Constructor-level config — semantic search as the default for this toolset
print('Constructor config: StackOneToolSet(search={"method": "semantic"})')
toolset_semantic = StackOneToolSet(search={"method": "semantic"})
try:
tools_semantic = toolset_semantic.search_tools(query, account_ids=_account_ids, top_k=5)
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()
# Constructor-level config — local search (no network call to semantic API)
print('Constructor config: StackOneToolSet(search={"method": "local"})')
toolset_local = StackOneToolSet(search={"method": "local"})
tools_local = toolset_local.search_tools(query, account_ids=_account_ids, top_k=5)
print(f" Found {len(tools_local)} tools:")
for tool in tools_local:
print(f" - {tool.name}")
print()
# Per-call override — constructor defaults can be overridden on each call
print("Per-call override: constructor uses semantic, but this call uses local")
tools_override = toolset_semantic.search_tools(query, account_ids=_account_ids, top_k=5, search="local")
print(f" Found {len(tools_override)} tools:")
for tool in tools_override:
print(f" - {tool.name}")
print()
# Auto (default) — tries semantic, falls back to local
print('Default: StackOneToolSet() uses search="auto" (semantic with local fallback)')
toolset_auto = StackOneToolSet()
tools_auto = toolset_auto.search_tools(query, account_ids=_account_ids, top_k=5)
print(f" Found {len(tools_auto)} tools:")
for tool in tools_auto:
print(f" - {tool.name}")
print()
def example_top_k_config():
"""Configuring top_k at the constructor level vs per-call.
Constructor-level top_k applies to all search_tools() and search_action_names()
calls. Per-call top_k overrides the constructor default for that single call.
"""
print("Example 3: top_k at constructor vs per-call\n")
# Constructor-level top_k — all calls default to returning 3 results
toolset = StackOneToolSet(search={"top_k": 3})
query = "manage employee records"
print(f'Constructor top_k=3: searching for "{query}"')
tools_default = toolset.search_tools(query, account_ids=_account_ids)
print(f" Got {len(tools_default)} tools (constructor default)")
for tool in tools_default:
print(f" - {tool.name}")
print()
# Per-call override — this single call returns up to 10 results
print("Per-call top_k=10: overriding constructor default")
tools_override = toolset.search_tools(query, account_ids=_account_ids, top_k=10)
print(f" Got {len(tools_override)} tools (per-call override)")
for tool in tools_override:
print(f" - {tool.name}")
print()
def example_search_tool_with_execution():
"""Example of discovering and executing tools dynamically"""
print("Example 4: Dynamic tool execution\n")
# 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 5: 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-5.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 6: 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-5.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_top_k_config()
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()