CPAL (#6255)
# Causal program-aided language (CPAL) chain
## Motivation
This builds on the recent [PAL](https://arxiv.org/abs/2211.10435) to
stop LLM hallucination. The problem with the
[PAL](https://arxiv.org/abs/2211.10435) approach is that it hallucinates
on a math problem with a nested chain of dependence. The innovation here
is that this new CPAL approach includes causal structure to fix
hallucination.
For example, using the below word problem, PAL answers with 5, and CPAL
answers with 13.
"Tim buys the same number of pets as Cindy and Boris."
"Cindy buys the same number of pets as Bill plus Bob."
"Boris buys the same number of pets as Ben plus Beth."
"Bill buys the same number of pets as Obama."
"Bob buys the same number of pets as Obama."
"Ben buys the same number of pets as Obama."
"Beth buys the same number of pets as Obama."
"If Obama buys one pet, how many pets total does everyone buy?"
The CPAL chain represents the causal structure of the above narrative as
a causal graph or DAG, which it can also plot, as shown below.
![complex-graph](https://github.com/hwchase17/langchain/assets/367522/d938db15-f941-493d-8605-536ad530f576)
.
The two major sections below are:
1. Technical overview
2. Future application
Also see [this jupyter
notebook](https://github.com/borisdev/langchain/blob/master/docs/extras/modules/chains/additional/cpal.ipynb)
doc.
## 1. Technical overview
### CPAL versus PAL
Like [PAL](https://arxiv.org/abs/2211.10435), CPAL intends to reduce
large language model (LLM) hallucination.
The CPAL chain is different from the PAL chain for a couple of reasons.
* CPAL adds a causal structure (or DAG) to link entity actions (or math
expressions).
* The CPAL math expressions are modeling a chain of cause and effect
relations, which can be intervened upon, whereas for the PAL chain math
expressions are projected math identities.
PAL's generated python code is wrong. It hallucinates when complexity
increases.
```python
def solution():
"""Tim buys the same number of pets as Cindy and Boris.Cindy buys the same number of pets as Bill plus Bob.Boris buys the same number of pets as Ben plus Beth.Bill buys the same number of pets as Obama.Bob buys the same number of pets as Obama.Ben buys the same number of pets as Obama.Beth buys the same number of pets as Obama.If Obama buys one pet, how many pets total does everyone buy?"""
obama_pets = 1
tim_pets = obama_pets
cindy_pets = obama_pets + obama_pets
boris_pets = obama_pets + obama_pets
total_pets = tim_pets + cindy_pets + boris_pets
result = total_pets
return result # math result is 5
```
CPAL's generated python code is correct.
```python
story outcome data
name code value depends_on
0 obama pass 1.0 []
1 bill bill.value = obama.value 1.0 [obama]
2 bob bob.value = obama.value 1.0 [obama]
3 ben ben.value = obama.value 1.0 [obama]
4 beth beth.value = obama.value 1.0 [obama]
5 cindy cindy.value = bill.value + bob.value 2.0 [bill, bob]
6 boris boris.value = ben.value + beth.value 2.0 [ben, beth]
7 tim tim.value = cindy.value + boris.value 4.0 [cindy, boris]
query data
{
"question": "how many pets total does everyone buy?",
"expression": "SELECT SUM(value) FROM df",
"llm_error_msg": ""
}
# query result is 13
```
Based on the comments below, CPAL's intended location in the library is
`experimental/chains/cpal` and PAL's location is`chains/pal`.
### CPAL vs Graph QA
Both the CPAL chain and the Graph QA chain extract entity-action-entity
relations into a DAG.
The CPAL chain is different from the Graph QA chain for a few reasons.
* Graph QA does not connect entities to math expressions
* Graph QA does not associate actions in a sequence of dependence.
* Graph QA does not decompose the narrative into these three parts:
1. Story plot or causal model
4. Hypothetical question
5. Hypothetical condition
### Evaluation
Preliminary evaluation on simple math word problems shows that this CPAL
chain generates less hallucination than the PAL chain on answering
questions about a causal narrative. Two examples are in [this jupyter
notebook](https://github.com/borisdev/langchain/blob/master/docs/extras/modules/chains/additional/cpal.ipynb)
doc.
## 2. Future application
### "Describe as Narrative, Test as Code"
The thesis here is that the Describe as Narrative, Test as Code approach
allows you to represent a causal mental model both as code and as a
narrative, giving you the best of both worlds.
#### Why describe a causal mental mode as a narrative?
The narrative form is quick. At a consensus building meeting, people use
narratives to persuade others of their causal mental model, aka. plan.
You can share, version control and index a narrative.
#### Why test a causal mental model as a code?
Code is testable, complex narratives are not. Though fast, narratives
are problematic as their complexity increases. The problem is LLMs and
humans are prone to hallucination when predicting the outcomes of a
narrative. The cost of building a consensus around the validity of a
narrative outcome grows as its narrative complexity increases. Code does
not require tribal knowledge or social power to validate.
Code is composable, complex narratives are not. The answer of one CPAL
chain can be the hypothetical conditions of another CPAL Chain. For
stochastic simulations, a composable plan can be integrated with the
[DoWhy library](https://github.com/py-why/dowhy). Lastly, for the
futuristic folk, a composable plan as code allows ordinary community
folk to design a plan that can be integrated with a blockchain for
funding.
An explanation of a dependency planning application is
[here.](https://github.com/borisdev/cpal-llm-chain-demo)
---
Twitter handle: @boris_dev
---------
Co-authored-by: Boris Dev <borisdev@Boriss-MacBook-Air.local>
2023-07-11 14:11:21 +00:00
|
|
|
from __future__ import annotations # allows pydantic model to reference itself
|
|
|
|
|
|
|
|
import re
|
|
|
|
from typing import Any, Optional, Union
|
|
|
|
|
|
|
|
import duckdb
|
|
|
|
import pandas as pd
|
2023-07-22 01:44:32 +00:00
|
|
|
from langchain.graphs.networkx_graph import NetworkxEntityGraph
|
2023-07-21 20:32:39 +00:00
|
|
|
|
2023-07-22 01:44:32 +00:00
|
|
|
from langchain_experimental.cpal.constants import Constant
|
Use a submodule for pydantic v1 compat (#9371)
<!-- Thank you for contributing to LangChain!
Replace this entire comment with:
- Description: a description of the change,
- Issue: the issue # it fixes (if applicable),
- Dependencies: any dependencies required for this change,
- Tag maintainer: for a quicker response, tag the relevant maintainer
(see below),
- Twitter handle: we announce bigger features on Twitter. If your PR
gets announced and you'd like a mention, we'll gladly shout you out!
Please make sure your PR is passing linting and testing before
submitting. Run `make format`, `make lint` and `make test` to check this
locally.
See contribution guidelines for more information on how to write/run
tests, lint, etc:
https://github.com/hwchase17/langchain/blob/master/.github/CONTRIBUTING.md
If you're adding a new integration, please include:
1. a test for the integration, preferably unit tests that do not rely on
network access,
2. an example notebook showing its use. These live is docs/extras
directory.
If no one reviews your PR within a few days, please @-mention one of
@baskaryan, @eyurtsev, @hwchase17, @rlancemartin.
-->
2023-08-17 15:35:49 +00:00
|
|
|
from langchain_experimental.pydantic_v1 import (
|
|
|
|
BaseModel,
|
|
|
|
Field,
|
|
|
|
PrivateAttr,
|
|
|
|
root_validator,
|
|
|
|
validator,
|
|
|
|
)
|
CPAL (#6255)
# Causal program-aided language (CPAL) chain
## Motivation
This builds on the recent [PAL](https://arxiv.org/abs/2211.10435) to
stop LLM hallucination. The problem with the
[PAL](https://arxiv.org/abs/2211.10435) approach is that it hallucinates
on a math problem with a nested chain of dependence. The innovation here
is that this new CPAL approach includes causal structure to fix
hallucination.
For example, using the below word problem, PAL answers with 5, and CPAL
answers with 13.
"Tim buys the same number of pets as Cindy and Boris."
"Cindy buys the same number of pets as Bill plus Bob."
"Boris buys the same number of pets as Ben plus Beth."
"Bill buys the same number of pets as Obama."
"Bob buys the same number of pets as Obama."
"Ben buys the same number of pets as Obama."
"Beth buys the same number of pets as Obama."
"If Obama buys one pet, how many pets total does everyone buy?"
The CPAL chain represents the causal structure of the above narrative as
a causal graph or DAG, which it can also plot, as shown below.
![complex-graph](https://github.com/hwchase17/langchain/assets/367522/d938db15-f941-493d-8605-536ad530f576)
.
The two major sections below are:
1. Technical overview
2. Future application
Also see [this jupyter
notebook](https://github.com/borisdev/langchain/blob/master/docs/extras/modules/chains/additional/cpal.ipynb)
doc.
## 1. Technical overview
### CPAL versus PAL
Like [PAL](https://arxiv.org/abs/2211.10435), CPAL intends to reduce
large language model (LLM) hallucination.
The CPAL chain is different from the PAL chain for a couple of reasons.
* CPAL adds a causal structure (or DAG) to link entity actions (or math
expressions).
* The CPAL math expressions are modeling a chain of cause and effect
relations, which can be intervened upon, whereas for the PAL chain math
expressions are projected math identities.
PAL's generated python code is wrong. It hallucinates when complexity
increases.
```python
def solution():
"""Tim buys the same number of pets as Cindy and Boris.Cindy buys the same number of pets as Bill plus Bob.Boris buys the same number of pets as Ben plus Beth.Bill buys the same number of pets as Obama.Bob buys the same number of pets as Obama.Ben buys the same number of pets as Obama.Beth buys the same number of pets as Obama.If Obama buys one pet, how many pets total does everyone buy?"""
obama_pets = 1
tim_pets = obama_pets
cindy_pets = obama_pets + obama_pets
boris_pets = obama_pets + obama_pets
total_pets = tim_pets + cindy_pets + boris_pets
result = total_pets
return result # math result is 5
```
CPAL's generated python code is correct.
```python
story outcome data
name code value depends_on
0 obama pass 1.0 []
1 bill bill.value = obama.value 1.0 [obama]
2 bob bob.value = obama.value 1.0 [obama]
3 ben ben.value = obama.value 1.0 [obama]
4 beth beth.value = obama.value 1.0 [obama]
5 cindy cindy.value = bill.value + bob.value 2.0 [bill, bob]
6 boris boris.value = ben.value + beth.value 2.0 [ben, beth]
7 tim tim.value = cindy.value + boris.value 4.0 [cindy, boris]
query data
{
"question": "how many pets total does everyone buy?",
"expression": "SELECT SUM(value) FROM df",
"llm_error_msg": ""
}
# query result is 13
```
Based on the comments below, CPAL's intended location in the library is
`experimental/chains/cpal` and PAL's location is`chains/pal`.
### CPAL vs Graph QA
Both the CPAL chain and the Graph QA chain extract entity-action-entity
relations into a DAG.
The CPAL chain is different from the Graph QA chain for a few reasons.
* Graph QA does not connect entities to math expressions
* Graph QA does not associate actions in a sequence of dependence.
* Graph QA does not decompose the narrative into these three parts:
1. Story plot or causal model
4. Hypothetical question
5. Hypothetical condition
### Evaluation
Preliminary evaluation on simple math word problems shows that this CPAL
chain generates less hallucination than the PAL chain on answering
questions about a causal narrative. Two examples are in [this jupyter
notebook](https://github.com/borisdev/langchain/blob/master/docs/extras/modules/chains/additional/cpal.ipynb)
doc.
## 2. Future application
### "Describe as Narrative, Test as Code"
The thesis here is that the Describe as Narrative, Test as Code approach
allows you to represent a causal mental model both as code and as a
narrative, giving you the best of both worlds.
#### Why describe a causal mental mode as a narrative?
The narrative form is quick. At a consensus building meeting, people use
narratives to persuade others of their causal mental model, aka. plan.
You can share, version control and index a narrative.
#### Why test a causal mental model as a code?
Code is testable, complex narratives are not. Though fast, narratives
are problematic as their complexity increases. The problem is LLMs and
humans are prone to hallucination when predicting the outcomes of a
narrative. The cost of building a consensus around the validity of a
narrative outcome grows as its narrative complexity increases. Code does
not require tribal knowledge or social power to validate.
Code is composable, complex narratives are not. The answer of one CPAL
chain can be the hypothetical conditions of another CPAL Chain. For
stochastic simulations, a composable plan can be integrated with the
[DoWhy library](https://github.com/py-why/dowhy). Lastly, for the
futuristic folk, a composable plan as code allows ordinary community
folk to design a plan that can be integrated with a blockchain for
funding.
An explanation of a dependency planning application is
[here.](https://github.com/borisdev/cpal-llm-chain-demo)
---
Twitter handle: @boris_dev
---------
Co-authored-by: Boris Dev <borisdev@Boriss-MacBook-Air.local>
2023-07-11 14:11:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
class NarrativeModel(BaseModel):
|
|
|
|
"""
|
|
|
|
Represent the narrative input as three story elements.
|
|
|
|
"""
|
|
|
|
|
|
|
|
story_outcome_question: str
|
|
|
|
story_hypothetical: str
|
|
|
|
story_plot: str # causal stack of operations
|
|
|
|
|
|
|
|
@validator("*", pre=True)
|
|
|
|
def empty_str_to_none(cls, v: str) -> Union[str, None]:
|
|
|
|
"""Empty strings are not allowed"""
|
|
|
|
if v == "":
|
|
|
|
return None
|
|
|
|
return v
|
|
|
|
|
|
|
|
|
|
|
|
class EntityModel(BaseModel):
|
|
|
|
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 graph
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
validate_assignment = True
|
|
|
|
|
|
|
|
@validator("name")
|
|
|
|
def lower_case_name(cls, v: str) -> str:
|
|
|
|
v = v.lower()
|
|
|
|
return v
|
|
|
|
|
|
|
|
|
|
|
|
class CausalModel(BaseModel):
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
class EntitySettingModel(BaseModel):
|
|
|
|
"""
|
|
|
|
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)")
|
|
|
|
|
|
|
|
@validator("name")
|
|
|
|
def lower_case_transform(cls, v: str) -> str:
|
|
|
|
v = v.lower()
|
|
|
|
return v
|
|
|
|
|
|
|
|
|
|
|
|
class SystemSettingModel(BaseModel):
|
|
|
|
"""
|
|
|
|
Initial global conditions for the system.
|
|
|
|
|
|
|
|
{"parameter": "interest_rate", "value": .05}
|
|
|
|
"""
|
|
|
|
|
|
|
|
parameter: str
|
|
|
|
value: float
|
|
|
|
|
|
|
|
|
|
|
|
class InterventionModel(BaseModel):
|
|
|
|
"""
|
|
|
|
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
|
|
|
|
|
|
|
|
@validator("system_settings")
|
|
|
|
def lower_case_name(cls, v: str) -> Union[str, None]:
|
|
|
|
if v is not None:
|
|
|
|
raise NotImplementedError("system_setting is not implemented yet")
|
|
|
|
return v
|
|
|
|
|
|
|
|
|
|
|
|
class QueryModel(BaseModel):
|
2023-07-12 20:20:08 +00:00
|
|
|
"""translate a question about the story outcome into a programmatic expression"""
|
CPAL (#6255)
# Causal program-aided language (CPAL) chain
## Motivation
This builds on the recent [PAL](https://arxiv.org/abs/2211.10435) to
stop LLM hallucination. The problem with the
[PAL](https://arxiv.org/abs/2211.10435) approach is that it hallucinates
on a math problem with a nested chain of dependence. The innovation here
is that this new CPAL approach includes causal structure to fix
hallucination.
For example, using the below word problem, PAL answers with 5, and CPAL
answers with 13.
"Tim buys the same number of pets as Cindy and Boris."
"Cindy buys the same number of pets as Bill plus Bob."
"Boris buys the same number of pets as Ben plus Beth."
"Bill buys the same number of pets as Obama."
"Bob buys the same number of pets as Obama."
"Ben buys the same number of pets as Obama."
"Beth buys the same number of pets as Obama."
"If Obama buys one pet, how many pets total does everyone buy?"
The CPAL chain represents the causal structure of the above narrative as
a causal graph or DAG, which it can also plot, as shown below.
![complex-graph](https://github.com/hwchase17/langchain/assets/367522/d938db15-f941-493d-8605-536ad530f576)
.
The two major sections below are:
1. Technical overview
2. Future application
Also see [this jupyter
notebook](https://github.com/borisdev/langchain/blob/master/docs/extras/modules/chains/additional/cpal.ipynb)
doc.
## 1. Technical overview
### CPAL versus PAL
Like [PAL](https://arxiv.org/abs/2211.10435), CPAL intends to reduce
large language model (LLM) hallucination.
The CPAL chain is different from the PAL chain for a couple of reasons.
* CPAL adds a causal structure (or DAG) to link entity actions (or math
expressions).
* The CPAL math expressions are modeling a chain of cause and effect
relations, which can be intervened upon, whereas for the PAL chain math
expressions are projected math identities.
PAL's generated python code is wrong. It hallucinates when complexity
increases.
```python
def solution():
"""Tim buys the same number of pets as Cindy and Boris.Cindy buys the same number of pets as Bill plus Bob.Boris buys the same number of pets as Ben plus Beth.Bill buys the same number of pets as Obama.Bob buys the same number of pets as Obama.Ben buys the same number of pets as Obama.Beth buys the same number of pets as Obama.If Obama buys one pet, how many pets total does everyone buy?"""
obama_pets = 1
tim_pets = obama_pets
cindy_pets = obama_pets + obama_pets
boris_pets = obama_pets + obama_pets
total_pets = tim_pets + cindy_pets + boris_pets
result = total_pets
return result # math result is 5
```
CPAL's generated python code is correct.
```python
story outcome data
name code value depends_on
0 obama pass 1.0 []
1 bill bill.value = obama.value 1.0 [obama]
2 bob bob.value = obama.value 1.0 [obama]
3 ben ben.value = obama.value 1.0 [obama]
4 beth beth.value = obama.value 1.0 [obama]
5 cindy cindy.value = bill.value + bob.value 2.0 [bill, bob]
6 boris boris.value = ben.value + beth.value 2.0 [ben, beth]
7 tim tim.value = cindy.value + boris.value 4.0 [cindy, boris]
query data
{
"question": "how many pets total does everyone buy?",
"expression": "SELECT SUM(value) FROM df",
"llm_error_msg": ""
}
# query result is 13
```
Based on the comments below, CPAL's intended location in the library is
`experimental/chains/cpal` and PAL's location is`chains/pal`.
### CPAL vs Graph QA
Both the CPAL chain and the Graph QA chain extract entity-action-entity
relations into a DAG.
The CPAL chain is different from the Graph QA chain for a few reasons.
* Graph QA does not connect entities to math expressions
* Graph QA does not associate actions in a sequence of dependence.
* Graph QA does not decompose the narrative into these three parts:
1. Story plot or causal model
4. Hypothetical question
5. Hypothetical condition
### Evaluation
Preliminary evaluation on simple math word problems shows that this CPAL
chain generates less hallucination than the PAL chain on answering
questions about a causal narrative. Two examples are in [this jupyter
notebook](https://github.com/borisdev/langchain/blob/master/docs/extras/modules/chains/additional/cpal.ipynb)
doc.
## 2. Future application
### "Describe as Narrative, Test as Code"
The thesis here is that the Describe as Narrative, Test as Code approach
allows you to represent a causal mental model both as code and as a
narrative, giving you the best of both worlds.
#### Why describe a causal mental mode as a narrative?
The narrative form is quick. At a consensus building meeting, people use
narratives to persuade others of their causal mental model, aka. plan.
You can share, version control and index a narrative.
#### Why test a causal mental model as a code?
Code is testable, complex narratives are not. Though fast, narratives
are problematic as their complexity increases. The problem is LLMs and
humans are prone to hallucination when predicting the outcomes of a
narrative. The cost of building a consensus around the validity of a
narrative outcome grows as its narrative complexity increases. Code does
not require tribal knowledge or social power to validate.
Code is composable, complex narratives are not. The answer of one CPAL
chain can be the hypothetical conditions of another CPAL Chain. For
stochastic simulations, a composable plan can be integrated with the
[DoWhy library](https://github.com/py-why/dowhy). Lastly, for the
futuristic folk, a composable plan as code allows ordinary community
folk to design a plan that can be integrated with a blockchain for
funding.
An explanation of a dependency planning application is
[here.](https://github.com/borisdev/cpal-llm-chain-demo)
---
Twitter handle: @boris_dev
---------
Co-authored-by: Boris Dev <borisdev@Boriss-MacBook-Air.local>
2023-07-11 14:11:21 +00:00
|
|
|
|
|
|
|
question: str = Field(alias=Constant.narrative_input.value) # input
|
|
|
|
expression: str # output, part of llm completion
|
|
|
|
llm_error_msg: str # output, part of llm completion
|
|
|
|
_result_table: str = PrivateAttr() # result of the executed query
|
|
|
|
|
|
|
|
|
|
|
|
class ResultModel(BaseModel):
|
|
|
|
question: str = Field(alias=Constant.narrative_input.value) # input
|
|
|
|
_result_table: str = PrivateAttr() # result of the executed query
|
|
|
|
|
|
|
|
|
|
|
|
class StoryModel(BaseModel):
|
|
|
|
causal_operations: Any = Field(required=True)
|
|
|
|
intervention: Any = Field(required=True)
|
|
|
|
query: Any = Field(required=True)
|
|
|
|
_outcome_table: pd.DataFrame = 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
|
|
|
|
|
|
|
|
@root_validator
|
|
|
|
def check_intervention_is_valid(cls, values: dict) -> dict:
|
|
|
|
valid_names = [e.name for e in values["causal_operations"].entities]
|
|
|
|
for setting in values["intervention"].entity_settings:
|
|
|
|
if setting.name not in valid_names:
|
|
|
|
error_msg = f"""
|
|
|
|
Hypothetical question has an invalid entity name.
|
|
|
|
`{setting.name}` not in `{valid_names}`
|
|
|
|
"""
|
|
|
|
raise ValueError(error_msg)
|
|
|
|
return values
|
|
|
|
|
|
|
|
def _block_back_door_paths(self) -> None:
|
|
|
|
# stop intervention entities from depending on others
|
|
|
|
intervention_entities = [
|
|
|
|
entity_setting.name for entity_setting in self.intervention.entity_settings
|
|
|
|
]
|
|
|
|
for entity in self.causal_operations.entities:
|
|
|
|
if entity.name in intervention_entities:
|
|
|
|
entity.depends_on = []
|
|
|
|
entity.code = "pass"
|
|
|
|
|
|
|
|
def _set_initial_conditions(self) -> None:
|
|
|
|
for entity_setting in self.intervention.entity_settings:
|
|
|
|
for entity in self.causal_operations.entities:
|
|
|
|
if entity.name == entity_setting.name:
|
|
|
|
entity.value = entity_setting.value
|
|
|
|
|
|
|
|
def _make_graph(self) -> None:
|
|
|
|
self._networkx_wrapper = NetworkxEntityGraph()
|
|
|
|
for entity in self.causal_operations.entities:
|
|
|
|
for parent_name in entity.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 = [
|
|
|
|
entity
|
|
|
|
for entity in self.causal_operations.entities
|
|
|
|
if entity.name in self._networkx_wrapper.get_topological_sort()
|
|
|
|
]
|
|
|
|
|
|
|
|
def _sort_entities(self) -> None:
|
|
|
|
# order the sequence of causal actions
|
|
|
|
sorted_nodes = self._networkx_wrapper.get_topological_sort()
|
|
|
|
self.causal_operations.entities.sort(key=lambda x: sorted_nodes.index(x.name))
|
|
|
|
|
|
|
|
def _forward_propagate(self) -> None:
|
|
|
|
entity_scope = {
|
|
|
|
entity.name: entity for entity in self.causal_operations.entities
|
|
|
|
}
|
|
|
|
for entity in self.causal_operations.entities:
|
|
|
|
if entity.code == "pass":
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
# gist.github.com/dean0x7d/df5ce97e4a1a05be4d56d1378726ff92
|
|
|
|
exec(entity.code, globals(), entity_scope)
|
|
|
|
row_values = [entity.dict() for entity in entity_scope.values()]
|
|
|
|
self._outcome_table = pd.DataFrame(row_values)
|
|
|
|
|
|
|
|
def _run_query(self) -> None:
|
|
|
|
def humanize_sql_error_msg(error: str) -> str:
|
|
|
|
pattern = r"column\s+(.*?)\s+not found"
|
|
|
|
col_match = re.search(pattern, error)
|
|
|
|
if col_match:
|
|
|
|
return (
|
|
|
|
"SQL error: "
|
|
|
|
+ col_match.group(1)
|
|
|
|
+ " is not an attribute in your story!"
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
return str(error)
|
|
|
|
|
|
|
|
if self.query.llm_error_msg == "":
|
|
|
|
try:
|
|
|
|
df = self._outcome_table # noqa
|
|
|
|
query_result = duckdb.sql(self.query.expression).df()
|
|
|
|
self.query._result_table = query_result
|
|
|
|
except duckdb.BinderException as e:
|
|
|
|
self.query._result_table = humanize_sql_error_msg(str(e))
|
|
|
|
except Exception as e:
|
|
|
|
self.query._result_table = str(e)
|
|
|
|
else:
|
|
|
|
msg = "LLM maybe failed to translate question to SQL query."
|
|
|
|
raise ValueError(
|
|
|
|
{
|
|
|
|
"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()
|
|
|
|
|
|
|
|
def print_debug_report(self) -> None:
|
|
|
|
report = {
|
|
|
|
"outcome": self._outcome_table,
|
|
|
|
"query": self.query.dict(),
|
|
|
|
"result": self.query._result_table,
|
|
|
|
}
|
|
|
|
from pprint import pprint
|
|
|
|
|
|
|
|
pprint(report)
|