mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-10-21 15:31:43 +02:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67f6ab27b8 | ||
|
|
f7fe7c6698 |
@@ -7,12 +7,6 @@ on:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
description: "From which folder this pipeline executes"
|
description: "From which folder this pipeline executes"
|
||||||
|
|
||||||
release-branch:
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: "main"
|
|
||||||
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHON_VERSION: "3.10"
|
PYTHON_VERSION: "3.10"
|
||||||
@@ -22,7 +16,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
if: github.ref == 'refs/heads/${{ inputs.release-branch }}'
|
if: github.ref == 'refs/heads/main'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
if: github.ref == 'refs/heads/0.6'
|
if: github.ref == 'refs/heads/main'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
@@ -79,10 +79,6 @@ jobs:
|
|||||||
echo short-pkg-name="$SHORT_PKG_NAME" >> $GITHUB_OUTPUT
|
echo short-pkg-name="$SHORT_PKG_NAME" >> $GITHUB_OUTPUT
|
||||||
echo version="$VERSION" >> $GITHUB_OUTPUT
|
echo version="$VERSION" >> $GITHUB_OUTPUT
|
||||||
echo tag="$TAG" >> $GITHUB_OUTPUT
|
echo tag="$TAG" >> $GITHUB_OUTPUT
|
||||||
echo "Pkg-name: $PKG_NAME"
|
|
||||||
echo "Short-pkg-name: $SHORT_PKG_NAME"
|
|
||||||
echo "Version: $VERSION"
|
|
||||||
echo "Tag: $TAG"
|
|
||||||
|
|
||||||
release-notes:
|
release-notes:
|
||||||
needs:
|
needs:
|
||||||
@@ -97,7 +93,7 @@ jobs:
|
|||||||
path: langgraph
|
path: langgraph
|
||||||
sparse-checkout: | # this only grabs files for relevant dir
|
sparse-checkout: | # this only grabs files for relevant dir
|
||||||
${{ inputs.working-directory }}
|
${{ inputs.working-directory }}
|
||||||
ref: "0.6" #this scopes to just 0.6 branch
|
ref: main # this scopes to just master branch
|
||||||
fetch-depth: 0 # this fetches entire commit history
|
fetch-depth: 0 # this fetches entire commit history
|
||||||
- name: Check Tags
|
- name: Check Tags
|
||||||
id: check-tags
|
id: check-tags
|
||||||
@@ -109,48 +105,19 @@ jobs:
|
|||||||
VERSION: ${{ needs.build.outputs.version }}
|
VERSION: ${{ needs.build.outputs.version }}
|
||||||
TAG: ${{ needs.build.outputs.tag }}
|
TAG: ${{ needs.build.outputs.tag }}
|
||||||
run: |
|
run: |
|
||||||
# 1) Parse major/minor and enforce we're on the 0.6 series
|
if [ -z $SHORT_PKG_NAME ]; then
|
||||||
MAJOR="${VERSION%%.*}"
|
REGEX="^\\d+\\.\\d+\\.\\d+((a|b|rc)\\d+)?\$"
|
||||||
REST="${VERSION#*.}"
|
|
||||||
MINOR="${REST%%.*}"
|
|
||||||
|
|
||||||
# Minor/major numeric guard: fail if >= 0.7.* (or any non-0 major)
|
|
||||||
if [ -z "$MAJOR" ] || [ -z "$MINOR" ]; then
|
|
||||||
echo "Could not parse VERSION '$VERSION' to MAJOR.MINOR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$MAJOR" -ne 0 ] || [ "$MINOR" -ne 6 ]; then
|
|
||||||
echo "Refusing to release VERSION '$VERSION' from 0.6 maintenance branch (got $MAJOR.$MINOR)"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 2) Build a regex that only matches 0.6.* tags, handling both tag shapes
|
|
||||||
# - Plain: ^0\.6\.\d+((a|b|rc)\d+)?$
|
|
||||||
# - With prefix: ^<SHORT>==0\.6\.\d+((a|b|rc)\d+)?$
|
|
||||||
SERIES_REGEX="0\\.6"
|
|
||||||
if [ -z "$SHORT_PKG_NAME" ]; then
|
|
||||||
REGEX="^${SERIES_REGEX}\\.\\d+((a|b|rc)\\d+)?$"
|
|
||||||
else
|
else
|
||||||
# SHORT_PKG_NAME is already pre-sanitized by your step (e.g., no spaces).
|
REGEX="^$SHORT_PKG_NAME==\\d+\\.\\d+\\.\\d+((a|b|rc)\\d+)?\$"
|
||||||
REGEX="^${SHORT_PKG_NAME}==${SERIES_REGEX}\\.\\d+((a|b|rc)\\d+)?$"
|
|
||||||
fi
|
fi
|
||||||
|
echo $REGEX
|
||||||
echo "Tag match regex: $REGEX"
|
PREV_TAG=$(git tag --sort=-creatordate | grep -P $REGEX | head -1 || echo "")
|
||||||
|
echo $PREV_TAG
|
||||||
# 3) Find the most recent tag in this series
|
if [ "$TAG" == "$PREV_TAG" ]; then
|
||||||
PREV_TAG="$(git tag --sort=-creatordate | grep -P "$REGEX" | head -1 || true)"
|
echo "No new version to release"
|
||||||
echo "Previous tag in series: ${PREV_TAG:-<none>}"
|
|
||||||
|
|
||||||
# 4) If computed TAG equals the previous tag, there’s nothing new to release
|
|
||||||
if [ "$TAG" = "$PREV_TAG" ] && [ -n "$TAG" ]; then
|
|
||||||
echo "No new version to release for 0.6.x (TAG matches previous)."
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
echo prev-tag="$PREV_TAG" >> $GITHUB_OUTPUT
|
||||||
# 5) Surface prev-tag for later steps
|
|
||||||
echo "prev-tag=$PREV_TAG" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Generate release body
|
- name: Generate release body
|
||||||
id: generate-release-body
|
id: generate-release-body
|
||||||
working-directory: langgraph
|
working-directory: langgraph
|
||||||
@@ -182,7 +149,6 @@ jobs:
|
|||||||
uses: ./.github/workflows/_test_release.yml
|
uses: ./.github/workflows/_test_release.yml
|
||||||
with:
|
with:
|
||||||
working-directory: ${{ inputs.working-directory }}
|
working-directory: ${{ inputs.working-directory }}
|
||||||
release-branch: "0.6"
|
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
pre-release-checks:
|
pre-release-checks:
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KE
|
|||||||
f"""Ready!
|
f"""Ready!
|
||||||
- API: http://localhost:{port}
|
- API: http://localhost:{port}
|
||||||
- Docs: http://localhost:{port}/docs
|
- Docs: http://localhost:{port}/docs
|
||||||
- LangSmith Debugger: {debugger_origin}/studio/?baseUrl={debugger_base_url_query}
|
- LangGraph Studio: {debugger_origin}/studio/?baseUrl={debugger_base_url_query}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
@@ -652,17 +652,11 @@ def dockerfile(
|
|||||||
help="Wait for a debugger client to connect to the debug port before starting the server",
|
help="Wait for a debugger client to connect to the debug port before starting the server",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
@click.option(
|
|
||||||
"--debugger-url",
|
|
||||||
type=str,
|
|
||||||
default=None,
|
|
||||||
help="URL of the LangSmith Debugger instance to connect to. Defaults to https://smith.langchain.com",
|
|
||||||
)
|
|
||||||
@click.option(
|
@click.option(
|
||||||
"--studio-url",
|
"--studio-url",
|
||||||
type=str,
|
type=str,
|
||||||
default=None,
|
default=None,
|
||||||
help="(Deprecated: use --debugger-url instead) URL of the LangSmith Debugger instance to connect to.",
|
help="URL of the LangGraph Studio instance to connect to. Defaults to https://smith.langchain.com",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--allow-blocking",
|
"--allow-blocking",
|
||||||
@@ -698,21 +692,12 @@ def dev(
|
|||||||
no_browser: bool,
|
no_browser: bool,
|
||||||
debug_port: Optional[int],
|
debug_port: Optional[int],
|
||||||
wait_for_client: bool,
|
wait_for_client: bool,
|
||||||
debugger_url: Optional[str],
|
|
||||||
studio_url: Optional[str],
|
studio_url: Optional[str],
|
||||||
allow_blocking: bool,
|
allow_blocking: bool,
|
||||||
tunnel: bool,
|
tunnel: bool,
|
||||||
server_log_level: str,
|
server_log_level: str,
|
||||||
):
|
):
|
||||||
"""CLI entrypoint for running the LangGraph API server."""
|
"""CLI entrypoint for running the LangGraph API server."""
|
||||||
if studio_url is not None:
|
|
||||||
click.secho(
|
|
||||||
"Warning: --studio-url is deprecated and will be removed in a future version. "
|
|
||||||
"Please use --debugger-url instead.",
|
|
||||||
fg="yellow",
|
|
||||||
)
|
|
||||||
if debugger_url is None:
|
|
||||||
debugger_url = studio_url
|
|
||||||
try:
|
try:
|
||||||
from langgraph_api.cli import run_server # type: ignore
|
from langgraph_api.cli import run_server # type: ignore
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -776,7 +761,7 @@ def dev(
|
|||||||
http=config_json.get("http"),
|
http=config_json.get("http"),
|
||||||
ui=config_json.get("ui"),
|
ui=config_json.get("ui"),
|
||||||
ui_config=config_json.get("ui_config"),
|
ui_config=config_json.get("ui_config"),
|
||||||
studio_url=debugger_url,
|
studio_url=studio_url,
|
||||||
allow_blocking=allow_blocking,
|
allow_blocking=allow_blocking,
|
||||||
tunnel=tunnel,
|
tunnel=tunnel,
|
||||||
server_level=server_log_level,
|
server_level=server_log_level,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from langgraph.pregel._write import Overwrite, overwrite
|
||||||
from langgraph.pregel.main import NodeBuilder, Pregel
|
from langgraph.pregel.main import NodeBuilder, Pregel
|
||||||
|
|
||||||
__all__ = ("Pregel", "NodeBuilder")
|
__all__ = ("Pregel", "NodeBuilder", "overwrite", "Overwrite")
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ from langgraph.pregel._call import get_runnable_for_task, identifier
|
|||||||
from langgraph.pregel._io import read_channels
|
from langgraph.pregel._io import read_channels
|
||||||
from langgraph.pregel._log import logger
|
from langgraph.pregel._log import logger
|
||||||
from langgraph.pregel._read import INPUT_CACHE_KEY_TYPE, PregelNode
|
from langgraph.pregel._read import INPUT_CACHE_KEY_TYPE, PregelNode
|
||||||
|
from langgraph.pregel._write import _Overwrite
|
||||||
from langgraph.runtime import DEFAULT_RUNTIME, Runtime
|
from langgraph.runtime import DEFAULT_RUNTIME, Runtime
|
||||||
from langgraph.types import (
|
from langgraph.types import (
|
||||||
All,
|
All,
|
||||||
@@ -198,8 +199,16 @@ def local_read(
|
|||||||
# apply writes
|
# apply writes
|
||||||
local_channels: dict[str, BaseChannel] = {}
|
local_channels: dict[str, BaseChannel] = {}
|
||||||
for k in channels:
|
for k in channels:
|
||||||
cc = channels[k].copy()
|
if updated[k]:
|
||||||
cc.update(updated[k])
|
# If any overwrite is present for this channel, reflect it directly
|
||||||
|
ow = next((v for v in updated[k] if isinstance(v, _Overwrite)), None)
|
||||||
|
if ow is not None:
|
||||||
|
cc = channels[k].from_checkpoint(ow.value)
|
||||||
|
else:
|
||||||
|
cc = channels[k].copy()
|
||||||
|
cc.update(updated[k])
|
||||||
|
else:
|
||||||
|
cc = channels[k].copy()
|
||||||
local_channels[k] = cc
|
local_channels[k] = cc
|
||||||
# read fresh values
|
# read fresh values
|
||||||
values = read_channels(local_channels, select)
|
values = read_channels(local_channels, select)
|
||||||
@@ -277,12 +286,16 @@ def apply_writes(
|
|||||||
|
|
||||||
# Group writes by channel
|
# Group writes by channel
|
||||||
pending_writes_by_channel: dict[str, list[Any]] = defaultdict(list)
|
pending_writes_by_channel: dict[str, list[Any]] = defaultdict(list)
|
||||||
|
overwrite_by_channel: dict[str, Any] = {}
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
for chan, val in task.writes:
|
for chan, val in task.writes:
|
||||||
if chan in (NO_WRITES, PUSH, RESUME, INTERRUPT, RETURN, ERROR):
|
if chan in (NO_WRITES, PUSH, RESUME, INTERRUPT, RETURN, ERROR):
|
||||||
pass
|
continue
|
||||||
elif chan in channels:
|
if chan in channels:
|
||||||
pending_writes_by_channel[chan].append(val)
|
if isinstance(val, _Overwrite):
|
||||||
|
overwrite_by_channel[chan] = val.value
|
||||||
|
else:
|
||||||
|
pending_writes_by_channel[chan].append(val)
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Task {task.name} with path {task.path} wrote to unknown channel {chan}, ignoring it."
|
f"Task {task.name} with path {task.path} wrote to unknown channel {chan}, ignoring it."
|
||||||
@@ -290,13 +303,26 @@ def apply_writes(
|
|||||||
|
|
||||||
# Apply writes to channels
|
# Apply writes to channels
|
||||||
updated_channels: set[str] = set()
|
updated_channels: set[str] = set()
|
||||||
for chan, vals in pending_writes_by_channel.items():
|
for chan in set(pending_writes_by_channel.keys()) | set(
|
||||||
|
overwrite_by_channel.keys()
|
||||||
|
):
|
||||||
if chan in channels:
|
if chan in channels:
|
||||||
if channels[chan].update(vals) and next_version is not None:
|
if chan in overwrite_by_channel:
|
||||||
checkpoint["channel_versions"][chan] = next_version
|
# Overwrite the entire channel value, bypassing reducers.
|
||||||
# unavailable channels can't trigger tasks, so don't add them
|
channels[chan] = channels[chan].from_checkpoint(
|
||||||
|
overwrite_by_channel[chan]
|
||||||
|
)
|
||||||
|
if next_version is not None:
|
||||||
|
checkpoint["channel_versions"][chan] = next_version
|
||||||
if channels[chan].is_available():
|
if channels[chan].is_available():
|
||||||
updated_channels.add(chan)
|
updated_channels.add(chan)
|
||||||
|
else:
|
||||||
|
vals = pending_writes_by_channel.get(chan, [])
|
||||||
|
if channels[chan].update(vals) and next_version is not None:
|
||||||
|
checkpoint["channel_versions"][chan] = next_version
|
||||||
|
# unavailable channels can't trigger tasks, so don't add them
|
||||||
|
if channels[chan].is_available():
|
||||||
|
updated_channels.add(chan)
|
||||||
|
|
||||||
# Channels that weren't updated in this step are notified of a new step
|
# Channels that weren't updated in this step are notified of a new step
|
||||||
if bump_step:
|
if bump_step:
|
||||||
|
|||||||
@@ -26,6 +26,32 @@ SKIP_WRITE = object()
|
|||||||
PASSTHROUGH = object()
|
PASSTHROUGH = object()
|
||||||
|
|
||||||
|
|
||||||
|
class _Overwrite:
|
||||||
|
"""Marker wrapper indicating a direct channel overwrite.
|
||||||
|
|
||||||
|
Use via `overwrite(channel, value)` or `Overwrite(value)`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ("value",)
|
||||||
|
|
||||||
|
def __init__(self, value: Any):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
|
||||||
|
def Overwrite(value: Any) -> _Overwrite:
|
||||||
|
"""Wrap a value to force overwrite a channel, bypassing reducers."""
|
||||||
|
return _Overwrite(value)
|
||||||
|
|
||||||
|
|
||||||
|
def overwrite(channel: str, value: Any) -> ChannelWriteEntry:
|
||||||
|
"""Convenience factory for a write that overwrites the target channel.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
NodeBuilder().write_to(overwrite("foo", 123))
|
||||||
|
"""
|
||||||
|
return ChannelWriteEntry(channel, Overwrite(value))
|
||||||
|
|
||||||
|
|
||||||
class ChannelWriteEntry(NamedTuple):
|
class ChannelWriteEntry(NamedTuple):
|
||||||
channel: str
|
channel: str
|
||||||
"""Channel name to write to."""
|
"""Channel name to write to."""
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "langgraph"
|
name = "langgraph"
|
||||||
version = "0.6.11"
|
version = "0.6.10"
|
||||||
description = "Building stateful, multi-actor applications with LLMs"
|
description = "Building stateful, multi-actor applications with LLMs"
|
||||||
authors = []
|
authors = []
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
@@ -13,7 +13,7 @@ license = "MIT"
|
|||||||
license-files = ['LICENSE']
|
license-files = ['LICENSE']
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"langchain-core>=0.1",
|
"langchain-core>=0.1",
|
||||||
"langgraph-checkpoint>=2.1.0,<4.0.0",
|
"langgraph-checkpoint>=2.1.0,<3.0.0",
|
||||||
"langgraph-sdk>=0.2.2,<0.3.0",
|
"langgraph-sdk>=0.2.2,<0.3.0",
|
||||||
"langgraph-prebuilt>=0.6.0,<0.7.0",
|
"langgraph-prebuilt>=0.6.0,<0.7.0",
|
||||||
"xxhash>=3.5.0",
|
"xxhash>=3.5.0",
|
||||||
|
|||||||
Generated
+2
-2
@@ -1428,7 +1428,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langgraph"
|
name = "langgraph"
|
||||||
version = "0.6.11"
|
version = "0.6.10"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "langchain-core" },
|
{ name = "langchain-core" },
|
||||||
@@ -1674,7 +1674,7 @@ dev = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langgraph-prebuilt"
|
name = "langgraph-prebuilt"
|
||||||
version = "0.6.5"
|
version = "0.6.4"
|
||||||
source = { editable = "../prebuilt" }
|
source = { editable = "../prebuilt" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "langchain-core" },
|
{ name = "langchain-core" },
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "langgraph-prebuilt"
|
name = "langgraph-prebuilt"
|
||||||
version = "0.6.5"
|
version = "0.6.4"
|
||||||
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
|
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
|
||||||
authors = []
|
authors = []
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
@@ -12,7 +12,7 @@ readme = "README.md"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
license-files = ['LICENSE']
|
license-files = ['LICENSE']
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"langgraph-checkpoint>=2.1.0,<4.0.0",
|
"langgraph-checkpoint>=2.1.0,<3.0.0",
|
||||||
"langchain-core>=0.3.67",
|
"langchain-core>=0.3.67",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-2
@@ -257,7 +257,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langgraph"
|
name = "langgraph"
|
||||||
version = "0.6.11"
|
version = "0.6.10"
|
||||||
source = { editable = "../langgraph" }
|
source = { editable = "../langgraph" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "langchain-core" },
|
{ name = "langchain-core" },
|
||||||
@@ -403,7 +403,7 @@ dev = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langgraph-prebuilt"
|
name = "langgraph-prebuilt"
|
||||||
version = "0.6.5"
|
version = "0.6.4"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "langchain-core" },
|
{ name = "langchain-core" },
|
||||||
|
|||||||
Reference in New Issue
Block a user