Skip to content

Commit 424e370

Browse files
author
Matt Carey
committed
feat: add proper filtering
1 parent 5b03d05 commit 424e370

File tree

10 files changed

+255
-102
lines changed

10 files changed

+255
-102
lines changed

examples/available_tools.py

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,82 @@
11
"""
22
Get available tools from your StackOne organisation based on the account id.
33
4+
This example demonstrates different ways to filter and organize tools:
5+
1. Getting all available tools
6+
2. Filtering by vertical
7+
3. Using multiple patterns for cross-vertical functionality
8+
4. Filtering by specific operations
9+
5. Combining multiple operation patterns
10+
11+
# TODO: experimental - get_available_tools(account_id="your_account_id")
12+
413
```bash
514
uv run examples/available_tools.py
615
```
716
"""
817

18+
from dotenv import load_dotenv
19+
from stackone_ai import StackOneToolSet
20+
21+
load_dotenv()
22+
23+
24+
def get_available_tools() -> None:
25+
toolset = StackOneToolSet()
26+
27+
# First, get all tools
28+
all_tools = toolset.get_tools()
29+
assert len(all_tools) > 100, "Expected at least 100 tools in total"
30+
31+
# Then, let's get just HRIS tools using a vertical filter
32+
hris_tools = toolset.get_tools("hris_*")
33+
assert len(hris_tools) > 10, "Expected at least 10 HRIS tools"
34+
35+
# Now, let's get people-related tools across verticals
36+
people_tools = toolset.get_tools(
37+
[
38+
"hris_*employee*",
39+
"crm_*contact*",
40+
]
41+
)
42+
assert len(people_tools) > 20, "Expected at least 20 people-related tools"
43+
for tool in people_tools:
44+
assert "employee" in tool.name or "contact" in tool.name, (
45+
f"Tool {tool.name} doesn't contain 'employee' or 'contact'"
46+
)
47+
48+
# We can also filter by specific operations across all verticals
49+
upload_tools = toolset.get_tools("*upload*")
50+
assert len(upload_tools) > 0, "Expected at least one upload tool"
51+
for tool in upload_tools:
52+
assert "upload" in tool.name.lower(), f"Tool {tool.name} doesn't contain 'upload'"
53+
54+
# Get all tools except HRIS
55+
non_hris_tools = toolset.get_tools("!hris_*")
56+
assert len(non_hris_tools) > 0, "Expected at least one non-HRIS tool"
57+
for tool in non_hris_tools:
58+
assert not tool.name.startswith("hris_"), f"Tool {tool.name} should not be an HRIS tool"
959

10-
# TODO: Add examples
11-
def get_available_tools():
12-
print("Getting available tools")
60+
# Complex filtering with positive and negative patterns
61+
list_tools = toolset.get_tools(
62+
[
63+
"*list*", # Include list operations
64+
"*search*", # Include search operations
65+
"!*delete*", # Exclude delete operations
66+
"!*remove*", # Exclude remove operations
67+
]
68+
)
69+
assert len(list_tools) > 0, "Expected at least one list/search tool"
70+
for tool in list_tools:
71+
# Should match positive patterns
72+
assert any(op in tool.name.lower() for op in ["list", "search"]), (
73+
f"Tool {tool.name} doesn't contain 'list' or 'search'"
74+
)
75+
# Should not match negative patterns
76+
assert not any(op in tool.name.lower() for op in ["delete", "remove"]), (
77+
f"Tool {tool.name} contains excluded operation"
78+
)
1379

1480

1581
if __name__ == "__main__":
16-
print(get_available_tools())
82+
get_available_tools()

examples/crewai_integration.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@
1717

1818
def crewai_integration():
1919
toolset = StackOneToolSet()
20-
tools = toolset.get_tools(
21-
vertical="hris",
22-
account_id=account_id,
23-
)
20+
tools = toolset.get_tools("hris_*", account_id=account_id)
2421

2522
# CrewAI uses LangChain tools natively
2623
langchain_tools = tools.to_langchain()
24+
assert len(langchain_tools) > 0, "Expected at least one LangChain tool"
25+
26+
for tool in langchain_tools:
27+
assert hasattr(tool, "name"), "Expected tool to have name"
28+
assert hasattr(tool, "description"), "Expected tool to have description"
29+
assert hasattr(tool, "_run"), "Expected tool to have _run method"
2730

2831
agent = Agent(
2932
role="HR Manager",
@@ -42,7 +45,9 @@ def crewai_integration():
4245
)
4346

4447
crew = Crew(agents=[agent], tasks=[task])
45-
print(crew.kickoff())
48+
49+
result = crew.kickoff()
50+
assert result is not None, "Expected result to be returned"
4651

4752

4853
if __name__ == "__main__":

examples/error_handling.py

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,43 +20,45 @@
2020

