Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

StackOne AI SDK is a Python library that provides a unified interface for accessing various SaaS tools through AI-friendly APIs. It acts as a bridge between AI applications and multiple SaaS platforms (HRIS, CRM, ATS, LMS, Marketing, etc.) with support for OpenAI, LangChain, CrewAI, and Model Context Protocol (MCP).
StackOne AI SDK is a Python library that provides a unified interface for accessing various SaaS tools through AI-friendly APIs. It acts as a bridge between AI applications and multiple SaaS platforms (HRIS, CRM, ATS, LMS, Marketing, etc.) with support for Agno, OpenAI, LangChain, CrewAI, and Model Context Protocol (MCP).

## Essential Development Commands

Expand Down Expand Up @@ -50,7 +50,7 @@ make mcp-inspector # Run MCP server inspector for debugging
2. **Models** (`stackone_ai/models.py`): Data structures
- `StackOneTool`: Base class with execution logic
- `Tools`: Container for managing multiple tools
- Format converters for different AI frameworks
- Format converters for different AI frameworks (Agno, OpenAI, LangChain)

3. **OpenAPI Parser** (`stackone_ai/specs/parser.py`): Spec conversion
- Converts OpenAPI specs to tool definitions
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ result = execute_tool.call(toolName="hris_list_employees", params={"limit": 10})
- **Glob Pattern Filtering**: Advanced tool filtering with patterns like `"hris_*"` and exclusions `"!hris_delete_*"`
- **Meta Tools** (Beta): Dynamic tool discovery and execution based on natural language queries
- Integration with popular AI frameworks:
- Agno Agents
- OpenAI Functions
- LangChain Tools
- CrewAI Tools
Expand All @@ -72,10 +73,11 @@ For more examples and documentation, visit:

## AI Framework Integration

- [OpenAI Integration](docs/openai-integration.md)
- [LangChain Integration](docs/langchain-integration.md)
- [CrewAI Integration](docs/crewai-integration.md)
- [LangGraph Tool Node](docs/langgraph-tool-node.md)
- [Agno Integration](docs/agno_integration.md)
- [OpenAI Integration](docs/openai_integration.md)
- [LangChain Integration](docs/langchain_integration.md)
- [CrewAI Integration](docs/crewai_integration.md)
- [LangGraph Tool Node](docs/langgraph_tool_node.md)

## License

Expand Down
119 changes: 119 additions & 0 deletions examples/agno_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""
This example demonstrates how to use StackOne tools with Agno agents.

This example is runnable with the following command:
```bash
uv run examples/agno_integration.py
```

