Skip to content
Merged
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
13 changes: 13 additions & 0 deletions examples/lang-chain/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
### LangChain / LangGraph examples (Python)

These examples show how to use Sentience as a **tool layer** inside LangChain and LangGraph.

Install:

```bash
pip install sentienceapi[langchain]
```

Examples:
- `langchain_tools_demo.py`: build a Sentience tool pack for LangChain
- `langgraph_self_correcting_graph.py`: observe → act → verify → branch (retry) template
41 changes: 41 additions & 0 deletions examples/lang-chain/langchain_tools_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
Example: Build Sentience LangChain tools (async-only).

Install:
pip install sentienceapi[langchain]

Run:
python examples/lang-chain/langchain_tools_demo.py

Notes:
- This example focuses on creating the tools. Hook them into your agent of choice.
"""

from __future__ import annotations

import asyncio

from sentience import AsyncSentienceBrowser
from sentience.integrations.langchain import (
SentienceLangChainContext,
build_sentience_langchain_tools,
)


async def main() -> None:
browser = AsyncSentienceBrowser(headless=False)
await browser.start()
await browser.goto("https://example.com")

ctx = SentienceLangChainContext(browser=browser)
tools = build_sentience_langchain_tools(ctx)

print("Registered tools:")
for t in tools:
print(f"- {t.name}")

await browser.close()


if __name__ == "__main__":
asyncio.run(main())
80 changes: 80 additions & 0 deletions examples/lang-chain/langgraph_self_correcting_graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""
LangGraph reference example: Sentience observe → act → verify → branch (self-correcting).

Install:
pip install sentienceapi[langchain]

Run:
python examples/lang-chain/langgraph_self_correcting_graph.py
"""

from __future__ import annotations

import asyncio
from dataclasses import dataclass

from sentience import AsyncSentienceBrowser
from sentience.integrations.langchain import SentienceLangChainContext, SentienceLangChainCore


@dataclass
class State:
url: str | None = None
last_action: str | None = None
attempts: int = 0
done: bool = False


async def main() -> None:
from langgraph.graph import END, StateGraph

browser = AsyncSentienceBrowser(headless=False)
await browser.start()

core = SentienceLangChainCore(SentienceLangChainContext(browser=browser))

async def observe(state: State) -> State:
s = await core.snapshot_state()
state.url = s.url
return state

async def act(state: State) -> State:
# Replace with an LLM decision node. For demo we just navigate once.
if state.attempts == 0:
await core.navigate("https://example.com")
state.last_action = "navigate"
else:
state.last_action = "noop"
state.attempts += 1
return state

async def verify(state: State) -> State:
out = await core.verify_url_matches(r"example\.com")
state.done = bool(out.passed)
return state

def branch(state: State) -> str:
if state.done:
return "done"
if state.attempts >= 3:
return "done"
return "retry"

g = StateGraph(State)
g.add_node("observe", observe)
g.add_node("act", act)
g.add_node("verify", verify)
g.set_entry_point("observe")
g.add_edge("observe", "act")
g.add_edge("act", "verify")
g.add_conditional_edges("verify", branch, {"retry": "observe", "done": END})
app = g.compile()

final = await app.ainvoke(State())
print(final)

await browser.close()


if __name__ == "__main__":
asyncio.run(main())
88 changes: 88 additions & 0 deletions examples/langgraph/sentience_self_correcting_graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""
LangGraph reference example: Sentience observe → act → verify → branch (self-correcting).

Install:
pip install sentienceapi[langchain]

Run:
python examples/langgraph/sentience_self_correcting_graph.py

