Source code for langchain_experimental.cpal.models
from__future__importannotations# allows pydantic model to reference itselfimportrefromtypingimportAny,List,Optional,Unionfromlangchain_community.graphs.networkx_graphimportNetworkxEntityGraphfrompydanticimport(BaseModel,ConfigDict,Field,PrivateAttr,field_validator,model_validator,)fromlangchain_experimental.cpal.constantsimportConstant
[docs]classNarrativeModel(BaseModel):""" Narrative input as three story elements. """story_outcome_question:strstory_hypothetical:strstory_plot:str# causal stack of operations@field_validator("*",mode="before")defempty_str_to_none(cls,v:str)->Union[str,None]:"""Empty strings are not allowed"""ifv=="":returnNonereturnv
[docs]classEntityModel(BaseModel):"""Entity in the story."""name:str=Field(description="entity name")code:str=Field(description="entity actions")value:float=Field(description="entity initial value")depends_on:List[str]=Field(default=[],description="ancestor entities")# TODO: generalize to multivariate math# TODO: acyclic graphmodel_config=ConfigDict(validate_assignment=True,)@field_validator("name")deflower_case_name(cls,v:str)->str:v=v.lower()returnv
[docs]classCausalModel(BaseModel):"""Casual data."""attribute:str=Field(description="name of the attribute to be calculated")entities:List[EntityModel]=Field(description="entities in the story")
# TODO: root validate each `entity.depends_on` using system's entity names
[docs]classEntitySettingModel(BaseModel):"""Entity initial conditions. Initial conditions for an entity {"name": "bud", "attribute": "pet_count", "value": 12} """name:str=Field(description="name of the entity")attribute:str=Field(description="name of the attribute to be calculated")value:float=Field(description="entity's attribute value (calculated)")@field_validator("name")deflower_case_transform(cls,v:str)->str:v=v.lower()returnv
[docs]classSystemSettingModel(BaseModel):"""System initial conditions. Initial global conditions for the system. {"parameter": "interest_rate", "value": .05} """parameter:strvalue:float
[docs]classInterventionModel(BaseModel):"""Intervention data of the story aka initial conditions. >>> intervention.dict() { entity_settings: [ {"name": "bud", "attribute": "pet_count", "value": 12}, {"name": "pat", "attribute": "pet_count", "value": 0}, ], system_settings: None, } """entity_settings:List[EntitySettingModel]system_settings:Optional[List[SystemSettingModel]]=None@field_validator("system_settings")deflower_case_name(cls,v:str)->Union[str,None]:ifvisnotNone:raiseNotImplementedError("system_setting is not implemented yet")returnv
[docs]classQueryModel(BaseModel):"""Query data of the story. translate a question about the story outcome into a programmatic expression"""question:str=Field(# type: ignore[literal-required]alias=Constant.narrative_input.value)# input # type: ignore[literal-required]expression:str# output, part of llm completionllm_error_msg:str# output, part of llm completion_result_table:str=PrivateAttr()# result of the executed query
[docs]classResultModel(BaseModel):"""Result of the story query."""question:str=Field(# type: ignore[literal-required]alias=Constant.narrative_input.value)# input # type: ignore[literal-required]_result_table:str=PrivateAttr()# result of the executed query
[docs]classStoryModel(BaseModel):"""Story data."""causal_operations:Any=Field()intervention:Any=Field()query:Any=Field()_outcome_table:Any=PrivateAttr(default=None)_networkx_wrapper:Any=PrivateAttr(default=None)def__init__(self,**kwargs:Any):super().__init__(**kwargs)self._compute()# TODO: when langchain adopts pydantic.v2 replace w/ `__post_init__`# misses hints github.com/pydantic/pydantic/issues/1729#issuecomment-1300576214@model_validator(mode="before")@classmethoddefcheck_intervention_is_valid(cls,values:dict)->Any:valid_names=[e.nameforeinvalues["causal_operations"].entities]forsettinginvalues["intervention"].entity_settings:ifsetting.namenotinvalid_names:error_msg=f""" Hypothetical question has an invalid entity name. `{setting.name}` not in `{valid_names}` """raiseValueError(error_msg)returnvaluesdef_block_back_door_paths(self)->None:# stop intervention entities from depending on othersintervention_entities=[entity_setting.nameforentity_settinginself.intervention.entity_settings]forentityinself.causal_operations.entities:ifentity.nameinintervention_entities:entity.depends_on=[]entity.code="pass"def_set_initial_conditions(self)->None:forentity_settinginself.intervention.entity_settings:forentityinself.causal_operations.entities:ifentity.name==entity_setting.name:entity.value=entity_setting.valuedef_make_graph(self)->None:self._networkx_wrapper=NetworkxEntityGraph()forentityinself.causal_operations.entities:forparent_nameinentity.depends_on:self._networkx_wrapper._graph.add_edge(parent_name,entity.name,relation=entity.code)# TODO: is it correct to drop entities with no impact on the outcome (?)self.causal_operations.entities=[entityforentityinself.causal_operations.entitiesifentity.nameinself._networkx_wrapper.get_topological_sort()]def_sort_entities(self)->None:# order the sequence of causal actionssorted_nodes=self._networkx_wrapper.get_topological_sort()self.causal_operations.entities.sort(key=lambdax:sorted_nodes.index(x.name))def_forward_propagate(self)->None:try:importpandasaspdexceptImportErrorase:raiseImportError("Unable to import pandas, please install with `pip install pandas`.")fromeentity_scope={entity.name:entityforentityinself.causal_operations.entities}forentityinself.causal_operations.entities:ifentity.code=="pass":continueelse:# gist.github.com/dean0x7d/df5ce97e4a1a05be4d56d1378726ff92exec(entity.code,globals(),entity_scope)row_values=[entity.dict()forentityinentity_scope.values()]self._outcome_table=pd.DataFrame(row_values)def_run_query(self)->None:defhumanize_sql_error_msg(error:str)->str:pattern=r"column\s+(.*?)\s+not found"col_match=re.search(pattern,error)ifcol_match:return("SQL error: "+col_match.group(1)+" is not an attribute in your story!")else:returnstr(error)ifself.query.llm_error_msg=="":try:importduckdbdf=self._outcome_table# noqaquery_result=duckdb.sql(self.query.expression).df()self.query._result_table=query_resultexceptduckdb.BinderExceptionase:self.query._result_table=humanize_sql_error_msg(str(e))exceptImportErrorase:raiseImportError("Unable to import duckdb, please install with `pip install duckdb`.")fromeexceptExceptionase:self.query._result_table=str(e)else:msg="LLM maybe failed to translate question to SQL query."raiseValueError({"question":self.query.question,"llm_error_msg":self.query.llm_error_msg,"msg":msg,})def_compute(self)->Any:self._block_back_door_paths()self._set_initial_conditions()self._make_graph()self._sort_entities()self._forward_propagate()self._run_query()