mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-28 18:59:42 +02:00
Don't try to serialize exceptions
- Store their string repr instead
This commit is contained in:
@@ -7,6 +7,7 @@ import re
|
||||
from collections import deque
|
||||
from datetime import date, datetime, time, timedelta, timezone
|
||||
from enum import Enum
|
||||
from inspect import isclass
|
||||
from ipaddress import (
|
||||
IPv4Address,
|
||||
IPv4Interface,
|
||||
@@ -111,7 +112,7 @@ class JsonPlusSerializer(SerializerProtocol):
|
||||
obj.__class__, method="fromhex", args=[obj.hex()]
|
||||
)
|
||||
elif isinstance(obj, BaseException):
|
||||
return self._encode_constructor_args(obj.__class__, args=obj.args)
|
||||
return repr(obj)
|
||||
else:
|
||||
raise TypeError(
|
||||
f"Object of type {obj.__class__.__name__} is not JSON serializable"
|
||||
@@ -135,6 +136,8 @@ class JsonPlusSerializer(SerializerProtocol):
|
||||
method = getattr(cls, value["method"])
|
||||
else:
|
||||
method = cls
|
||||
if isclass(method) and issubclass(method, BaseException):
|
||||
return None
|
||||
if value["args"] and value["kwargs"]:
|
||||
return method(*value["args"], **value["kwargs"])
|
||||
elif value["args"]:
|
||||
|
||||
@@ -23,24 +23,6 @@ class AnyVersion:
|
||||
return hash(str(self))
|
||||
|
||||
|
||||
class ExceptionLike:
|
||||
def __init__(self, exc: Exception) -> None:
|
||||
self.exc = exc
|
||||
|
||||
def __eq__(self, value: object) -> bool:
|
||||
return (
|
||||
isinstance(value, Exception)
|
||||
and self.exc.__class__ == value.__class__
|
||||
and str(self.exc) == str(value)
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.exc.__class__, str(self.exc)))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return str(self.exc)
|
||||
|
||||
|
||||
class UnsortedSequence:
|
||||
def __init__(self, *values: Any) -> None:
|
||||
self.seq = values
|
||||
|
||||
@@ -24,8 +24,6 @@ class NoopSerializer(SerializerProtocol):
|
||||
|
||||
|
||||
class MemorySaverAssertImmutable(MemorySaver):
|
||||
serde = NoopSerializer()
|
||||
|
||||
storage_for_copies: defaultdict[str, dict[str, dict[str, Checkpoint]]]
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -64,7 +64,7 @@ from langgraph.pregel import Channel, GraphRecursionError, Pregel, StateSnapshot
|
||||
from langgraph.pregel.retry import RetryPolicy
|
||||
from langgraph.pregel.types import PregelTask
|
||||
from langgraph.store.memory import MemoryStore
|
||||
from tests.any_str import AnyStr, AnyVersion, ExceptionLike, UnsortedSequence
|
||||
from tests.any_str import AnyStr, AnyVersion, UnsortedSequence
|
||||
from tests.fake_tracer import FakeTracer
|
||||
from tests.memory_assert import (
|
||||
MemorySaverAssertCheckpointMetadata,
|
||||
@@ -1307,7 +1307,7 @@ def test_invoke_checkpoint_two(
|
||||
assert checkpoint_tup is not None
|
||||
assert checkpoint_tup.checkpoint["channel_values"].get("total") == 7
|
||||
assert checkpoint_tup.pending_writes == [
|
||||
(AnyStr(), ERROR, ExceptionLike(ValueError("Input is too large")))
|
||||
(AnyStr(), ERROR, "ValueError('Input is too large')")
|
||||
]
|
||||
# on a new thread, total starts out as 0, so output is 0+5=5
|
||||
assert app.invoke(5, {"configurable": {"thread_id": "2"}}) == 5
|
||||
@@ -1374,7 +1374,7 @@ def test_pending_writes_resume(
|
||||
assert state.next == ("one", "two")
|
||||
assert state.tasks == (
|
||||
PregelTask(AnyStr(), "one"),
|
||||
PregelTask(AnyStr(), "two", ExceptionLike(ConnectionError("I'm not good"))),
|
||||
PregelTask(AnyStr(), "two", 'ConnectionError("I\'m not good")'),
|
||||
)
|
||||
assert state.metadata == {"source": "loop", "step": 0, "writes": None}
|
||||
# should contain pending write of "one"
|
||||
@@ -1384,7 +1384,7 @@ def test_pending_writes_resume(
|
||||
expected_writes = [
|
||||
(AnyStr(), "one", "one"),
|
||||
(AnyStr(), "value", 2),
|
||||
(AnyStr(), ERROR, ExceptionLike(ConnectionError("I'm not good"))),
|
||||
(AnyStr(), ERROR, 'ConnectionError("I\'m not good")'),
|
||||
]
|
||||
assert len(checkpoint.pending_writes) == 3
|
||||
assert all(w in expected_writes for w in checkpoint.pending_writes)
|
||||
@@ -1518,7 +1518,7 @@ def test_pending_writes_resume(
|
||||
pending_writes=UnsortedSequence(
|
||||
(AnyStr(), "one", "one"),
|
||||
(AnyStr(), "value", 2),
|
||||
(AnyStr(), "__error__", ExceptionLike(ConnectionError("I'm not good"))),
|
||||
(AnyStr(), "__error__", 'ConnectionError("I\'m not good")'),
|
||||
(AnyStr(), "two", "two"),
|
||||
(AnyStr(), "value", 3),
|
||||
),
|
||||
|
||||
@@ -61,7 +61,7 @@ from langgraph.pregel import Channel, GraphRecursionError, Pregel, StateSnapshot
|
||||
from langgraph.pregel.retry import RetryPolicy
|
||||
from langgraph.pregel.types import PregelTask
|
||||
from langgraph.store.memory import MemoryStore
|
||||
from tests.any_str import AnyStr, AnyVersion, ExceptionLike, UnsortedSequence
|
||||
from tests.any_str import AnyStr, AnyVersion, UnsortedSequence
|
||||
from tests.fake_tracer import FakeTracer
|
||||
from tests.memory_assert import (
|
||||
MemorySaverAssertCheckpointMetadata,
|
||||
@@ -1593,7 +1593,7 @@ async def test_pending_writes_resume(
|
||||
assert state.next == ("one", "two")
|
||||
assert state.tasks == (
|
||||
PregelTask(AnyStr(), "one"),
|
||||
PregelTask(AnyStr(), "two", ExceptionLike(ValueError("I'm not good"))),
|
||||
PregelTask(AnyStr(), "two", 'ValueError("I\'m not good")'),
|
||||
)
|
||||
assert state.metadata == {"source": "loop", "step": 0, "writes": None}
|
||||
# should contain pending write of "one"
|
||||
@@ -1603,7 +1603,7 @@ async def test_pending_writes_resume(
|
||||
expected_writes = [
|
||||
(AnyStr(), "one", "one"),
|
||||
(AnyStr(), "value", 2),
|
||||
(AnyStr(), ERROR, ExceptionLike(ValueError("I'm not good"))),
|
||||
(AnyStr(), ERROR, 'ValueError("I\'m not good")'),
|
||||
]
|
||||
assert len(checkpoint.pending_writes) == 3
|
||||
assert all(w in expected_writes for w in checkpoint.pending_writes)
|
||||
@@ -1741,7 +1741,7 @@ async def test_pending_writes_resume(
|
||||
pending_writes=UnsortedSequence(
|
||||
(AnyStr(), "one", "one"),
|
||||
(AnyStr(), "value", 2),
|
||||
(AnyStr(), "__error__", ExceptionLike(ValueError("I'm not good"))),
|
||||
(AnyStr(), "__error__", 'ValueError("I\'m not good")'),
|
||||
(AnyStr(), "two", "two"),
|
||||
(AnyStr(), "value", 3),
|
||||
),
|
||||
@@ -3108,7 +3108,7 @@ async def test_conditional_graph_state(mocker: MockerFixture) -> None:
|
||||
observation = {t.name: t for t in tools}[agent_action.tool].invoke(
|
||||
agent_action.tool_input
|
||||
)
|
||||
return {"intermediate_steps": [(agent_action, observation)]}
|
||||
return {"intermediate_steps": [[agent_action, observation]]}
|
||||
|
||||
# Define decision-making logic
|
||||
def should_continue(data: AgentState) -> str:
|
||||
@@ -3140,22 +3140,22 @@ async def test_conditional_graph_state(mocker: MockerFixture) -> None:
|
||||
assert await app.ainvoke({"input": "what is weather in sf"}) == {
|
||||
"input": "what is weather in sf",
|
||||
"intermediate_steps": [
|
||||
(
|
||||
[
|
||||
AgentAction(
|
||||
tool="search_api",
|
||||
tool_input="query",
|
||||
log="tool:search_api:query",
|
||||
),
|
||||
"result for query",
|
||||
),
|
||||
(
|
||||
],
|
||||
[
|
||||
AgentAction(
|
||||
tool="search_api",
|
||||
tool_input="another",
|
||||
log="tool:search_api:another",
|
||||
),
|
||||
"result for another",
|
||||
),
|
||||
],
|
||||
],
|
||||
"agent_outcome": AgentFinish(
|
||||
return_values={"answer": "answer"}, log="finish:answer"
|
||||
@@ -3176,14 +3176,14 @@ async def test_conditional_graph_state(mocker: MockerFixture) -> None:
|
||||
{
|
||||
"tools": {
|
||||
"intermediate_steps": [
|
||||
(
|
||||
[
|
||||
AgentAction(
|
||||
tool="search_api",
|
||||
tool_input="query",
|
||||
log="tool:search_api:query",
|
||||
),
|
||||
"result for query",
|
||||
)
|
||||
]
|
||||
],
|
||||
}
|
||||
},
|
||||
@@ -3199,14 +3199,14 @@ async def test_conditional_graph_state(mocker: MockerFixture) -> None:
|
||||
{
|
||||
"tools": {
|
||||
"intermediate_steps": [
|
||||
(
|
||||
[
|
||||
AgentAction(
|
||||
tool="search_api",
|
||||
tool_input="another",
|
||||
log="tool:search_api:another",
|
||||
),
|
||||
"result for another",
|
||||
),
|
||||
],
|
||||
],
|
||||
}
|
||||
},
|
||||
@@ -3361,14 +3361,14 @@ async def test_conditional_graph_state(mocker: MockerFixture) -> None:
|
||||
{
|
||||
"tools": {
|
||||
"intermediate_steps": [
|
||||
(
|
||||
[
|
||||
AgentAction(
|
||||
tool="search_api",
|
||||
tool_input="query",
|
||||
log="tool:search_api:a different query",
|
||||
),
|
||||
"result for query",
|
||||
)
|
||||
]
|
||||
],
|
||||
}
|
||||
},
|
||||
@@ -3401,14 +3401,14 @@ async def test_conditional_graph_state(mocker: MockerFixture) -> None:
|
||||
log="finish:a really nice answer",
|
||||
),
|
||||
"intermediate_steps": [
|
||||
(
|
||||
[
|
||||
AgentAction(
|
||||
tool="search_api",
|
||||
tool_input="query",
|
||||
log="tool:search_api:a different query",
|
||||
),
|
||||
"result for query",
|
||||
)
|
||||
]
|
||||
],
|
||||
},
|
||||
tasks=(),
|
||||
@@ -3537,14 +3537,14 @@ async def test_conditional_graph_state(mocker: MockerFixture) -> None:
|
||||
{
|
||||
"tools": {
|
||||
"intermediate_steps": [
|
||||
(
|
||||
[
|
||||
AgentAction(
|
||||
tool="search_api",
|
||||
tool_input="query",
|
||||
log="tool:search_api:a different query",
|
||||
),
|
||||
"result for query",
|
||||
)
|
||||
]
|
||||
],
|
||||
}
|
||||
},
|
||||
@@ -3576,14 +3576,14 @@ async def test_conditional_graph_state(mocker: MockerFixture) -> None:
|
||||
log="finish:a really nice answer",
|
||||
),
|
||||
"intermediate_steps": [
|
||||
(
|
||||
[
|
||||
AgentAction(
|
||||
tool="search_api",
|
||||
tool_input="query",
|
||||
log="tool:search_api:a different query",
|
||||
),
|
||||
"result for query",
|
||||
)
|
||||
]
|
||||
],
|
||||
},
|
||||
tasks=(),
|
||||
|
||||
Reference in New Issue
Block a user