[docs]classBaseSerialized(TypedDict):"""Base class for serialized objects. Parameters: lc: The version of the serialization format. id: The unique identifier of the object. name: The name of the object. Optional. graph: The graph of the object. Optional. """lc:intid:list[str]name:NotRequired[str]graph:NotRequired[dict[str,Any]]
[docs]classSerializedConstructor(BaseSerialized):"""Serialized constructor. Parameters: type: The type of the object. Must be "constructor". kwargs: The constructor arguments. """type:Literal["constructor"]kwargs:dict[str,Any]
[docs]classSerializedSecret(BaseSerialized):"""Serialized secret. Parameters: type: The type of the object. Must be "secret". """type:Literal["secret"]
[docs]classSerializedNotImplemented(BaseSerialized):"""Serialized not implemented. Parameters: type: The type of the object. Must be "not_implemented". repr: The representation of the object. Optional. """type:Literal["not_implemented"]repr:Optional[str]
[docs]deftry_neq_default(value:Any,key:str,model:BaseModel)->bool:"""Try to determine if a value is different from the default. Args: value: The value. key: The key. model: The pydantic model. Returns: Whether the value is different from the default. Raises: Exception: If the key is not in the model. """field=type(model).model_fields[key]return_try_neq_default(value,field)
def_try_neq_default(value:Any,field:FieldInfo)->bool:# Handle edge case: inequality of two objects does not evaluate to a bool (e.g. two# Pandas DataFrames).try:returnbool(field.get_default()!=value)exceptExceptionas_:try:returnall(field.get_default()!=value)exceptExceptionas_:try:returnvalueisnotfield.defaultexceptExceptionas_:returnFalse
[docs]classSerializable(BaseModel,ABC):"""Serializable base class. This class is used to serialize objects to JSON. It relies on the following methods and properties: - `is_lc_serializable`: Is this class serializable? By design, even if a class inherits from Serializable, it is not serializable by default. This is to prevent accidental serialization of objects that should not be serialized. - `get_lc_namespace`: Get the namespace of the langchain object. During deserialization, this namespace is used to identify the correct class to instantiate. Please see the `Reviver` class in `langchain_core.load.load` for more details. During deserialization an additional mapping is handle classes that have moved or been renamed across package versions. - `lc_secrets`: A map of constructor argument names to secret ids. - `lc_attributes`: List of additional attribute names that should be included as part of the serialized representation. """# Remove default BaseModel init docstring.def__init__(self,*args:Any,**kwargs:Any)->None:""""""super().__init__(*args,**kwargs)@classmethoddefis_lc_serializable(cls)->bool:"""Is this class serializable? By design, even if a class inherits from Serializable, it is not serializable by default. This is to prevent accidental serialization of objects that should not be serialized. Returns: Whether the class is serializable. Default is False. """returnFalse@classmethoddefget_lc_namespace(cls)->list[str]:"""Get the namespace of the langchain object. For example, if the class is `langchain.llms.openai.OpenAI`, then the namespace is ["langchain", "llms", "openai"] """returncls.__module__.split(".")@propertydeflc_secrets(self)->dict[str,str]:"""A map of constructor argument names to secret ids. For example, {"openai_api_key": "OPENAI_API_KEY"} """return{}@propertydeflc_attributes(self)->dict:"""List of attribute names that should be included in the serialized kwargs. These attributes must be accepted by the constructor. Default is an empty dictionary. """return{}@classmethoddeflc_id(cls)->list[str]:"""A unique identifier for this class for serialization purposes. The unique identifier is a list of strings that describes the path to the object. For example, for the class `langchain.llms.openai.OpenAI`, the id is ["langchain", "llms", "openai", "OpenAI"]. """# Pydantic generics change the class name. So we need to do the followingif("origin"incls.__pydantic_generic_metadata__andcls.__pydantic_generic_metadata__["origin"]isnotNone):original_name=cls.__pydantic_generic_metadata__["origin"].__name__else:original_name=cls.__name__return[*cls.get_lc_namespace(),original_name]model_config=ConfigDict(extra="ignore",)def__repr_args__(self)->Any:return[(k,v)fork,vinsuper().__repr_args__()if(knotintype(self).model_fieldsortry_neq_default(v,k,self))]defto_json(self)->Union[SerializedConstructor,SerializedNotImplemented]:"""Serialize the object to JSON. Returns: A json serializable object or a SerializedNotImplemented object. """ifnotself.is_lc_serializable():returnself.to_json_not_implemented()model_fields=type(self).model_fieldssecrets={}# Get latest values for kwargs if there is an attribute with same namelc_kwargs={}fork,vinself:ifnot_is_field_useful(self,k,v):continue# Do nothing if the field is excludedifkinmodel_fieldsandmodel_fields[k].exclude:continuelc_kwargs[k]=getattr(self,k,v)# Merge the lc_secrets and lc_attributes from every class in the MROforclsin[None,*self.__class__.mro()]:# Once we get to Serializable, we're doneifclsisSerializable:breakifcls:deprecated_attributes=["lc_namespace","lc_serializable",]forattrindeprecated_attributes:ifhasattr(cls,attr):msg=(f"Class {self.__class__} has a deprecated "f"attribute {attr}. Please use the corresponding "f"classmethod instead.")raiseValueError(msg)# Get a reference to self bound to each class in the MROthis=cast("Serializable",selfifclsisNoneelsesuper(cls,self))secrets.update(this.lc_secrets)# Now also add the aliases for the secrets# This ensures known secret aliases are hidden.# Note: this does NOT hide any other extra kwargs# that are not present in the fields.forkeyinlist(secrets):value=secrets[key]if(keyinmodel_fields)and(alias:=model_fields[key].alias)isnotNone:secrets[alias]=valuelc_kwargs.update(this.lc_attributes)# include all secrets, even if not specified in kwargs# as these secrets may be passed as an environment variable insteadforkeyinsecrets:secret_value=getattr(self,key,None)orlc_kwargs.get(key)ifsecret_valueisnotNone:lc_kwargs.update({key:secret_value})return{"lc":1,"type":"constructor","id":self.lc_id(),"kwargs":lc_kwargsifnotsecretselse_replace_secrets(lc_kwargs,secrets),}defto_json_not_implemented(self)->SerializedNotImplemented:returnto_json_not_implemented(self)
def_is_field_useful(inst:Serializable,key:str,value:Any)->bool:"""Check if a field is useful as a constructor argument. Args: inst: The instance. key: The key. value: The value. Returns: Whether the field is useful. If the field is required, it is useful. If the field is not required, it is useful if the value is not None. If the field is not required and the value is None, it is useful if the default value is different from the value. """field=type(inst).model_fields.get(key)ifnotfield:returnFalseiffield.is_required():returnTrue# Handle edge case: a value cannot be converted to a boolean (e.g. a# Pandas DataFrame).try:value_is_truthy=bool(value)exceptExceptionas_:value_is_truthy=Falseifvalue_is_truthy:returnTrue# Value is still falsy here!iffield.default_factoryisdictandisinstance(value,dict):returnFalse# Value is still falsy here!iffield.default_factoryislistandisinstance(value,list):returnFalsevalue_neq_default=_try_neq_default(value,field)# If value is falsy and does not match the defaultreturnvalue_is_truthyorvalue_neq_defaultdef_replace_secrets(root:dict[Any,Any],secrets_map:dict[str,str])->dict[Any,Any]:result=root.copy()forpath,secret_idinsecrets_map.items():[*parts,last]=path.split(".")current=resultforpartinparts:ifpartnotincurrent:breakcurrent[part]=current[part].copy()current=current[part]iflastincurrent:current[last]={"lc":1,"type":"secret","id":[secret_id],}returnresult
[docs]defto_json_not_implemented(obj:object)->SerializedNotImplemented:"""Serialize a "not implemented" object. Args: obj: object to serialize. Returns: SerializedNotImplemented """_id:list[str]=[]try:ifhasattr(obj,"__name__"):_id=[*obj.__module__.split("."),obj.__name__]elifhasattr(obj,"__class__"):_id=[*obj.__class__.__module__.split("."),obj.__class__.__name__]exceptException:passresult:SerializedNotImplemented={"lc":1,"type":"not_implemented","id":_id,"repr":None,}withcontextlib.suppress(Exception):result["repr"]=repr(obj)returnresult