Skip to content

Commit b80fafe

Browse files
committed
Add extra serialization step similar to pydantic docs for circular references
Signed-off-by: Nijat Khanbabayev <[email protected]>
1 parent a721aab commit b80fafe

File tree

2 files changed

+50
-5
lines changed

2 files changed

+50
-5
lines changed

ccflow/base.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import copy
55
import logging
66
import pathlib
7-
import platform
87
import sys
98
import warnings
109
from types import MappingProxyType
@@ -123,10 +122,14 @@ def type_(self) -> PyObjectPath:
123122

124123
# https://docs.pydantic.dev/latest/concepts/serialization/#overriding-the-serialize_as_any-default-false
125124
def model_dump(self, **kwargs) -> dict[str, Any]:
126-
return super().model_dump(serialize_as_any=True, **kwargs)
125+
if not kwargs.get("serialize_as_any"):
126+
kwargs["serialize_as_any"] = True
127+
return super().model_dump(**kwargs)
127128

128129
def model_dump_json(self, **kwargs) -> str:
129-
return super().model_dump_json(serialize_as_any=True, **kwargs)
130+
if not kwargs.get("serialize_as_any"):
131+
kwargs["serialize_as_any"] = True
132+
return super().model_dump_json(**kwargs)
130133

131134
def __str__(self):
132135
# Because the standard string representation does not include class name
@@ -322,7 +325,8 @@ def _validate_name(cls, v):
322325
@model_serializer(mode="wrap")
323326
def _registry_serializer(self, handler):
324327
values = handler(self)
325-
values["models"] = self._models
328+
models_serialized = {k: model.model_dump(serialize_as_any=True, by_alias=True) for k, model in self._models.items()}
329+
values["models"] = models_serialized
326330
return values
327331

328332
@property

ccflow/callable.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,18 @@
1717
from inspect import Signature, isclass, signature
1818
from typing import Any, ClassVar, Dict, Generic, List, Optional, Tuple, Type, TypeVar
1919

20-
from pydantic import BaseModel as PydanticBaseModel, ConfigDict, Field, InstanceOf, PrivateAttr, TypeAdapter, field_validator, model_validator
20+
from pydantic import (
21+
BaseModel as PydanticBaseModel,
22+
ConfigDict,
23+
Field,
24+
InstanceOf,
25+
PrivateAttr,
26+
SerializerFunctionWrapHandler,
27+
TypeAdapter,
28+
field_validator,
29+
model_serializer,
30+
model_validator,
31+
)
2132
from typing_extensions import override
2233

2334
from .base import (
@@ -426,6 +437,36 @@ def __call__(self) -> ResultType:
426437
else:
427438
return fn(self.context)
428439

440+
# When serialize_as_any=True, pydantic may detect repeated object ids in nested graphs
441+
# (e.g., shared default lists) and raise a circular reference error during serialization.
442+
# For computing cache keys, fall back to a minimal, stable representation if such an error occurs.
443+
# This is similar to how we the pydantic docs here:
444+
# https://docs.pydantic.dev/latest/concepts/forward_annotations/#cyclic-references
445+
# handle cyclic references during serialization.
446+
@model_serializer(mode="wrap")
447+
def _serialize_model_evaluation_context(self, handler: SerializerFunctionWrapHandler):
448+
try:
449+
return handler(self)
450+
except ValueError as exc:
451+
msg = str(exc)
452+
if "Circular reference" not in msg and "id repeated" not in msg:
453+
raise
454+
# Minimal, stable representation sufficient for cache-key tokenization
455+
try:
456+
model_repr = self.model.model_dump(mode="python", serialize_as_any=True, by_alias=True)
457+
except Exception:
458+
model_repr = repr(self.model)
459+
try:
460+
context_repr = self.context.model_dump(mode="python", serialize_as_any=True, by_alias=True)
461+
except Exception:
462+
context_repr = repr(self.context)
463+
return dict(
464+
fn=self.fn,
465+
model=model_repr,
466+
context=context_repr,
467+
options=dict(self.options),
468+
)
469+
429470

430471
class EvaluatorBase(_CallableModel, abc.ABC):
431472
"""Base class for evaluators, which are higher-order models that evaluate ModelAndContext.

0 commit comments

Comments
 (0)