2121
def error_handling() -> None:
2222
# Example 1: Configuration error - missing API key
23+
original_api_key = os.environ.pop("STACKONE_API_KEY", None)
2324
try:
24-
print("\n1. Testing configuration error (missing API key)...")
25-
original_api_key = os.environ.pop("STACKONE_API_KEY", None)
2625
try:
2726
StackOneToolSet(api_key=None)
2827
raise AssertionError("Expected ToolsetConfigError")
29-
finally:
30-
if original_api_key:
31-
os.environ["STACKONE_API_KEY"] = original_api_key
32-
except ToolsetConfigError as e:
33-
print("✗ Config Error:", e)
28+
except ToolsetConfigError as e:
29+
assert (
30+
str(e)
31+
== "API key must be provided either through api_key parameter or STACKONE_API_KEY environment variable"
32+
)
33+
finally:
34+
if original_api_key:
35+
os.environ["STACKONE_API_KEY"] = original_api_key
3436

3537
# Example 2: Invalid vertical error
38+
toolset = StackOneToolSet()
3639
try:
37-
print("\n2. Testing invalid vertical...")
38-
toolset = StackOneToolSet()
39-
toolset.get_tools(vertical="invalid_vertical")
40-
raise AssertionError("Expected ToolsetLoadError")
40+
# Use a non-existent vertical to trigger error
41+
tools = toolset.get_tools("nonexistent_vertical_*")
42+
# If we get here, no tools were found but no error was raised
43+
assert len(tools) == 0, "Expected no tools for nonexistent vertical"
4144
except ToolsetLoadError as e:
42-
print("✗ Load Error:", e)
45+
assert "Error loading tools" in str(e)
4346

4447
# Example 3: API error - invalid request
45-
try:
46-
print("\n3. Testing API error...")
47-
toolset = StackOneToolSet()
48-
tools = toolset.get_tools(vertical="crm")
48+
toolset = StackOneToolSet()
49+
tools = toolset.get_tools("crm_*")
4950

50-
# Try to make an API call without required parameters
51-
list_contacts = tools.get_tool("crm_list_contacts")
52-
assert list_contacts is not None, "Expected crm_list_contacts tool to exist"
51+
# Try to make an API call without required parameters
52+
list_contacts = tools.get_tool("crm_list_contacts")
53+
assert list_contacts is not None, "Expected crm_list_contacts tool to exist"
5354

54-
list_contacts.execute()
55+
try:
56+
# Execute without required parameters should raise error
57+
list_contacts.execute({})
5558
raise AssertionError("Expected StackOneAPIError")
5659
except StackOneAPIError as e:
57-
print("✗ API Error:", e)
58-
print(" Status:", e.status_code)
59-
print(" Response:", e.response_body)
60+
assert e.status_code >= 400, "Expected error status code"
61+
assert e.response_body is not None, "Expected error response body"
6062

6163

6264
if __name__ == "__main__":

examples/file_uploads.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def upload_employee_document() -> None:
4444
resume_file.write_text(resume_content)
4545

4646
toolset = StackOneToolSet()
47-
tools = toolset.get_tools(vertical="hris", account_id=account_id)
47+
tools = toolset.get_tools("hris_*", account_id=account_id)
4848

4949
upload_tool = tools.get_tool("hris_upload_employee_document")
5050
assert upload_tool is not None

examples/index.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@
5151
def quickstart():
5252
toolset = StackOneToolSet()
5353

54-
# Supply a StackOne Account ID
55-
tools = toolset.get_tools(account_id=account_id)
54+
# Get all HRIS-related tools
55+
tools = toolset.get_tools("hris_*", account_id=account_id)
5656

5757
# Use a specific tool
5858
employee_tool = tools.get_tool("hris_get_employee")

examples/langchain_integration.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,20 @@
1818

1919
def langchain_integration() -> None:
2020
toolset = StackOneToolSet()
21-
tools = toolset.get_tools(vertical="hris", account_id=account_id)
21+
tools = toolset.get_tools("hris_*", account_id=account_id)
2222

23+
# Convert to LangChain format and verify
2324
langchain_tools = tools.to_langchain()
25+
assert len(langchain_tools) > 0, "Expected at least one LangChain tool"
2426

27+
# Verify tool structure
28+
for tool in langchain_tools:
29+
assert hasattr(tool, "name"), "Expected tool to have name"
30+
assert hasattr(tool, "description"), "Expected tool to have description"
31+
assert hasattr(tool, "_run"), "Expected tool to have _run method"
32+
assert hasattr(tool, "args_schema"), "Expected tool to have args_schema"
33+
34+
# Create model with tools
2535
model = ChatOpenAI(model="gpt-4o-mini")
2636
model_with_tools = model.bind_tools(langchain_tools)
2737

examples/langgraph_tool_node.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,37 @@
11
"""
2+
TODO!!
3+
24
This example demonstrates how to use StackOne tools with LangGraph.
35
46
```bash
57
uv run examples/langgraph_tool_node.py
68
```
79
"""
810

11+
from dotenv import load_dotenv
12+
from stackone_ai import StackOneToolSet
13+
14+
load_dotenv()
15+
16+
account_id = "45072196112816593343"
17+
employee_id = "c28xIQaWQ6MzM5MzczMDA2NzMzMzkwNzIwNA"
18+
919

