Skip to content

Commit 3dfb2e2

Browse files
committed
feat(integrations): add LangGraph helpers and example
- Add stackone_ai.integrations.langgraph with: - to_tool_node and to_tool_executor - bind_model_with_tools and create_react_agent - Update example examples/langgraph_tool_node.py - Add README section with small agent loop - Include langgraph in examples extras
1 parent 3c5d8fb commit 3dfb2e2

File tree

5 files changed

+172
-8
lines changed

5 files changed

+172
-8
lines changed

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,52 @@ for tool_call in response.tool_calls:
110110

111111
</details>
112112

113+
<details>
114+
<summary>LangGraph Integration</summary>
115+
116+
StackOne tools convert to LangChain tools, which LangGraph consumes via its prebuilt nodes:
117+
118+
```python
119+
from langchain_openai import ChatOpenAI
120+
from typing import Annotated
121+
from typing_extensions import TypedDict
122+
123+
from langgraph.graph import StateGraph, START, END
124+
from langgraph.graph.message import add_messages
125+
from langgraph.prebuilt import tools_condition
126+
127+
from stackone_ai import StackOneToolSet
128+
from stackone_ai.integrations.langgraph import to_tool_node, bind_model_with_tools
129+
130+
# Prepare tools
131+
toolset = StackOneToolSet()
132+
tools = toolset.get_tools("hris_*", account_id="your-account-id")
133+
langchain_tools = tools.to_langchain()
134+
135+
class State(TypedDict):
136+
messages: Annotated[list, add_messages]
137+
138+
# Build a small agent loop: LLM -> maybe tools -> back to LLM
139+
graph = StateGraph(State)
140+
graph.add_node("tools", to_tool_node(langchain_tools))
141+
142+
def call_llm(state: dict):
143+
llm = ChatOpenAI(model="gpt-4o-mini")
144+
llm = bind_model_with_tools(llm, langchain_tools)
145+
resp = llm.invoke(state["messages"]) # returns AIMessage with optional tool_calls
146+
return {"messages": state["messages"] + [resp]}
147+
148+
graph.add_node("llm", call_llm)
149+
graph.add_edge(START, "llm")
150+
graph.add_conditional_edges("llm", tools_condition)
151+
graph.add_edge("tools", "llm")
152+
app = graph.compile()
153+
154+
_ = app.invoke({"messages": [("user", "Get employee with id emp123") ]})
155+
```
156+
157+
</details>
158+
113159
<details>
114160
<summary>CrewAI Integration (Python 3.10+)</summary>
115161

examples/langgraph_tool_node.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
"""
2-
TODO!!
3-
42
This example demonstrates how to use StackOne tools with LangGraph.
53
6-
```bash
7-
uv run examples/langgraph_tool_node.py
8-
```
4+
Run:
5+
6+
uv run examples/langgraph_tool_node.py
7+
8+
Notes:
9+
- Requires `langgraph` installed (included in `stackone-ai[examples]`).
910
"""
1011

1112
from dotenv import load_dotenv
1213

1314
from stackone_ai import StackOneToolSet
15+
from stackone_ai.integrations.langgraph import to_tool_executor, to_tool_node
1416

1517
load_dotenv()
1618

@@ -28,11 +30,17 @@ def langgraph_tool_node() -> None:
2830
employee_tool = tools.get_tool("hris_get_employee")
2931
assert employee_tool is not None, "Expected hris_get_employee tool"
3032

31-
# TODO: Add LangGraph specific integration
32-
# For now, just verify the tools are properly configured
33+
# Convert to LangChain tools (LangGraph consumes these)
3334
langchain_tools = tools.to_langchain()
3435
assert len(langchain_tools) > 0, "Expected LangChain tools"
35-
assert all(hasattr(tool, "_run") for tool in langchain_tools), "Expected all tools to have _run method"
36+
37+
# Build LangGraph prebuilt components
38+
node = to_tool_node(tools) # or to_tool_node(langchain_tools)
39+
executor = to_tool_executor(tools)
40+
41+
# Smoke checks: ToolNode is callable and ToolExecutor has `invoke`
42+
assert callable(node), "ToolNode should be callable in a graph"
43+
assert hasattr(executor, "invoke"), "ToolExecutor should expose invoke()"
3644

3745

