Skip to content

Commit caac58d

Browse files
committed
Phase 3: langchain/langgraph integration
1 parent 54c4324 commit caac58d

File tree

11 files changed

+906
-20
lines changed

11 files changed

+906
-20
lines changed

examples/lang-chain/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
### LangChain / LangGraph examples (Python)
2+
3+
These examples show how to use Sentience as a **tool layer** inside LangChain and LangGraph.
4+
5+
Install:
6+
7+
```bash
8+
pip install sentienceapi[langchain]
9+
```
10+
11+
Examples:
12+
- `langchain_tools_demo.py`: build a Sentience tool pack for LangChain
13+
- `langgraph_self_correcting_graph.py`: observe → act → verify → branch (retry) template
14+
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""
2+
Example: Build Sentience LangChain tools (async-only).
3+
4+
Install:
5+
pip install sentienceapi[langchain]
6+
7+
Run:
8+
python examples/lang-chain/langchain_tools_demo.py
9+
10+
Notes:
11+
- This example focuses on creating the tools. Hook them into your agent of choice.
12+
"""
13+
14+
from __future__ import annotations
15+
16+
import asyncio
17+
18+
from sentience import AsyncSentienceBrowser
19+
from sentience.integrations.langchain import SentienceLangChainContext, build_sentience_langchain_tools
20+
21+
22+
async def main() -> None:
23+
browser = AsyncSentienceBrowser(headless=False)
24+
await browser.start()
25+
await browser.goto("https://example.com")
26+
27+
ctx = SentienceLangChainContext(browser=browser)
28+
tools = build_sentience_langchain_tools(ctx)
29+
30+
print("Registered tools:")
31+
for t in tools:
32+
print(f"- {t.name}")
33+
34+
await browser.close()
35+
36+
37+
if __name__ == "__main__":
38+
asyncio.run(main())
39+
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""
2+
LangGraph reference example: Sentience observe → act → verify → branch (self-correcting).
3+
4+
Install:
5+
pip install sentienceapi[langchain]
6+
7+
Run:
8+
python examples/lang-chain/langgraph_self_correcting_graph.py
9+
"""
10+
11+
from __future__ import annotations
12+
13+
import asyncio
14+
from dataclasses import dataclass
15+
16+
from sentience import AsyncSentienceBrowser
17+
from sentience.integrations.langchain import SentienceLangChainContext, SentienceLangChainCore
18+
19+
20+
@dataclass
21+
class State:
22+
url: str | None = None
23+
last_action: str | None = None
24+
attempts: int = 0
25+
done: bool = False
26+
27+
28+
async def main() -> None:
29+
from langgraph.graph import END, StateGraph
30+
31+
browser = AsyncSentienceBrowser(headless=False)
32+
await browser.start()
33+
34+
core = SentienceLangChainCore(SentienceLangChainContext(browser=browser))
35+
36+
async def observe(state: State) -> State:
37+
s = await core.snapshot_state()
38+
state.url = s.url
39+
return state
40+
41+
async def act(state: State) -> State:
42+
# Replace with an LLM decision node. For demo we just navigate once.
43+
if state.attempts == 0:
44+
await core.navigate("https://example.com")
45+
state.last_action = "navigate"
46+
else:
47+
state.last_action = "noop"
48+
state.attempts += 1
49+
return state
50+
51+
async def verify(state: State) -> State:
52+
out = await core.verify_url_matches(r"example\.com")
53+
state.done = bool(out.passed)
54+
return state
55+
56+
def branch(state: State) -> str:
57+
if state.done:
58+
return "done"
59+
if state.attempts >= 3:
60+
return "done"
61+
return "retry"
62+
63+
g = StateGraph(State)
64+
g.add_node("observe", observe)
65+
g.add_node("act", act)
66+
g.add_node("verify", verify)
67+
g.set_entry_point("observe")
68+
g.add_edge("observe", "act")
69+
g.add_edge("act", "verify")
70+
g.add_conditional_edges("verify", branch, {"retry": "observe", "done": END})
71+
app = g.compile()
72+
73+
final = await app.ainvoke(State())
74+
print(final)
75+
76+
await browser.close()
77+
78+
79+
if __name__ == "__main__":
80+
asyncio.run(main())
81+
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"""
2+
LangGraph reference example: Sentience observe → act → verify → branch (self-correcting).
3+
4+
Install:
5+
pip install sentienceapi[langchain]
6+
7+
Run:
8+
python examples/langgraph/sentience_self_correcting_graph.py
9+
10+
Notes:
11+
- This is a template demonstrating control flow; you can replace the "decide" node
12+
with an LLM step (LangChain) that chooses actions based on snapshot_state/read_page.
13+
"""
14+
15+
from __future__ import annotations
16+
17+
import asyncio
18+
from dataclasses import dataclass
19+
from typing import Optional
20+
21+
from sentience import AsyncSentienceBrowser
22+
from sentience.integrations.langchain import SentienceLangChainContext, SentienceLangChainCore
23+
24+
25+
@dataclass
26+
class State:
27+
url: str | None = None
28+
last_action: str | None = None
29+
attempts: int = 0
30+
done: bool = False
31+
32+
33+
async def main() -> None:
34+
# Lazy import so the file can exist without langgraph installed
35+
from langgraph.graph import END, StateGraph
36+
37+
browser = AsyncSentienceBrowser(headless=False)
38+
await browser.start()
39+
40+
core = SentienceLangChainCore(SentienceLangChainContext(browser=browser))
41+
42+
async def observe(state: State) -> State:
43+
s = await core.snapshot_state()
44+
state.url = s.url
45+
return state
46+
47+
async def act(state: State) -> State:
48+
# Replace this with an LLM-driven decision. For demo purposes, we just navigate once.
49+
if state.attempts == 0:
50+
await core.navigate("https://example.com")
51+
state.last_action = "navigate"
52+
else:
53+
state.last_action = "noop"
54+
state.attempts += 1
55+
return state
56+
57+
async def verify(state: State) -> State:
58+
# Guard condition: URL should contain example.com
59+
out = await core.verify_url_matches(r"example\.com")
60+
state.done = bool(out.passed)
61+
return state
62+
63+
def should_continue(state: State) -> str:
64+
# Self-correcting loop: retry observe→act→verify up to 3 attempts
65+
if state.done:
66+
return "done"
67+
if state.attempts >= 3:
68+
return "done"
69+
return "retry"
70+
71+
g = StateGraph(State)
72+
g.add_node("observe", observe)
73+
g.add_node("act", act)
74+
g.add_node("verify", verify)
75+
g.set_entry_point("observe")
76+
g.add_edge("observe", "act")
77+
g.add_edge("act", "verify")
78+
g.add_conditional_edges("verify", should_continue, {"retry": "observe", "done": END})
79+
app = g.compile()
80+
81+
final = await app.ainvoke(State())
82+
print(final)
83+
84+
await browser.close()
85+
86+
87+
if __name__ == "__main__":
88+
asyncio.run(main())
89+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""
2+
LangChain / LangGraph integration helpers (optional).
3+
4+
This package is designed so the base SDK can be imported without LangChain installed.
5+
All LangChain imports are done lazily inside tool-builder functions.
6+
"""
7+
8+
from .context import SentienceLangChainContext
9+
from .core import SentienceLangChainCore
10+
from .tools import build_sentience_langchain_tools
11+
12+
__all__ = ["SentienceLangChainContext", "SentienceLangChainCore", "build_sentience_langchain_tools"]
13+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass
4+
5+
from sentience.browser import AsyncSentienceBrowser
6+
from sentience.tracing import Tracer
7+
8+
9+
@dataclass
10+
class SentienceLangChainContext:
11+
"""
12+
Context for LangChain/LangGraph integrations.
13+
14+
We keep this small and explicit; it mirrors the PydanticAI deps object.
15+
"""
16+
17+
browser: AsyncSentienceBrowser
18+
tracer: Tracer | None = None
19+

0 commit comments

Comments
 (0)