10-
# TODO: Add examples
1120
def langgraph_tool_node() -> None:
12-
print("LangGraph tool node")
21+
"""Demonstrate basic LangGraph integration with StackOne tools."""
22+
toolset = StackOneToolSet()
23+
tools = toolset.get_tools("hris_*", account_id=account_id)
24+
25+
# Verify we have the tools we need
26+
assert len(tools) > 0, "Expected at least one HRIS tool"
27+
employee_tool = tools.get_tool("hris_get_employee")
28+
assert employee_tool is not None, "Expected hris_get_employee tool"
29+
30+
# TODO: Add LangGraph specific integration
31+
# For now, just verify the tools are properly configured
32+
langchain_tools = tools.to_langchain()
33+
assert len(langchain_tools) > 0, "Expected LangChain tools"
34+
assert all(hasattr(tool, "_run") for tool in langchain_tools), "Expected all tools to have _run method"
1335

1436

1537
if __name__ == "__main__":

examples/openai_integration.py

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,15 @@ def openai_integration() -> None:
3232
client = OpenAI()
3333
toolset = StackOneToolSet()
3434

35-
all_tools = toolset.get_tools(vertical="hris", account_id=account_id)
36-
37-
needed_tool_names = [
38-
"hris_get_employee",
39-
"hris_list_employee_employments",
40-
"hris_get_employee_employment",
41-
]
42-
43-
# Filter tools to only the ones we need
44-
# We need this because otherwise we can go over a context window limit
45-
# TODO: better filtering options.
46-
filtered_tools = [tool for tool in all_tools.tools if tool.name in needed_tool_names]
47-
tools = type(all_tools)(filtered_tools)
35+
# Filter tools to only the ones we need to avoid context window limits
36+
tools = toolset.get_tools(
37+
[
38+
"hris_get_employee",
39+
"hris_list_employee_employments",
40+
"hris_get_employee_employment",
41+
],
42+
account_id=account_id,
43+
)
4844
openai_tools = tools.to_openai()
4945

5046
messages = [
@@ -55,31 +51,41 @@ def openai_integration() -> None:
5551
},
5652
]
5753

58-
while True:
59-
response = client.chat.completions.create(
60-
model="gpt-4o-mini",
61-
messages=messages,
62-
tools=openai_tools,
63-
tool_choice="auto",
64-
)
65-
66-
if not response.choices[0].message.tool_calls:
67-
print("Response:", response.choices[0].message.content)
68-
break
69-
70-
results = handle_tool_calls(tools, response.choices[0].message.tool_calls)
71-
assert results is not None
72-
73-
messages.extend(
74-
[
75-
{"role": "assistant", "content": None, "tool_calls": response.choices[0].message.tool_calls},
76-
{
77-
"role": "tool",
78-
"tool_call_id": response.choices[0].message.tool_calls[0].id,
79-
"content": str(results[0]),
80-
},
81-
]
82-
)
54+
response = client.chat.completions.create(
55+
model="gpt-4o-mini",
56+
messages=messages,
57+
tools=openai_tools,
58+
tool_choice="auto",
59+
)
60+
61+
# Verify we got a response with tool calls
62+
assert response.choices[0].message.tool_calls is not None, "Expected tool calls in response"
63+
64+
# Handle the tool calls and verify results
65+
results = handle_tool_calls(tools, response.choices[0].message.tool_calls)
66+
assert results is not None and len(results) > 0, "Expected tool call results"
67+
assert "data" in results[0], "Expected data in tool call result"
68+
69+
# Verify we can continue the conversation with the results
70+
messages.extend(
71+
[
72+
{"role": "assistant", "content": None, "tool_calls": response.choices[0].message.tool_calls},
73+
{
74+
"role": "tool",
75+
"tool_call_id": response.choices[0].message.tool_calls[0].id,
76+
"content": str(results[0]),
77+
},
78+
]
79+
)
80+
81+
# Verify the final response
82+
final_response = client.chat.completions.create(
83+
model="gpt-4o-mini",
84+
messages=messages,
85+
tools=openai_tools,
86+
tool_choice="auto",
87+
)
88+
assert final_response.choices[0].message.content is not None, "Expected final response content"
8389

8490

8591
if __name__ == "__main__":

examples/stackone_account_ids.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
def stackone_account_ids():
1616
toolset = StackOneToolSet()
1717

18-
# Filter by vertical and set the account ID
19-
tools = toolset.get_tools(vertical="hris", account_id="test_id")
18+
# Filter by pattern and set the account ID
19+
tools = toolset.get_tools("hris_*", account_id="test_id")
2020

2121
# You can over write the account ID here..
2222
tools.set_account_id("a_different_id")
2323

24-
employee_tool = tools.get_tool("get_employee")
24+
employee_tool = tools.get_tool("hris_get_employee")
2525
assert employee_tool is not None
2626

2727
# You can even set the account ID on a per-tool basis

0 commit comments

Comments
 (0)