3846
if __name__ == "__main__":

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ mcp = [
5353
examples = [
5454
"crewai>=0.102.0; python_version>='3.10'",
5555
"langchain-openai>=0.3.6",
56+
"langgraph>=0.2.0",
5657
"openai>=1.63.2",
5758
"python-dotenv>=1.0.1",
5859
]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Integration helpers for external frameworks.
2+
3+
Currently includes:
4+
5+
- LangGraph helpers to turn StackOne tools into a `ToolNode` or `ToolExecutor`.
6+
"""
7+
8+
from .langgraph import (
9+
bind_model_with_tools,
10+
create_react_agent,
11+
to_tool_executor,
12+
to_tool_node,
13+
)
14+
15+
__all__ = [
16+
"to_tool_node",
17+
"to_tool_executor",
18+
"bind_model_with_tools",
19+
"create_react_agent",
20+
]
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"""LangGraph integration helpers.
2+
3+
These utilities convert StackOne tools into LangGraph prebuilt components.
4+
5+
Usage:
6+
from stackone_ai import StackOneToolSet
7+
from stackone_ai.integrations.langgraph import to_tool_node
8+
9+
toolset = StackOneToolSet()
10+
tools = toolset.get_tools("hris_*", account_id="...")
11+
node = to_tool_node(tools) # langgraph.prebuilt.ToolNode
12+
"""
13+
14+
from __future__ import annotations
15+
16+
from collections.abc import Sequence
17+
from typing import TYPE_CHECKING, Any
18+
19+
from langchain_core.tools import BaseTool
20+
21+
from stackone_ai.models import Tools
22+
23+
if TYPE_CHECKING: # pragma: no cover - only for typing
24+
try:
25+
from langgraph.prebuilt import ToolExecutor, ToolNode
26+
except Exception: # pragma: no cover
27+
ToolExecutor = Any
28+
ToolNode = Any
29+
30+
31+
def _ensure_langgraph() -> None:
32+
try:
33+
from langgraph import prebuilt as _ # noqa: F401
34+
except Exception as e: # pragma: no cover
35+
raise ImportError(
36+
"LangGraph is not installed. Install with `pip install langgraph` or "
37+
"`pip install 'stackone-ai[examples]'`"
38+
) from e
39+
40+
41+
def _to_langchain_tools(tools: Tools | Sequence[BaseTool]) -> Sequence[BaseTool]:
42+
if isinstance(tools, Tools):
43+
return tools.to_langchain()
44+
return tools
45+
46+
47+
def to_tool_node(tools: Tools | Sequence[BaseTool], **kwargs: Any) -> Any:
48+
"""Create a LangGraph `ToolNode` from StackOne tools or LangChain tools.
49+
50+
Accepts either a `Tools` collection from this SDK or an existing sequence of
51+
LangChain `BaseTool` instances and returns a LangGraph `ToolNode` suitable
52+
for inclusion in a graph.
53+
"""
54+
_ensure_langgraph()
55+
from langgraph.prebuilt import ToolNode # local import with helpful error
56+
57+
langchain_tools = _to_langchain_tools(tools)
58+
return ToolNode(langchain_tools, **kwargs)
59+
60+
61+
def to_tool_executor(tools: Tools | Sequence[BaseTool], **kwargs: Any) -> Any:
62+
"""Create a LangGraph `ToolExecutor` from StackOne tools or LangChain tools."""
63+
_ensure_langgraph()
64+
from langgraph.prebuilt import ToolExecutor # local import with helpful error
65+
66+
langchain_tools = _to_langchain_tools(tools)
67+
return ToolExecutor(langchain_tools, **kwargs)
68+
69+
70+
def bind_model_with_tools(model: Any, tools: Tools | Sequence[BaseTool]) -> Any:
71+
"""Bind tools to an LLM that supports LangChain's `.bind_tools()` API.
72+
73+
This is a tiny helper that converts a `Tools` collection to LangChain tools
74+
and calls `model.bind_tools(...)`.
75+
"""
76+
langchain_tools = _to_langchain_tools(tools)
77+
return model.bind_tools(langchain_tools)
78+
79+
80+
def create_react_agent(llm: Any, tools: Tools | Sequence[BaseTool], **kwargs: Any) -> Any:
81+
"""Create a LangGraph ReAct agent using StackOne tools.
82+
83+
Thin wrapper around `langgraph.prebuilt.create_react_agent` that accepts a
84+
`Tools` collection from this SDK.
85+
"""
86+
_ensure_langgraph()
87+
from langgraph.prebuilt import create_react_agent as _create
88+
89+
return _create(llm, _to_langchain_tools(tools), **kwargs)

0 commit comments

Comments
 (0)