Don't try to serialize exceptions

- Store their string repr instead
This commit is contained in:
Nuno Campos
2024-08-22 21:36:27 -07:00
parent dec7eb6f58
commit 82db383199
5 changed files with 30 additions and 47 deletions
@@ -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"]:
-18
View File
@@ -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
-2
View File
@@ -24,8 +24,6 @@ class NoopSerializer(SerializerProtocol):
class MemorySaverAssertImmutable(MemorySaver):
serde = NoopSerializer()
storage_for_copies: defaultdict[str, dict[str, dict[str, Checkpoint]]]
def __init__(
+5 -5
View File
@@ -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),
),
+21 -21
View File
@@ -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=(),