Compare commits

..
10 changed files with 86 additions and 88 deletions
+1 -7
View File
@@ -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:
+11 -45
View File
@@ -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:
+3 -18
View File
@@ -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,
+2 -1
View File
@@ -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")
+35 -9
View File
@@ -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
View File
@@ -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."""
+2 -2
View File
@@ -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",
+2 -2
View File
@@ -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" },
+2 -2
View File
@@ -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",
] ]
+2 -2
View File
@@ -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" },