You can find out more about Agno framework at https://docs.agno.com
"""

from agno.agent import Agent
from agno.models.openai import OpenAIChat
from dotenv import load_dotenv

from stackone_ai import StackOneToolSet

load_dotenv()

account_id = "45072196112816593343"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid hard-coding potentially sensitive account and employee identifiers; read them from environment variables or user input instead.

Prompt for AI agents
Address the following comment on examples/agno_integration.py at line 20:

<comment>Avoid hard-coding potentially sensitive account and employee identifiers; read them from environment variables or user input instead.</comment>

<file context>
@@ -0,0 +1,119 @@
+&quot;&quot;&quot;
+This example demonstrates how to use StackOne tools with Agno agents.
+
+This example is runnable with the following command:
+```bash
+uv run examples/agno_integration.py
+```
+
+You can find out more about Agno framework at https://docs.agno.com
</file context>

employee_id = "c28xIQaWQ6MzM5MzczMDA2NzMzMzkwNzIwNA"


def agno_integration() -> None:
"""Demonstrate StackOne tools with Agno agents"""
toolset = StackOneToolSet()

# Filter tools to only the ones we need to avoid context window limits
tools = toolset.get_tools(
[
"hris_get_employee",
"hris_list_employee_employments",
"hris_get_employee_employment",
],
account_id=account_id,
)

# Convert to Agno format
agno_tools = tools.to_agno()

# Create an Agno agent with the tools
agent = Agent(
name="HR Assistant Agent",
role="Helpful HR assistant that can access employee data",
model=OpenAIChat(id="gpt-4o-mini"),
tools=agno_tools,
instructions=[
"You are a helpful HR assistant.",
"Use the provided tools to access employee information.",
"Always be professional and respectful when handling employee data.",
],
show_tool_calls=True,
markdown=True,
)

# Test the agent with a query
query = f"Can you get me information about employee with ID: {employee_id}?"

print(f"Query: {query}")
print("Agent response:")

response = agent.run(query)
print(response.content)

# Verify we got a meaningful response
assert response.content is not None, "Expected response content"
assert len(response.content) > 0, "Expected non-empty response"


def agno_async_integration() -> None:
"""Demonstrate async StackOne tools with Agno agents"""
import asyncio

async def run_async_agent() -> None:
toolset = StackOneToolSet()

# Filter tools to only the ones we need
tools = toolset.get_tools(
["hris_get_employee"],
account_id=account_id,
)

# Convert to Agno format
agno_tools = tools.to_agno()

# Create an Agno agent
agent = Agent(
name="Async HR Agent",
role="Async HR assistant",
model=OpenAIChat(id="gpt-4o-mini"),
tools=agno_tools,
instructions=["You are an async HR assistant."],
)

# Run the agent asynchronously
query = f"Get employee information for ID: {employee_id}"
response = await agent.arun(query)

print(f"Async query: {query}")
print("Async agent response:")
print(response.content)

# Verify response
assert response.content is not None, "Expected async response content"

# Run the async example
asyncio.run(run_async_agent())


if __name__ == "__main__":
print("=== StackOne + Agno Integration Demo ===\n")

print("1. Basic Agno Integration:")
agno_integration()

print("\n2. Async Agno Integration:")
agno_async_integration()

print("\n=== Demo completed successfully! ===")
7 changes: 4 additions & 3 deletions examples/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def get_example_files() -> list[str]:

# Map of example files to required optional packages
OPTIONAL_DEPENDENCIES = {
"agno_integration.py": ["agno"],
"openai_integration.py": ["openai"],
"langchain_integration.py": ["langchain_openai"],
"crewai_integration.py": ["crewai"],
Expand Down Expand Up @@ -60,19 +61,19 @@ def test_run_example(example_file: str) -> None:
"id": "test-employee-1",
"first_name": "John",
"last_name": "Doe",
"email": "[email protected]"
"email": "[email protected]",
}
]
},
status=200
status=200,
)

# Mock document upload endpoint
responses.add(
responses.POST,
"https://api.stackone.com/unified/hris/employees/c28xIQaWQ6MzM5MzczMDA2NzMzMzkwNzIwNA/documents/upload",
json={"success": True, "document_id": "test-doc-123"},
status=200
status=200,
)

example_path = Path(__file__).parent / example_file
Expand Down
7 changes: 4 additions & 3 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ nav:
- Available Tools: available-tools.md
- File Uploads: file-uploads.md
- Integrations:
- OpenAI: openai-integration.md
- CrewAI: crewai-integration.md
- LangChain: langchain-integration.md
- Agno: agno_integration.md
- OpenAI: openai_integration.md
- CrewAI: crewai_integration.md
- LangChain: langchain_integration.md
13 changes: 13 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ packages = ["stackone_ai"]

[project.optional-dependencies]
examples = [
"agno>=1.7.0",
"crewai>=0.102.0",
"langchain-openai>=0.3.6",
"openai>=1.63.2",
Expand Down Expand Up @@ -110,3 +111,15 @@ warn_unreachable = true
[[tool.mypy.overrides]]
module = "bm25s"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "agno.tools"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "agno.agent"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "agno.models.openai"
ignore_missing_imports = true
36 changes: 36 additions & 0 deletions stackone_ai/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,34 @@ async def _arun(self, **kwargs: Any) -> Any:

return StackOneLangChainTool()

def to_agno(self) -> Any:
"""Convert this tool to Agno format

Returns:
Function callable in Agno format
"""
try:
from agno.tools import tool
except ImportError as e:
raise ImportError(
"Agno is not installed. Please install it with 'pip install agno>=1.7.0' "
"or add 'agno>=1.7.0' to your requirements."
) from e

parent_tool = self

def agno_tool_function(**kwargs: Any) -> JsonDict:
"""Execute the StackOne tool with the provided arguments"""
return parent_tool.execute(kwargs)

# Apply Agno tool decorator with metadata
decorated_tool = tool(
name=parent_tool.name,
description=parent_tool.description,
)(agno_tool_function)

return decorated_tool

def set_account_id(self, account_id: str | None) -> None:
"""Set the account ID for this tool

Expand Down Expand Up @@ -480,6 +508,14 @@ def to_langchain(self) -> Sequence[BaseTool]:
"""
return [tool.to_langchain() for tool in self.tools]

def to_agno(self) -> list[Any]:
"""Convert all tools to Agno format

Returns:
List of tools in Agno format
"""
return [tool.to_agno() for tool in self.tools]

def meta_tools(self) -> "Tools":
"""Return meta tools for tool discovery and execution

Expand Down
Loading