LangChain integration for the Yutori API — Navigator browser control, browser automation, web research, and monitoring
Project description
langchain-yutori
LangChain integration for the Yutori API — Navigator browser control, browser automation, deep research, and recurring web monitors.
Installation
pip install langchain langchain-yutori
This package is implemented as a standalone LangChain integration package. It uses the official yutori
Python SDK for Browsing, Research, and Scouts, and wraps Navigator as a LangChain chat model.
Installing langchain-yutori also installs the yutori Python package, plus the yutori CLI.
Components
| Class | Type | Description |
|---|---|---|
ChatYutoriNavigator |
ChatModel |
Yutori Navigator browser-control model (OpenAI-compatible, defaults to n1.5) |
YutoriBrowsingTool |
BaseTool |
Execute web browsing tasks on a remote browser |
YutoriResearchTool |
BaseTool |
Perform deep and broad research using 100+ tools |
YutoriScoutingTool |
BaseTool |
Create and manage recurring web monitors with Scouts |
Authentication
Recommended:
yutori auth login
This opens your browser and saves your API key locally for the SDK and this package to use.
Or set your API key via environment variable:
export YUTORI_API_KEY="yt_..."
Or pass it directly to each class.
Get your API key at platform.yutori.com.
Usage
ChatYutoriNavigator
Navigator is Yutori's pixels-to-actions LLM for browser navigation. It accepts screenshots and returns browser actions (click, type, scroll, etc.). The current version is n1.5 (the default); older versions like n1 remain selectable via the model argument.
from langchain_yutori import ChatYutoriNavigator
from langchain_core.messages import HumanMessage
from yutori.navigator import aplaywright_screenshot_to_data_url
llm = ChatYutoriNavigator() # defaults to n1.5-latest, uses YUTORI_API_KEY env var
image_url = await aplaywright_screenshot_to_data_url(page)
message = HumanMessage(content=[
{"type": "text", "text": "What is the next action to complete the task: 'Add item to cart'?"},
{"type": "image_url", "image_url": {"url": image_url}},
])
response = llm.invoke([message])
# Returns tool_calls with browser actions
To pin to a specific Navigator version:
llm = ChatYutoriNavigator(model="n1-latest") # older Navigator n1
llm = ChatYutoriNavigator(model="n1.5-latest") # current Navigator n1.5 (default)
With Playwright, use the SDK helper to capture the screenshot and re-encode it as a WebP data URL
at the resolution Navigator was trained on (1280×800, q90). ChatYutoriNavigator accepts image
URLs but doesn't capture or preprocess screenshots itself.
If you execute returned browser actions yourself, Navigator coordinates are normalized to a
1000x1000 space. Convert them back into viewport pixels with the SDK helper:
from yutori.navigator import denormalize_coordinates
coords = [500, 250]
x, y = denormalize_coordinates(coords, width=1280, height=800)
await page.mouse.click(x, y)
Tool sets and request options
Navigator's available action set is server-side and version-tagged. Select it (and optionally
disable specific actions) via first-class constructor params; they are forwarded to the OpenAI
client's extra_body:
llm = ChatYutoriNavigator(
tool_set="browser_tools_expanded-20260403", # adds extract_elements, find, set_element_value, execute_js
disable_tools=["hold_key", "drag"],
)
Other Navigator-specific request fields (e.g. json_schema for structured output) can be passed
through extra_body directly — they're merged with the first-class params:
llm = ChatYutoriNavigator(
tool_set="browser_tools_core-20260403",
extra_body={"json_schema": {"type": "object", "properties": {...}}},
)
Multi-turn loop and message-history shape
After Navigator returns tool_calls, you execute the action in your browser, capture a fresh
screenshot, and feed the result back as a ToolMessage whose content is a multimodal list
of [text, image_url]. Navigator requires the new screenshot inside the tool message — not in a
separate HumanMessage:
from langchain_core.messages import AIMessage, HumanMessage
from langchain_yutori import navigator_tool_result, trim_navigator_history
from yutori.navigator import aplaywright_screenshot_to_data_url, denormalize_coordinates
history = [
HumanMessage(content=[
{"type": "text", "text": "Search for Yutori on Google."},
{"type": "image_url", "image_url": {"url": await aplaywright_screenshot_to_data_url(page)}},
])
]
while True:
response: AIMessage = llm.invoke(trim_navigator_history(history))
history.append(response)
if not response.tool_calls:
break # Navigator finished the task; response.content has the summary
last_idx = len(response.tool_calls) - 1
for i, call in enumerate(response.tool_calls):
result_text = await execute_in_browser(page, call) # your code; use denormalize_coordinates etc.
history.append(navigator_tool_result(
tool_call_id=call["id"],
result_text=result_text,
current_url=page.url,
# only the last result in a batched turn carries the next screenshot
screenshot_data_url=(
await aplaywright_screenshot_to_data_url(page) if i == last_idx else None
),
))
Notes:
-
Don't add a system message. Navigator's docs recommend placing extra instructions in the first user message instead.
-
Include
Current URL: ...in the tool result text — it improves grounding. -
Don't drop messages — only old screenshots. For long trajectories, use
trim_navigator_historyto dropimage_urlblocks from older messages while keeping every message and its text intact:from langchain_yutori import trim_navigator_history response = llm.invoke(trim_navigator_history(history)) # default keep_recent=2
The Yutori SDK exposes equivalent helpers (
trim_images_to_fit,trimmed_messages_to_fit,create_trimmed/acreate_trimmed) that operate on dict-shaped messages and bypass LangChain — use those if you call the SDK directly, otherwise prefer the LangChain helper above forllm.invoke(...)flows.
For the full Navigator input requirements and action schema, see the Yutori docs: https://docs.yutori.com/llm-quickstart and https://docs.yutori.com/reference/navigator
YutoriBrowsingTool
Runs a browser automation agent on Yutori's cloud browser. The tool creates the task and polls until it completes (up to 20 minutes by default; tasks typically take 5–15 minutes).
from langchain_yutori import YutoriBrowsingTool
tool = YutoriBrowsingTool()
result = tool.run({
"task": "Find the price of the MacBook Pro 14-inch M4",
"start_url": "https://www.apple.com",
})
print(result) # JSON string with task result
In a LangChain agent:
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate
from langchain_yutori import YutoriBrowsingTool
tools = [YutoriBrowsingTool()]
llm = ChatOpenAI(model="gpt-4o")
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant with web browsing capabilities."),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools)
executor.invoke({"input": "What's the current price of AAPL on Yahoo Finance?"})
YutoriResearchTool
Performs deep and broad research using Yutori's research agent (100+ MCP tools). Creates the task and polls until complete.
from langchain_yutori import YutoriResearchTool
tool = YutoriResearchTool()
result = tool.run({
"query": "What are the top 5 AI coding assistants in 2026 and how do their pricing models compare?",
"user_location": "San Francisco, CA, US",
})
print(result) # JSON string with research report
YutoriScoutingTool
Manages Yutori Scouts — recurring web monitors that run on a schedule and surface findings.
from langchain_yutori import YutoriScoutingTool
tool = YutoriScoutingTool()
# Create a scout (runs daily by default)
result = tool.run({
"action": "create",
"query": "Monitor Hacker News for posts about browser automation agents",
"output_interval": 3600, # hourly
})
# List all scouts
scouts = tool.run({"action": "list"})
# Get updates from a specific scout
updates = tool.run({
"action": "get_updates",
"scout_id": "abc123...",
"limit": 10,
})
# Pause / resume / delete
tool.run({"action": "pause", "scout_id": "abc123..."})
tool.run({"action": "resume", "scout_id": "abc123..."})
tool.run({"action": "delete", "scout_id": "abc123..."})
Configuration
Browsing and Research tools accept poll_interval (seconds between status checks, default 60, minimum 60) and timeout (max wait seconds, default 1200):
tool = YutoriBrowsingTool(
api_key="yt_...",
poll_interval=10.0,
timeout=300.0,
)
Links
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file langchain_yutori-0.2.0.tar.gz.
File metadata
- Download URL: langchain_yutori-0.2.0.tar.gz
- Upload date:
- Size: 135.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
604e2075a48755465cfcbf45df26feb648a104afb56d82b3a2954f036bba9fd3
|
|
| MD5 |
50a082e4ed4140903ca985ab801ae402
|
|
| BLAKE2b-256 |
5ad5ec473f99ac2b67987b45037eedbebf848e8ca7f5d54cb6d289e80a6e14ae
|
Provenance
The following attestation bundles were made for langchain_yutori-0.2.0.tar.gz:
Publisher:
publish-langchain-yutori.yml on yutori-ai/langchain-yutori
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
langchain_yutori-0.2.0.tar.gz -
Subject digest:
604e2075a48755465cfcbf45df26feb648a104afb56d82b3a2954f036bba9fd3 - Sigstore transparency entry: 1463077391
- Sigstore integration time:
-
Permalink:
yutori-ai/langchain-yutori@4ff6b7c8a9647eb004e9030d52940b6c14ffe145 -
Branch / Tag:
refs/tags/langchain-yutori/v0.2.0 - Owner: https://github.com/yutori-ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-langchain-yutori.yml@4ff6b7c8a9647eb004e9030d52940b6c14ffe145 -
Trigger Event:
push
-
Statement type:
File details
Details for the file langchain_yutori-0.2.0-py3-none-any.whl.
File metadata
- Download URL: langchain_yutori-0.2.0-py3-none-any.whl
- Upload date:
- Size: 15.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f8e269456b8a5a08793fe23c48a03dfc97d381adcaefb431420d9c68a9519006
|
|
| MD5 |
ede0a57eec0ded692d6cb740d9db40d9
|
|
| BLAKE2b-256 |
cf20b632b95558eb9dfe8aaccf1b6717e8c10d509de6d8f221a5e3a5f454eb77
|
Provenance
The following attestation bundles were made for langchain_yutori-0.2.0-py3-none-any.whl:
Publisher:
publish-langchain-yutori.yml on yutori-ai/langchain-yutori
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
langchain_yutori-0.2.0-py3-none-any.whl -
Subject digest:
f8e269456b8a5a08793fe23c48a03dfc97d381adcaefb431420d9c68a9519006 - Sigstore transparency entry: 1463077394
- Sigstore integration time:
-
Permalink:
yutori-ai/langchain-yutori@4ff6b7c8a9647eb004e9030d52940b6c14ffe145 -
Branch / Tag:
refs/tags/langchain-yutori/v0.2.0 - Owner: https://github.com/yutori-ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-langchain-yutori.yml@4ff6b7c8a9647eb004e9030d52940b6c14ffe145 -
Trigger Event:
push
-
Statement type: