mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-27 01:52:25 +02:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
933619d751 |
@@ -99,7 +99,7 @@ jobs:
|
||||
env:
|
||||
LANGCHAIN_API_KEY: test
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "schedule" ]; then
|
||||
if [ "${{ github.event_name }}" == "schedule" ] || [ "${{ github.event_name }}" == "workflow_dispatch" ] || ([ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == "refs/heads/main" ]); then
|
||||
echo "Running link check on all HTML files matching notebooks in docs directory..."
|
||||
poetry run pytest -v \
|
||||
--check-links-ignore "https://(api|web|docs)\.smith\.langchain\.com/.*" \
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
# LLMs-txt Overview
|
||||
# LLMs-txt for LangGraph
|
||||
|
||||
## Overview
|
||||
|
||||
Below you can find a list of documentation files in the [`llms.txt`](https://llmstxt.org/) format, specifically `llms.txt` and `llms-full.txt`. These files allow large language models (LLMs) and agents to access programming documentation and APIs, particularly useful within integrated development environments (IDEs).
|
||||
LangGraph provides documentation files in the [`llms.txt`](https://llmstxt.org/) format, specifically `llms.txt` and `llms-full.txt`. These files allow large language models (LLMs) and agents to access programming documentation and APIs, particularly useful within integrated development environments (IDEs).
|
||||
|
||||
| Language Version | llms.txt | llms-full.txt |
|
||||
|------------------|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------|
|
||||
| LangGraph Python | [https://langchain-ai.github.io/langgraph/llms.txt](https://langchain-ai.github.io/langgraph/llms.txt) | [https://langchain-ai.github.io/langgraph/llms-full.txt](https://langchain-ai.github.io/langgraph/llms-full.txt) |
|
||||
| LangGraph JS | [https://langchain-ai.github.io/langgraphjs/llms.txt](https://langchain-ai.github.io/langgraphjs/llms.txt) | [https://langchain-ai.github.io/langgraphjs/llms-full.txt](https://langchain-ai.github.io/langgraphjs/llms-full.txt) |
|
||||
| LangChain Python | [https://python.langchain.com/llms.txt](https://python.langchain.com/llms.txt) | N/A |
|
||||
| LangChain JS | [https://js.langchain.com/llms.txt](https://js.langchain.com/llms.txt) | N/A |
|
||||
|
||||
!!! info "Review the output"
|
||||
|
||||
Even with access to up-to-date documentation, current state-of-the-art models may not always generate correct code. Treat the generated code as a starting point, and always review it before shipping
|
||||
code to production.
|
||||
|
||||
## Differences Between `llms.txt` and `llms-full.txt`
|
||||
|
||||
@@ -26,17 +19,9 @@ A key consideration when using `llms-full.txt` is its size. For extensive docume
|
||||
|
||||
## Using `llms.txt` via an MCP Server
|
||||
|
||||
As of March 9, 2025, IDEs [do not yet have robust native support for `llms.txt`](https://x.com/jeremyphoward/status/1902109312216129905?t=1eHFv2vdNdAckajnug0_Vw&s=19). However, you can still use `llms.txt` effectively through an MCP server.
|
||||
As of March 9, 2025, IDEs [do not yet have robust native support for `llms.txt`](https://x.com/jeremyphoward/status/1902109312216129905?t=1eHFv2vdNdAckajnug0_Vw&s=19). However, you can utilize `llms.txt` effectively through an MCP server.
|
||||
|
||||
### 🚀 Use the `mcpdoc` Server
|
||||
|
||||
We provide an **MCP server** that was designed to serve documentation for LLMs and IDEs:
|
||||
|
||||
👉 **[langchain-ai/mcpdoc GitHub Repository](https://github.com/langchain-ai/mcpdoc)**
|
||||
|
||||
This MCP server allows integrating `llms.txt` into tools like **Cursor**, **Windsurf**, **Claude**, and **Claude Code**.
|
||||
|
||||
📘 **Setup instructions and usage examples** are available in the repository.
|
||||
We provide an MCP server specifically designed to serve documentation, called [`mcpdoc`](https://github.com/langchain-ai/mcpdoc). This setup is compatible with IDEs and platforms such as Cursor, Windsurf, Claude, and Claude Code. Instructions for using `mcpdoc` with these tools are available in the repository.
|
||||
|
||||
## Using `llms-full.txt`
|
||||
|
||||
|
||||
@@ -118,6 +118,17 @@ def get_store() -> BaseStore:
|
||||
return config[CONF][CONFIG_KEY_STORE]
|
||||
|
||||
|
||||
def set_store(store: BaseStore) -> None:
|
||||
"""Set LangGraph store in context."""
|
||||
var_config = var_child_runnable_config.get()
|
||||
if not var_config:
|
||||
var_config = {CONF: {CONFIG_KEY_STORE: store}}
|
||||
var_child_runnable_config.set(var_config)
|
||||
else:
|
||||
var_config[CONF][CONFIG_KEY_STORE] = store
|
||||
var_child_runnable_config.set(var_config)
|
||||
|
||||
|
||||
def get_stream_writer() -> StreamWriter:
|
||||
"""Access LangGraph [StreamWriter][langgraph.types.StreamWriter] from inside a graph node or entrypoint task at runtime.
|
||||
|
||||
@@ -183,3 +194,14 @@ def get_stream_writer() -> StreamWriter:
|
||||
"""
|
||||
config = get_config()
|
||||
return config[CONF].get(CONFIG_KEY_STREAM_WRITER, _no_op_stream_writer)
|
||||
|
||||
|
||||
def set_stream_writer(stream_writer: StreamWriter) -> None:
|
||||
"""Set LangGraph stream writer in context."""
|
||||
var_config = var_child_runnable_config.get()
|
||||
if not var_config:
|
||||
var_config = {CONF: {CONFIG_KEY_STREAM_WRITER: stream_writer}}
|
||||
var_child_runnable_config.set(var_config)
|
||||
else:
|
||||
var_config[CONF][CONFIG_KEY_STREAM_WRITER] = stream_writer
|
||||
var_child_runnable_config.set(var_config)
|
||||
|
||||
@@ -242,7 +242,7 @@ class StateGraph(Graph):
|
||||
metadata: Optional[dict[str, Any]] = None,
|
||||
input: Optional[Type[Any]] = None,
|
||||
retry: Optional[RetryPolicy] = None,
|
||||
destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None,
|
||||
destinations: Optional[Union[dict[str, str], tuple[str]]] = None,
|
||||
) -> Self:
|
||||
"""Adds a new node to the state graph.
|
||||
Will take the name of the function/runnable as the node name.
|
||||
@@ -267,7 +267,7 @@ class StateGraph(Graph):
|
||||
metadata: Optional[dict[str, Any]] = None,
|
||||
input: Optional[Type[Any]] = None,
|
||||
retry: Optional[RetryPolicy] = None,
|
||||
destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None,
|
||||
destinations: Optional[Union[dict[str, str], tuple[str]]] = None,
|
||||
) -> Self:
|
||||
"""Adds a new node to the state graph.
|
||||
|
||||
@@ -291,7 +291,7 @@ class StateGraph(Graph):
|
||||
metadata: Optional[dict[str, Any]] = None,
|
||||
input: Optional[Type[Any]] = None,
|
||||
retry: Optional[RetryPolicy] = None,
|
||||
destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None,
|
||||
destinations: Optional[Union[dict[str, str], tuple[str]]] = None,
|
||||
) -> Self:
|
||||
"""Adds a new node to the state graph.
|
||||
|
||||
@@ -303,7 +303,7 @@ class StateGraph(Graph):
|
||||
metadata (Optional[dict[str, Any]]): The metadata associated with the node. (default: None)
|
||||
input (Optional[Type[Any]]): The input schema for the node. (default: the graph's input schema)
|
||||
retry (Optional[RetryPolicy]): The policy for retrying the node. (default: None)
|
||||
destinations (Optional[Union[dict[str, str], tuple[str, ...]]]): Destinations that indicate where a node can route to.
|
||||
destinations (Optional[Union[dict[str, str], tuple[str]]]): Destinations that indicate where a node can route to.
|
||||
This is useful for edgeless graphs with nodes that return `Command` objects.
|
||||
If a dict is provided, the keys will be used as the target node names and the values will be used as the labels for the edges.
|
||||
If a tuple is provided, the values will be used as the target node names.
|
||||
|
||||
@@ -156,16 +156,10 @@ class AsyncBackgroundExecutor(AsyncContextManager):
|
||||
if self.semaphore:
|
||||
coro = gated(self.semaphore, coro)
|
||||
if CONTEXT_NOT_SUPPORTED:
|
||||
task = run_coroutine_threadsafe(
|
||||
coro, self.loop, name=__name__, lazy=__next_tick__
|
||||
)
|
||||
task = run_coroutine_threadsafe(coro, self.loop, name=__name__)
|
||||
else:
|
||||
task = run_coroutine_threadsafe(
|
||||
coro,
|
||||
self.loop,
|
||||
name=__name__,
|
||||
context=copy_context(),
|
||||
lazy=__next_tick__,
|
||||
coro, self.loop, name=__name__, context=copy_context()
|
||||
)
|
||||
self.tasks[task] = (__cancel_on_exit__, __reraise_on_exit__)
|
||||
task.add_done_callback(self.done)
|
||||
|
||||
@@ -44,18 +44,6 @@ from langgraph.utils.future import chain_future
|
||||
F = TypeVar("F", concurrent.futures.Future, asyncio.Future)
|
||||
E = TypeVar("E", threading.Event, asyncio.Event)
|
||||
|
||||
# List of filenames to exclude from exception traceback
|
||||
# Note: Frames will be removed if they are the last frame in traceback, recursively
|
||||
EXCLUDED_FRAME_FNAMES = (
|
||||
"langgraph/pregel/retry.py",
|
||||
"langgraph/pregel/runner.py",
|
||||
"langgraph/pregel/executor.py",
|
||||
"langgraph/utils/runnable.py",
|
||||
"langchain_core/runnables/config.py",
|
||||
"concurrent/futures/thread.py",
|
||||
"concurrent/futures/_base.py",
|
||||
)
|
||||
|
||||
|
||||
class FuturesDict(Generic[F, E], dict[F, Optional[PregelExecutableTask]]):
|
||||
event: E
|
||||
@@ -179,13 +167,6 @@ class PregelRunner:
|
||||
fut.set_exception(exc)
|
||||
futures.done.add(fut)
|
||||
elif reraise:
|
||||
if tb := exc.__traceback__:
|
||||
while tb.tb_next is not None and any(
|
||||
tb.tb_frame.f_code.co_filename.endswith(name)
|
||||
for name in EXCLUDED_FRAME_FNAMES
|
||||
):
|
||||
tb = tb.tb_next
|
||||
exc.__traceback__ = tb
|
||||
raise
|
||||
if not futures: # maybe `t` schuduled another task
|
||||
return
|
||||
@@ -248,20 +229,10 @@ class PregelRunner:
|
||||
# give control back to the caller
|
||||
yield
|
||||
# panic on failure or timeout
|
||||
try:
|
||||
_panic_or_proceed(
|
||||
futures.done.union(f for f, t in futures.items() if t is not None),
|
||||
panic=reraise,
|
||||
)
|
||||
except Exception as exc:
|
||||
if tb := exc.__traceback__:
|
||||
while tb.tb_next is not None and any(
|
||||
tb.tb_frame.f_code.co_filename.endswith(name)
|
||||
for name in EXCLUDED_FRAME_FNAMES
|
||||
):
|
||||
tb = tb.tb_next
|
||||
exc.__traceback__ = tb
|
||||
raise
|
||||
_panic_or_proceed(
|
||||
futures.done.union(f for f, t in futures.items() if t is not None),
|
||||
panic=reraise,
|
||||
)
|
||||
|
||||
async def atick(
|
||||
self,
|
||||
@@ -312,13 +283,6 @@ class PregelRunner:
|
||||
fut.set_exception(exc)
|
||||
futures.done.add(fut)
|
||||
elif reraise:
|
||||
if tb := exc.__traceback__:
|
||||
while tb.tb_next is not None and any(
|
||||
tb.tb_frame.f_code.co_filename.endswith(name)
|
||||
for name in EXCLUDED_FRAME_FNAMES
|
||||
):
|
||||
tb = tb.tb_next
|
||||
exc.__traceback__ = tb
|
||||
raise
|
||||
if not futures: # maybe `t` schuduled another task
|
||||
return
|
||||
@@ -393,21 +357,11 @@ class PregelRunner:
|
||||
for fut in futures:
|
||||
fut.cancel()
|
||||
# panic on failure or timeout
|
||||
try:
|
||||
_panic_or_proceed(
|
||||
futures.done.union(f for f, t in futures.items() if t is not None),
|
||||
timeout_exc_cls=asyncio.TimeoutError,
|
||||
panic=reraise,
|
||||
)
|
||||
except Exception as exc:
|
||||
if tb := exc.__traceback__:
|
||||
while tb.tb_next is not None and any(
|
||||
tb.tb_frame.f_code.co_filename.endswith(name)
|
||||
for name in EXCLUDED_FRAME_FNAMES
|
||||
):
|
||||
tb = tb.tb_next
|
||||
exc.__traceback__ = tb
|
||||
raise
|
||||
_panic_or_proceed(
|
||||
futures.done.union(f for f, t in futures.items() if t is not None),
|
||||
timeout_exc_cls=asyncio.TimeoutError,
|
||||
panic=reraise,
|
||||
)
|
||||
|
||||
def commit(
|
||||
self,
|
||||
|
||||
@@ -10,7 +10,6 @@ T = TypeVar("T")
|
||||
AnyFuture = Union[asyncio.Future, concurrent.futures.Future]
|
||||
|
||||
CONTEXT_NOT_SUPPORTED = sys.version_info < (3, 11)
|
||||
EAGER_NOT_SUPPORTED = sys.version_info < (3, 12)
|
||||
|
||||
|
||||
def _get_loop(fut: asyncio.Future) -> asyncio.AbstractEventLoop:
|
||||
@@ -143,7 +142,6 @@ def _ensure_future(
|
||||
loop: asyncio.AbstractEventLoop,
|
||||
name: Optional[str] = None,
|
||||
context: Optional[contextvars.Context] = None,
|
||||
lazy: bool = True,
|
||||
) -> asyncio.Task[T]:
|
||||
called_wrap_awaitable = False
|
||||
if not asyncio.iscoroutine(coro_or_future):
|
||||
@@ -161,12 +159,8 @@ def _ensure_future(
|
||||
try:
|
||||
if CONTEXT_NOT_SUPPORTED:
|
||||
return loop.create_task(coro_or_future, name=name)
|
||||
elif EAGER_NOT_SUPPORTED or lazy:
|
||||
return loop.create_task(coro_or_future, name=name, context=context)
|
||||
else:
|
||||
return asyncio.eager_task_factory(
|
||||
loop, coro_or_future, name=name, context=context
|
||||
)
|
||||
return loop.create_task(coro_or_future, name=name, context=context)
|
||||
except RuntimeError:
|
||||
if not called_wrap_awaitable:
|
||||
coro_or_future.close()
|
||||
@@ -186,8 +180,6 @@ def _wrap_awaitable(awaitable: Awaitable[T]) -> Generator[None, None, T]:
|
||||
def run_coroutine_threadsafe(
|
||||
coro: Coroutine[None, None, T],
|
||||
loop: asyncio.AbstractEventLoop,
|
||||
*,
|
||||
lazy: bool,
|
||||
name: Optional[str] = None,
|
||||
context: Optional[contextvars.Context] = None,
|
||||
) -> asyncio.Future[T]:
|
||||
@@ -195,23 +187,18 @@ def run_coroutine_threadsafe(
|
||||
|
||||
Return a asyncio.Future to access the result.
|
||||
"""
|
||||
future: asyncio.Future[T] = asyncio.Future(loop=loop)
|
||||
|
||||
if asyncio._get_running_loop() is loop:
|
||||
return _ensure_future(coro, loop=loop, name=name, context=context, lazy=lazy)
|
||||
else:
|
||||
future: asyncio.Future[T] = asyncio.Future(loop=loop)
|
||||
def callback() -> None:
|
||||
try:
|
||||
chain_future(
|
||||
_ensure_future(coro, loop=loop, name=name, context=context), future
|
||||
)
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
raise
|
||||
except BaseException as exc:
|
||||
future.set_exception(exc)
|
||||
raise
|
||||
|
||||
def callback() -> None:
|
||||
try:
|
||||
chain_future(
|
||||
_ensure_future(coro, loop=loop, name=name, context=context),
|
||||
future,
|
||||
)
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
raise
|
||||
except BaseException as exc:
|
||||
future.set_exception(exc)
|
||||
raise
|
||||
|
||||
loop.call_soon_threadsafe(callback, context=context)
|
||||
return future
|
||||
loop.call_soon_threadsafe(callback, context=context)
|
||||
return future
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph"
|
||||
version = "0.3.21"
|
||||
version = "0.3.20"
|
||||
description = "Building stateful, multi-actor applications with LLMs"
|
||||
authors = []
|
||||
license = "MIT"
|
||||
|
||||
@@ -12,7 +12,6 @@ from langgraph_sdk.schema import StreamPart
|
||||
from langgraph.errors import GraphInterrupt
|
||||
from langgraph.pregel.remote import RemoteGraph
|
||||
from langgraph.pregel.types import StateSnapshot
|
||||
from langgraph.types import Interrupt
|
||||
|
||||
|
||||
def test_with_config():
|
||||
@@ -416,19 +415,7 @@ def test_stream():
|
||||
StreamPart(event="values", data={"chunk": "data2"}),
|
||||
StreamPart(event="values", data={"chunk": "data3"}),
|
||||
StreamPart(event="updates", data={"chunk": "data4"}),
|
||||
StreamPart(
|
||||
event="updates",
|
||||
data={
|
||||
"__interrupt__": [
|
||||
{
|
||||
"value": {"question": "Does this look good?"},
|
||||
"resumable": True,
|
||||
"ns": ["some_ns"],
|
||||
"when": "during",
|
||||
}
|
||||
]
|
||||
},
|
||||
),
|
||||
StreamPart(event="updates", data={"__interrupt__": ()}),
|
||||
]
|
||||
|
||||
# call method / assertions
|
||||
@@ -439,7 +426,7 @@ def test_stream():
|
||||
|
||||
# stream modes doesn't include 'updates'
|
||||
stream_parts = []
|
||||
with pytest.raises(GraphInterrupt) as exc:
|
||||
with pytest.raises(GraphInterrupt):
|
||||
for stream_part in remote_pregel.stream(
|
||||
{"input": "data"},
|
||||
config={"configurable": {"thread_id": "thread_1"}},
|
||||
@@ -447,15 +434,6 @@ def test_stream():
|
||||
):
|
||||
stream_parts.append(stream_part)
|
||||
|
||||
assert exc.value.args[0] == [
|
||||
Interrupt(
|
||||
value={"question": "Does this look good?"},
|
||||
resumable=True,
|
||||
ns=["some_ns"],
|
||||
when="during",
|
||||
)
|
||||
]
|
||||
|
||||
assert stream_parts == [
|
||||
{"chunk": "data1"},
|
||||
{"chunk": "data2"},
|
||||
@@ -539,19 +517,7 @@ async def test_astream():
|
||||
StreamPart(event="values", data={"chunk": "data2"}),
|
||||
StreamPart(event="values", data={"chunk": "data3"}),
|
||||
StreamPart(event="updates", data={"chunk": "data4"}),
|
||||
StreamPart(
|
||||
event="updates",
|
||||
data={
|
||||
"__interrupt__": [
|
||||
{
|
||||
"value": {"question": "Does this look good?"},
|
||||
"resumable": True,
|
||||
"ns": ["some_ns"],
|
||||
"when": "during",
|
||||
}
|
||||
]
|
||||
},
|
||||
),
|
||||
StreamPart(event="updates", data={"__interrupt__": ()}),
|
||||
]
|
||||
mock_async_client.runs.stream.return_value = async_iter
|
||||
|
||||
@@ -563,7 +529,7 @@ async def test_astream():
|
||||
|
||||
# stream modes doesn't include 'updates'
|
||||
stream_parts = []
|
||||
with pytest.raises(GraphInterrupt) as exc:
|
||||
with pytest.raises(GraphInterrupt):
|
||||
async for stream_part in remote_pregel.astream(
|
||||
{"input": "data"},
|
||||
config={"configurable": {"thread_id": "thread_1"}},
|
||||
@@ -571,15 +537,6 @@ async def test_astream():
|
||||
):
|
||||
stream_parts.append(stream_part)
|
||||
|
||||
assert exc.value.args[0] == [
|
||||
Interrupt(
|
||||
value={"question": "Does this look good?"},
|
||||
resumable=True,
|
||||
ns=["some_ns"],
|
||||
when="during",
|
||||
)
|
||||
]
|
||||
|
||||
assert stream_parts == [
|
||||
{"chunk": "data1"},
|
||||
{"chunk": "data2"},
|
||||
|
||||
@@ -192,18 +192,6 @@ class BaseUser(typing.Protocol):
|
||||
"""The permissions associated with the user."""
|
||||
...
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Get a key from your minimal user dict."""
|
||||
...
|
||||
|
||||
def __contains__(self, key):
|
||||
"""Check if a property exists."""
|
||||
...
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterate over the keys of the user."""
|
||||
...
|
||||
|
||||
|
||||
class StudioUser:
|
||||
"""A user object that's populated from authenticated requests from the LangGraph studio.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph-sdk"
|
||||
version = "0.1.60"
|
||||
version = "0.1.59"
|
||||
description = "SDK for interacting with LangGraph API"
|
||||
authors = []
|
||||
license = "MIT"
|
||||
|
||||
Reference in New Issue
Block a user