Compare commits

..
18 Commits
Author SHA1 Message Date
Nuno Campos ebe01c2639 lib0.1.14 2024-07-24 10:53:23 -07:00
Nuno CamposandGitHub 272219e410 Merge pull request #1121 from langchain-ai/nc/24jul/disable-nested-checkpoints-unless-interrupt 2024-07-24 10:52:29 -07:00
Nuno Campos bb1324cdc3 Disable nested checkpoints unless interrupts set on subgraph 2024-07-24 10:45:39 -07:00
Nuno Campos 85af0603fa lib0.1.13 2024-07-24 09:49:23 -07:00
Nuno CamposandGitHub e6e5911aae Merge pull request #1119 from langchain-ai/nc/24jul/avoid-crash-missing-node
Avoid crash when a node in pending sends is removed
2024-07-24 09:41:20 -07:00
Nuno CamposandGitHub 9fdbd0dd49 Merge pull request #1118 from langchain-ai/nc/24jul/fix-recursion-limit-thread
Fix recursion limit considering steps taken in previous runs on same thread
2024-07-24 09:35:12 -07:00
Nuno Campos 8a20c6f7e4 Avoid crash when a node in pending sends is removed 2024-07-24 09:29:38 -07:00
Nuno Campos 03f9b27e3b Fix 2024-07-24 09:26:49 -07:00
Nuno CamposandGitHub 19f6f7d5ca Merge pull request #1110 from langchain-ai/wfh/test_pending_writes_null
Test Null Pending Writes
2024-07-24 08:51:33 -07:00
Nuno Campos 745b96eb63 Fix recursion limit considering steps taken in previous runs on same thread 2024-07-24 08:50:32 -07:00
b90d44d97d Add local tool calling agent example (#1109)
* Add local tool calling agent example

* Update copy_notebooks.py

---------

Co-authored-by: Nuno Campos <nuno@langchain.dev>
2024-07-24 07:39:53 -07:00
Vadym BardaandGitHub d99d3e05c7 langgraph: release 0.1.12 (#1116) 2024-07-24 10:38:07 -04:00
Vadym BardaandGitHub ca6aef4746 langgraph: bring back tool content stringify (#1114) 2024-07-24 10:36:32 -04:00
William Fu-Hinthorn f5b9e463e3 Merge branch 'main' into wfh/test_pending_writes_null 2024-07-23 21:28:51 -07:00
William FHandGitHub 82cbe25be8 Update typedoc build (#1087) 2024-07-23 21:27:40 -07:00
William Fu-Hinthorn 36505d9656 Add test 2024-07-23 21:15:53 -07:00
Lance MartinandGitHub b72ea0ea1b Update MR docs (#1098) 2024-07-23 17:13:20 -07:00
Nuno Campos 03bf1e5414 Fix null pending writes 2024-07-23 14:55:07 -07:00
19 changed files with 585 additions and 2406 deletions
+11 -3
View File
@@ -1,6 +1,14 @@
.PHONY: build-docs serve-docs serve-clean-docs clean-docs codespell
.PHONY: build-docs serve-docs serve-clean-docs clean-docs codespell build-typedoc
build-docs:
build-typedoc:
cd libs/sdk-js && yarn install --include-dev && yarn typedoc
cd libs/sdk-js && yarn --silent concat-md --decrease-title-levels --ignore=js_ts_sdk_ref.md --start-title-level-at 2 docs > ../../docs/docs/cloud/reference/sdk/js_ts_sdk_ref.md 2>/dev/null
# Add links to the monorepo
sed -e '1,10s|@langchain/langgraph-sdk|[@langchain/langgraph-sdk](https://github.com/langchain-ai/langgraph/tree/main/libs/sdk-js)|g' docs/docs/cloud/reference/sdk/js_ts_sdk_ref.md > temp_file && mv temp_file docs/docs/cloud/reference/sdk/js_ts_sdk_ref.md
build-docs: build-typedoc
poetry run python docs/_scripts/copy_notebooks.py
poetry run python -m mkdocs build --clean -f docs/mkdocs.yml --strict
@@ -8,7 +16,7 @@ serve-clean-docs: clean-docs
poetry run python docs/_scripts/copy_notebooks.py
poetry run python -m mkdocs serve -c -f docs/mkdocs.yml --strict -w ./libs/langgraph
serve-docs:
serve-docs: build-typedoc
poetry run python docs/_scripts/copy_notebooks.py
poetry run python -m mkdocs serve -f docs/mkdocs.yml -w ./libs/langgraph --dirty
+1
View File
@@ -1,3 +1,4 @@
*.ipynb
site/
docs/tutorials/**/*.png
docs/cloud/reference/sdk/js_ts_sdk_ref.md
+1
View File
@@ -101,6 +101,7 @@ _HIDE = set(
"docs/quickstart.ipynb",
"tutorials/rag-agent-testing.ipynb",
"tutorials/rag-agent-testing-local.ipynb",
"tutorials/tool-calling-agent-local.ipynb",
"time-travel.ipynb",
"code_assistant/langgraph_code_assistant_mistral.ipynb",
]
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+88 -11
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+23 -2
View File
@@ -1,4 +1,5 @@
import asyncio
import json
from copy import copy
from typing import (
Any,
@@ -28,6 +29,16 @@ INVALID_TOOL_NAME_ERROR_TEMPLATE = (
TOOL_CALL_ERROR_TEMPLATE = "Error: {error}\n Please fix your mistakes."
def str_output(output: Any) -> str:
if isinstance(output, str):
return output
else:
try:
return json.dumps(output)
except Exception:
return str(output)
class ToolNode(RunnableCallable):
"""A node that runs the tools called in the last AIMessage.
@@ -94,7 +105,12 @@ class ToolNode(RunnableCallable):
try:
input = {**call, **{"type": "tool_call"}}
return self.tools_by_name[call["name"]].invoke(input, config)
tool_message: ToolMessage = self.tools_by_name[call["name"]].invoke(
input, config
)
# TODO: handle this properly in core
tool_message.content = str_output(tool_message.content)
return tool_message
except Exception as e:
if not self.handle_tool_errors:
raise e
@@ -106,7 +122,12 @@ class ToolNode(RunnableCallable):
return invalid_tool_message
try:
input = {**call, **{"type": "tool_call"}}
return await self.tools_by_name[call["name"]].ainvoke(input, config)
tool_message: ToolMessage = await self.tools_by_name[call["name"]].ainvoke(
input, config
)
# TODO: handle this properly in core
tool_message.content = str_output(tool_message.content)
return tool_message
except Exception as e:
if not self.handle_tool_errors:
raise e
+4 -2
View File
@@ -722,8 +722,10 @@ class Pregel(
if config and config.get("configurable", {}).get(CONFIG_KEY_READ) is not None:
# if being called as a node in another graph, always use values mode
stream_mode = ["values"]
if config is not None and config.get("configurable", {}).get(
CONFIG_KEY_CHECKPOINTER
if (
config is not None
and config.get("configurable", {}).get(CONFIG_KEY_CHECKPOINTER)
and (interrupt_after or interrupt_before)
):
checkpointer: Optional[BaseCheckpointSaver] = config["configurable"][
CONFIG_KEY_CHECKPOINTER
+3
View File
@@ -257,6 +257,9 @@ def prepare_next_tasks(
if not isinstance(packet, Send):
logger.warn(f"Ignoring invalid packet type {type(packet)} in pending sends")
continue
if packet.node not in processes:
logger.warn(f"Ignoring unknown node name {packet.node} in pending sends")
continue
if for_execution:
proc = processes[packet.node]
if node := proc.get_node():
+4 -1
View File
@@ -102,6 +102,7 @@ class PregelLoop:
checkpoint_pending_writes: List[PendingWrite]
step: int
stop: int
status: Literal[
"pending", "done", "interrupt_before", "interrupt_after", "out_of_steps"
]
@@ -203,7 +204,7 @@ class PregelLoop:
return False
# check if iteration limit is reached
if self.step > self.config["recursion_limit"]:
if self.step > self.stop:
self.status = "out_of_steps"
return False
@@ -419,6 +420,7 @@ class SyncPregelLoop(PregelLoop, ContextManager):
)
self.status = "pending"
self.step = self.checkpoint_metadata["step"] + 1
self.stop = self.step + self.config["recursion_limit"] + 1
return self
@@ -497,6 +499,7 @@ class AsyncPregelLoop(PregelLoop, AsyncContextManager):
)
self.status = "pending"
self.step = self.checkpoint_metadata["step"] + 1
self.stop = self.step + self.config["recursion_limit"] + 1
return self
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph"
version = "0.1.11"
version = "0.1.14"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
license = "MIT"
+9
View File
@@ -7,6 +7,7 @@ from langchain_core.runnables import RunnableConfig
from langgraph.checkpoint.base import (
Checkpoint,
CheckpointMetadata,
CheckpointTuple,
SerializerProtocol,
copy_checkpoint,
)
@@ -119,3 +120,11 @@ class MemorySaverAssertCheckpointMetadata(MemorySaver):
return await asyncio.get_running_loop().run_in_executor(
None, self.put, config, checkpoint, metadata
)
class MemorySaverNoPending(MemorySaver):
def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
result = super().get_tuple(config)
if result:
return CheckpointTuple(result.config, result.checkpoint, result.metadata)
return result
+32
View File
@@ -267,6 +267,13 @@ async def test_tool_node():
raise ValueError("Test error")
return f"tool2: {some_val} - {some_other_val}"
async def tool3(some_val: int, some_other_val: str) -> str:
"""Tool 3 docstring."""
return [
{"key_1": some_val, "key_2": "foo"},
{"key_1": some_other_val, "key_2": "baz"},
]
result = ToolNode([tool1]).invoke(
{
"messages": [
@@ -377,6 +384,31 @@ async def test_tool_node():
)
assert tool_message.tool_call_id == "some 0"
# list of dicts tool content
result3 = await ToolNode([tool3]).ainvoke(
{
"messages": [
AIMessage(
"hi?",
tool_calls=[
{
"name": "tool3",
"args": {"some_val": 2, "some_other_val": "bar"},
"id": "some 0",
}
],
)
]
}
)
tool_message: ToolMessage = result3["messages"][-1]
assert tool_message.type == "tool"
assert (
tool_message.content
== '[{"key_1": 2, "key_2": "foo"}, {"key_1": "bar", "key_2": "baz"}]'
)
assert tool_message.tool_call_id == "some 0"
def my_function(some_val: int, some_other_val: str) -> str:
return f"{some_val} - {some_other_val}"
+25 -1
View File
@@ -66,6 +66,7 @@ from tests.any_str import AnyStr
from tests.memory_assert import (
MemorySaverAssertCheckpointMetadata,
MemorySaverAssertImmutable,
MemorySaverNoPending,
NoopSerializer,
)
@@ -1149,10 +1150,32 @@ def test_cond_edge_after_send() -> None:
builder.add_conditional_edges("1", send_for_fun)
builder.add_conditional_edges("2", route_to_three)
graph = builder.compile()
assert graph.invoke(["0"]) == ["0", "1", "2", "3"]
async def test_checkpointer_null_pending_writes() -> None:
class Node:
def __init__(self, name: str):
self.name = name
setattr(self, "__name__", name)
def __call__(self, state):
return [self.name]
builder = StateGraph(Annotated[list, operator.add])
builder.add_node(Node("1"))
builder.add_edge(START, "1")
graph = builder.compile(checkpointer=MemorySaverNoPending())
assert graph.invoke([], {"configurable": {"thread_id": "foo"}}) == ["1"]
assert graph.invoke([], {"configurable": {"thread_id": "foo"}}) == ["1"] * 2
assert (await graph.ainvoke([], {"configurable": {"thread_id": "foo"}})) == [
"1"
] * 3
assert (await graph.ainvoke([], {"configurable": {"thread_id": "foo"}})) == [
"1"
] * 4
def test_invoke_checkpoint_sqlite(mocker: MockerFixture) -> None:
adder = mocker.Mock(side_effect=lambda x: x["total"] + x["input"])
@@ -8570,6 +8593,7 @@ def test_nested_graph_interrupts_parallel(checkpointer: BaseCheckpointSaver) ->
checkpointer.__exit__(None, None, None)
@pytest.mark.skip
@pytest.mark.parametrize(
"checkpointer",
[
@@ -7099,6 +7099,7 @@ async def test_nested_graph_interrupts_parallel(
await checkpointer.__aexit__(None, None, None)
@pytest.mark.skip
@pytest.mark.parametrize(
"checkpointer",
[
+13 -3
View File
@@ -38,9 +38,19 @@ const updateConfig = () => {
...json,
typedocOptions: {
...json.typedocOptions,
entryPoints: [...Object.keys(entrypoints)].map(
(key) => `src/${entrypoints[key]}.ts`,
),
entryPoints: [...Object.keys(entrypoints)].map((key) => {
const basePath = `src/${entrypoints[key]}`;
if (fs.existsSync(`${basePath}.mts`)) {
return `${basePath}.mts`;
} else if (fs.existsSync(`${basePath}.ts`)) {
return `${basePath}.ts`;
} else {
console.warn(
`Warning: Neither ${basePath}.mts nor ${basePath}.ts found for entrypoint ${key}`,
);
return `${basePath}.ts`; // Default to .ts if neither exists
}
}),
},
}));
+4 -4
View File
@@ -107,7 +107,7 @@ class BaseClient {
}
}
class CronsClient extends BaseClient {
export class CronsClient extends BaseClient {
/**
*
* @param threadId The ID of the thread.
@@ -195,7 +195,7 @@ class CronsClient extends BaseClient {
}
}
class AssistantsClient extends BaseClient {
export class AssistantsClient extends BaseClient {
/**
* Get an assistant by ID.
*
@@ -302,7 +302,7 @@ class AssistantsClient extends BaseClient {
}
}
class ThreadsClient extends BaseClient {
export class ThreadsClient extends BaseClient {
/**
* Get a thread by ID.
*
@@ -496,7 +496,7 @@ class ThreadsClient extends BaseClient {
}
}
class RunsClient extends BaseClient {
export class RunsClient extends BaseClient {
stream(
threadId: null,
assistantId: string,
+18 -7
View File
@@ -2,7 +2,11 @@
"extends": "@tsconfig/recommended",
"compilerOptions": {
"target": "ES2021",
"lib": ["ES2021", "ES2022.Object", "DOM"],
"lib": [
"ES2021",
"ES2022.Object",
"DOM"
],
"module": "NodeNext",
"moduleResolution": "nodenext",
"esModuleInterop": true,
@@ -17,19 +21,26 @@
"strict": true,
"outDir": "dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "coverage"],
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist",
"coverage"
],
"includeVersion": true,
"typedocOptions": {
"entryPoints": [
"src/client.mts",
"src/schema.ts",
"src/types.mts"
"src/client.mts"
],
"readme": "none",
"out": "docs",
"plugin": [
"typedoc-plugin-markdown"
]
],
"excludePrivate": true,
"excludeProtected": true,
"excludeExternals": false
}
}