Notes:
- This is a template demonstrating control flow; you can replace the "decide" node
with an LLM step (LangChain) that chooses actions based on snapshot_state/read_page.
"""

from __future__ import annotations

import asyncio
from dataclasses import dataclass
from typing import Optional

from sentience import AsyncSentienceBrowser
from sentience.integrations.langchain import SentienceLangChainContext, SentienceLangChainCore


@dataclass
class State:
url: str | None = None
last_action: str | None = None
attempts: int = 0
done: bool = False


async def main() -> None:
# Lazy import so the file can exist without langgraph installed
from langgraph.graph import END, StateGraph

browser = AsyncSentienceBrowser(headless=False)
await browser.start()

core = SentienceLangChainCore(SentienceLangChainContext(browser=browser))

async def observe(state: State) -> State:
s = await core.snapshot_state()
state.url = s.url
return state

async def act(state: State) -> State:
# Replace this with an LLM-driven decision. For demo purposes, we just navigate once.
if state.attempts == 0:
await core.navigate("https://example.com")
state.last_action = "navigate"
else:
state.last_action = "noop"
state.attempts += 1
return state

async def verify(state: State) -> State:
# Guard condition: URL should contain example.com
out = await core.verify_url_matches(r"example\.com")
state.done = bool(out.passed)
return state

def should_continue(state: State) -> str:
# Self-correcting loop: retry observe→act→verify up to 3 attempts
if state.done:
return "done"
if state.attempts >= 3:
return "done"
return "retry"

g = StateGraph(State)
g.add_node("observe", observe)
g.add_node("act", act)
g.add_node("verify", verify)
g.set_entry_point("observe")
g.add_edge("observe", "act")
g.add_edge("act", "verify")
g.add_conditional_edges("verify", should_continue, {"retry": "observe", "done": END})
app = g.compile()

final = await app.ainvoke(State())
print(final)

await browser.close()


if __name__ == "__main__":
asyncio.run(main())
43 changes: 43 additions & 0 deletions examples/pydantic_ai/pydantic_ai_self_correcting_click.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
Example: PydanticAI + Sentience self-correcting action loop using URL guards.

Run:
pip install sentienceapi[pydanticai]
python examples/pydantic_ai/pydantic_ai_self_correcting_click.py
"""

from __future__ import annotations

from sentience import AsyncSentienceBrowser
from sentience.integrations.pydanticai import SentiencePydanticDeps, register_sentience_tools


async def main() -> None:
from pydantic_ai import Agent

browser = AsyncSentienceBrowser(headless=False)
await browser.start()
await browser.page.goto("https://example.com") # replace with a real target

agent = Agent(
"openai:gpt-5",
deps_type=SentiencePydanticDeps,
output_type=str,
instructions=(
"Navigate on the site and click the appropriate link/button. "
"After clicking, use assert_eventually_url_matches to confirm the URL changed as expected."
),
)
register_sentience_tools(agent)

deps = SentiencePydanticDeps(browser=browser)
result = await agent.run("Click something that navigates, then confirm URL changed.", deps=deps)
print(result.output)

await browser.close()


if __name__ == "__main__":
import asyncio

asyncio.run(main())
47 changes: 47 additions & 0 deletions examples/pydantic_ai/pydantic_ai_typed_extraction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
Example: PydanticAI + Sentience typed extraction (Phase 1 integration).

Run:
pip install sentienceapi[pydanticai]
python examples/pydantic_ai/pydantic_ai_typed_extraction.py
"""

from __future__ import annotations

from pydantic import BaseModel, Field

from sentience import AsyncSentienceBrowser
from sentience.integrations.pydanticai import SentiencePydanticDeps, register_sentience_tools


class ProductInfo(BaseModel):
title: str = Field(..., description="Product title")
price: str = Field(..., description="Displayed price string")


async def main() -> None:
from pydantic_ai import Agent

browser = AsyncSentienceBrowser(headless=False)
await browser.start()
await browser.page.goto("https://example.com") # replace with a real target

agent = Agent(
"openai:gpt-5",
deps_type=SentiencePydanticDeps,
output_type=ProductInfo,
instructions="Extract the product title and price from the page.",
)
register_sentience_tools(agent)

deps = SentiencePydanticDeps(browser=browser)
result = await agent.run("Extract title and price.", deps=deps)
print(result.output)

await browser.close()


if __name__ == "__main__":
import asyncio

asyncio.run(main())
9 changes: 9 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ sentience = "sentience.cli:main"
browser-use = [
"browser-use>=0.1.40",
]
pydanticai = [
# PydanticAI framework (PyPI: pydantic-ai, import: pydantic_ai)
"pydantic-ai",
]
langchain = [
# LangChain + LangGraph (kept optional to avoid forcing heavyweight deps on core SDK users)
"langchain",
"langgraph",
]
vision-local = [
"pillow>=10.0.0",
"torch>=2.2.0",
Expand Down
Loading
Loading