|
17 | 17 | from inspect import Signature, isclass, signature |
18 | 18 | from typing import Any, ClassVar, Dict, Generic, List, Optional, Tuple, Type, TypeVar |
19 | 19 |
|
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 | +) |
21 | 32 | from typing_extensions import override |
22 | 33 |
|
23 | 34 | from .base import ( |
@@ -426,6 +437,36 @@ def __call__(self) -> ResultType: |
426 | 437 | else: |
427 | 438 | return fn(self.context) |
428 | 439 |
|
| 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 | + |
429 | 470 |
|
430 | 471 | class EvaluatorBase(_CallableModel, abc.ABC): |
431 | 472 | """Base class for evaluators, which are higher-order models that evaluate ModelAndContext. |
|
0 commit comments