aiagents4pharma 1.10.0__py3-none-any.whl → 1.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aiagents4pharma/talk2biomodels/agents/t2b_agent.py +7 -10
- aiagents4pharma/talk2biomodels/models/basico_model.py +29 -32
- aiagents4pharma/talk2biomodels/models/sys_bio_model.py +9 -6
- aiagents4pharma/talk2biomodels/states/state_talk2biomodels.py +3 -3
- aiagents4pharma/talk2biomodels/tests/test_basico_model.py +7 -8
- aiagents4pharma/talk2biomodels/tests/test_langgraph.py +64 -2
- aiagents4pharma/talk2biomodels/tests/test_sys_bio_model.py +13 -7
- aiagents4pharma/talk2biomodels/tools/__init__.py +1 -0
- aiagents4pharma/talk2biomodels/tools/get_modelinfo.py +5 -3
- aiagents4pharma/talk2biomodels/tools/parameter_scan.py +292 -0
- aiagents4pharma/talk2biomodels/tools/simulate_model.py +9 -11
- aiagents4pharma/talk2knowledgegraphs/__init__.py +2 -1
- aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_enrichments.py +39 -0
- aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_ollama.py +117 -0
- aiagents4pharma/talk2knowledgegraphs/utils/__init__.py +5 -0
- aiagents4pharma/talk2knowledgegraphs/utils/enrichments/__init__.py +5 -0
- aiagents4pharma/talk2knowledgegraphs/utils/enrichments/enrichments.py +36 -0
- aiagents4pharma/talk2knowledgegraphs/utils/enrichments/ollama.py +123 -0
- {aiagents4pharma-1.10.0.dist-info → aiagents4pharma-1.12.0.dist-info}/METADATA +5 -6
- {aiagents4pharma-1.10.0.dist-info → aiagents4pharma-1.12.0.dist-info}/RECORD +23 -17
- {aiagents4pharma-1.10.0.dist-info → aiagents4pharma-1.12.0.dist-info}/LICENSE +0 -0
- {aiagents4pharma-1.10.0.dist-info → aiagents4pharma-1.12.0.dist-info}/WHEEL +0 -0
- {aiagents4pharma-1.10.0.dist-info → aiagents4pharma-1.12.0.dist-info}/top_level.txt +0 -0
@@ -16,6 +16,7 @@ from ..tools.get_modelinfo import GetModelInfoTool
|
|
16
16
|
from ..tools.simulate_model import SimulateModelTool
|
17
17
|
from ..tools.custom_plotter import CustomPlotterTool
|
18
18
|
from ..tools.ask_question import AskQuestionTool
|
19
|
+
from ..tools.parameter_scan import ParameterScanTool
|
19
20
|
from ..states.state_talk2biomodels import Talk2Biomodels
|
20
21
|
|
21
22
|
# Initialize logger
|
@@ -35,17 +36,13 @@ def get_app(uniq_id, llm_model='gpt-4o-mini'):
|
|
35
36
|
return response
|
36
37
|
|
37
38
|
# Define the tools
|
38
|
-
simulate_model = SimulateModelTool()
|
39
|
-
custom_plotter = CustomPlotterTool()
|
40
|
-
ask_question = AskQuestionTool()
|
41
|
-
search_model = SearchModelsTool()
|
42
|
-
get_modelinfo = GetModelInfoTool()
|
43
39
|
tools = ToolNode([
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
40
|
+
SimulateModelTool(),
|
41
|
+
AskQuestionTool(),
|
42
|
+
CustomPlotterTool(),
|
43
|
+
SearchModelsTool(),
|
44
|
+
GetModelInfoTool(),
|
45
|
+
ParameterScanTool()
|
49
46
|
])
|
50
47
|
|
51
48
|
# Define the model
|
@@ -48,52 +48,49 @@ class BasicoModel(SysBioModel):
|
|
48
48
|
self.name = basico.model_info.get_model_name(model=self.copasi_model)
|
49
49
|
return self
|
50
50
|
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
51
|
+
def update_parameters(self, parameters: Dict[str, Union[float, int]]) -> None:
|
52
|
+
"""
|
53
|
+
Update model parameters with new values.
|
54
|
+
"""
|
55
|
+
# Update parameters in the model
|
56
|
+
for param_name, param_value in parameters.items():
|
57
|
+
# check if the param_name is not None
|
58
|
+
if param_name is None:
|
59
|
+
continue
|
60
|
+
# if param is a kinetic parameter
|
61
|
+
df_all_params = basico.model_info.get_parameters(model=self.copasi_model)
|
62
|
+
if param_name in df_all_params.index.tolist():
|
63
|
+
basico.model_info.set_parameters(name=param_name,
|
64
|
+
exact=True,
|
65
|
+
initial_value=param_value,
|
66
|
+
model=self.copasi_model)
|
67
|
+
# if param is a species
|
68
|
+
else:
|
69
|
+
basico.model_info.set_species(name=param_name,
|
70
|
+
exact=True,
|
71
|
+
initial_concentration=param_value,
|
72
|
+
model=self.copasi_model)
|
73
|
+
|
74
|
+
def simulate(self, duration: Union[int, float] = 10, interval: int = 10) -> pd.DataFrame:
|
56
75
|
"""
|
57
76
|
Simulate the COPASI model over a specified range of time points.
|
58
77
|
|
59
78
|
Args:
|
60
|
-
parameters: Dictionary of model parameters to update before simulation.
|
61
79
|
duration: Duration of the simulation in time units.
|
62
80
|
interval: Interval between time points in the simulation.
|
63
81
|
|
64
82
|
Returns:
|
65
83
|
Pandas DataFrame with time-course simulation results.
|
66
84
|
"""
|
67
|
-
|
68
|
-
# Update parameters in the model
|
69
|
-
if parameters:
|
70
|
-
for param_name, param_value in parameters.items():
|
71
|
-
# check if the param_name is not None
|
72
|
-
if param_name is None:
|
73
|
-
continue
|
74
|
-
# if param is a kinectic parameter
|
75
|
-
df_all_params = basico.model_info.get_parameters(model=self.copasi_model)
|
76
|
-
if param_name in df_all_params.index.tolist():
|
77
|
-
basico.model_info.set_parameters(name=param_name,
|
78
|
-
exact=True,
|
79
|
-
initial_value=param_value,
|
80
|
-
model=self.copasi_model)
|
81
|
-
# if param is a species
|
82
|
-
else:
|
83
|
-
basico.model_info.set_species(name=param_name,
|
84
|
-
exact=True,
|
85
|
-
initial_concentration=param_value,
|
86
|
-
model=self.copasi_model)
|
87
|
-
|
88
85
|
# Run the simulation and return results
|
89
86
|
df_result = basico.run_time_course(model=self.copasi_model,
|
90
87
|
intervals=interval,
|
91
88
|
duration=duration)
|
92
|
-
# Replace curly braces in column headers with square brackets
|
93
|
-
# Because curly braces in the world of LLMS are used for
|
94
|
-
# structured output
|
95
|
-
df_result.columns = df_result.columns.str.replace('{', '[', regex=False).\
|
96
|
-
|
89
|
+
# # Replace curly braces in column headers with square brackets
|
90
|
+
# # Because curly braces in the world of LLMS are used for
|
91
|
+
# # structured output
|
92
|
+
# df_result.columns = df_result.columns.str.replace('{', '[', regex=False).\
|
93
|
+
# str.replace('}', ']', regex=False)
|
97
94
|
# Reset the index
|
98
95
|
df_result.reset_index(inplace=True)
|
99
96
|
# Store the simulation results
|
@@ -35,18 +35,21 @@ class SysBioModel(ABC, BaseModel):
|
|
35
35
|
Returns:
|
36
36
|
dict: Dictionary with model metadata
|
37
37
|
"""
|
38
|
+
@abstractmethod
|
39
|
+
def update_parameters(self, parameters: Dict[str, Union[float, int]]) -> None:
|
40
|
+
"""
|
41
|
+
Abstract method to update model parameters.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
parameters: Dictionary of parameter values.
|
45
|
+
"""
|
38
46
|
|
39
47
|
@abstractmethod
|
40
|
-
def simulate(self,
|
41
|
-
parameters: Dict[str, Union[float, int]],
|
42
|
-
duration: Union[int, float]) -> List[float]:
|
48
|
+
def simulate(self, duration: Union[int, float]) -> List[float]:
|
43
49
|
"""
|
44
50
|
Abstract method to run a simulation of the model.
|
45
|
-
This method should be implemented to simulate model
|
46
|
-
behavior based on the provided parameters.
|
47
51
|
|
48
52
|
Args:
|
49
|
-
parameters: Dictionary of parameter values.
|
50
53
|
duration: Duration of the simulation.
|
51
54
|
|
52
55
|
Returns:
|
@@ -12,13 +12,13 @@ class Talk2Biomodels(AgentState):
|
|
12
12
|
"""
|
13
13
|
The state for the Talk2BioModels agent.
|
14
14
|
"""
|
15
|
-
|
16
|
-
# sbml_file_path: str
|
15
|
+
llm_model: str
|
17
16
|
# A StateGraph may receive a concurrent updates
|
18
17
|
# which is not supported by the StateGraph.
|
19
18
|
# Therefore, we need to use Annotated to specify
|
20
19
|
# the operator for the sbml_file_path field.
|
21
20
|
# https://langchain-ai.github.io/langgraph/troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE/
|
21
|
+
model_id: Annotated[list, operator.add]
|
22
22
|
sbml_file_path: Annotated[list, operator.add]
|
23
23
|
dic_simulated_data: Annotated[list[dict], operator.add]
|
24
|
-
|
24
|
+
dic_scanned_data: Annotated[list[dict], operator.add]
|
@@ -19,13 +19,14 @@ def test_with_biomodel_id(model):
|
|
19
19
|
Test initialization of BasicoModel with biomodel_id.
|
20
20
|
"""
|
21
21
|
assert model.biomodel_id == 64
|
22
|
+
model.update_parameters(parameters={'Pyruvate': 0.5, 'KmPFKF6P': 1.5})
|
23
|
+
df_species = basico.model_info.get_species(model=model.copasi_model)
|
24
|
+
assert df_species.loc['Pyruvate', 'initial_concentration'] == 0.5
|
25
|
+
df_parameters = basico.model_info.get_parameters(model=model.copasi_model)
|
26
|
+
assert df_parameters.loc['KmPFKF6P', 'initial_value'] == 1.5
|
22
27
|
# check if the simulation results are a pandas DataFrame object
|
23
|
-
assert isinstance(model.simulate(
|
24
|
-
|
25
|
-
interval=2),
|
26
|
-
pd.DataFrame)
|
27
|
-
assert isinstance(model.simulate(parameters={None: None}, duration=2, interval=2),
|
28
|
-
pd.DataFrame)
|
28
|
+
assert isinstance(model.simulate(duration=2, interval=2), pd.DataFrame)
|
29
|
+
model.update_parameters(parameters={None: None})
|
29
30
|
assert model.description == basico.biomodels.get_model_info(model.biomodel_id)["description"]
|
30
31
|
|
31
32
|
def test_with_sbml_file():
|
@@ -35,8 +36,6 @@ def test_with_sbml_file():
|
|
35
36
|
model_object = BasicoModel(sbml_file_path="./BIOMD0000000064_url.xml")
|
36
37
|
assert model_object.sbml_file_path == "./BIOMD0000000064_url.xml"
|
37
38
|
assert isinstance(model_object.simulate(duration=2, interval=2), pd.DataFrame)
|
38
|
-
assert isinstance(model_object.simulate(parameters={'NADH': 0.5}, duration=2, interval=2),
|
39
|
-
pd.DataFrame)
|
40
39
|
|
41
40
|
def test_check_biomodel_id_or_sbml_file_path():
|
42
41
|
'''
|
@@ -119,6 +119,68 @@ def test_simulate_model_tool():
|
|
119
119
|
# Check if the data of the second model contains
|
120
120
|
assert 'mTORC2' in dic_simulated_data[1]['data']
|
121
121
|
|
122
|
+
def test_param_scan_tool():
|
123
|
+
'''
|
124
|
+
In this test, we will test the parameter_scan tool.
|
125
|
+
We will prompt it to scan the parameter `kIL6RBind`
|
126
|
+
from 1 to 100 in steps of 10, record the changes
|
127
|
+
in the concentration of the species `Ab{serum}` in
|
128
|
+
model 537.
|
129
|
+
|
130
|
+
We will pass the inaccuarate parameter (`KIL6Rbind`)
|
131
|
+
and species names (just `Ab`) to the tool to test
|
132
|
+
if it can deal with it.
|
133
|
+
|
134
|
+
We expect the agent to first invoke the parameter_scan
|
135
|
+
tool and raise an error. It will then invoke another
|
136
|
+
tool get_modelinfo to get the correct parameter
|
137
|
+
and species names. Finally, the agent will reinvoke
|
138
|
+
the parameter_scan tool with the correct parameter
|
139
|
+
and species names.
|
140
|
+
|
141
|
+
'''
|
142
|
+
unique_id = 123
|
143
|
+
app = get_app(unique_id)
|
144
|
+
config = {"configurable": {"thread_id": unique_id}}
|
145
|
+
app.update_state(config, {"llm_model": "gpt-4o-mini"})
|
146
|
+
prompt = """How will the value of Ab in model 537 change
|
147
|
+
if the param kIL6Rbind is varied from 1 to 100 in steps of 10?
|
148
|
+
Set the initial `DoseQ2W` concentration to 300.
|
149
|
+
Reset the IL6{serum} concentration to 100 every 500 hours.
|
150
|
+
Assume that the model is simulated for 2016 hours with
|
151
|
+
an interval of 2016."""
|
152
|
+
# Invoke the agent
|
153
|
+
app.invoke(
|
154
|
+
{"messages": [HumanMessage(content=prompt)]},
|
155
|
+
config=config
|
156
|
+
)
|
157
|
+
current_state = app.get_state(config)
|
158
|
+
reversed_messages = current_state.values["messages"][::-1]
|
159
|
+
# Loop through the reversed messages until a
|
160
|
+
# ToolMessage is found.
|
161
|
+
df = pd.DataFrame(columns=['name', 'status', 'content'])
|
162
|
+
names = []
|
163
|
+
statuses = []
|
164
|
+
contents = []
|
165
|
+
for msg in reversed_messages:
|
166
|
+
# Assert that the message is a ToolMessage
|
167
|
+
# and its status is "error"
|
168
|
+
if not isinstance(msg, ToolMessage):
|
169
|
+
continue
|
170
|
+
names.append(msg.name)
|
171
|
+
statuses.append(msg.status)
|
172
|
+
contents.append(msg.content)
|
173
|
+
df = pd.DataFrame({'name': names, 'status': statuses, 'content': contents})
|
174
|
+
# print (df)
|
175
|
+
assert any((df["status"] == "error") &
|
176
|
+
(df["name"] == "parameter_scan") &
|
177
|
+
(df["content"].str.startswith("Error: ValueError('Invalid parameter name:")))
|
178
|
+
assert any((df["status"] == "success") &
|
179
|
+
(df["name"] == "parameter_scan") &
|
180
|
+
(df["content"].str.startswith("Parameter scan results of")))
|
181
|
+
assert any((df["status"] == "success") &
|
182
|
+
(df["name"] == "get_modelinfo"))
|
183
|
+
|
122
184
|
def test_integration():
|
123
185
|
'''
|
124
186
|
Test the integration of the tools.
|
@@ -184,9 +246,9 @@ def test_integration():
|
|
184
246
|
reversed_messages = current_state.values["messages"][::-1]
|
185
247
|
# Loop through the reversed messages
|
186
248
|
# until a ToolMessage is found.
|
187
|
-
expected_header = ['Time', 'CRP
|
249
|
+
expected_header = ['Time', 'CRP{serum}', 'CRPExtracellular']
|
188
250
|
expected_header += ['CRP Suppression (%)', 'CRP (% of baseline)']
|
189
|
-
expected_header += ['CRP
|
251
|
+
expected_header += ['CRP{liver}']
|
190
252
|
predicted_artifact = []
|
191
253
|
for msg in reversed_messages:
|
192
254
|
if isinstance(msg, ToolMessage):
|
@@ -16,6 +16,8 @@ class TestBioModel(SysBioModel):
|
|
16
16
|
sbml_file_path: Optional[str] = Field(None, description="Path to an SBML file")
|
17
17
|
name: Optional[str] = Field(..., description="Name of the model")
|
18
18
|
description: Optional[str] = Field("", description="Description of the model")
|
19
|
+
param1: Optional[float] = Field(0.0, description="Parameter 1")
|
20
|
+
param2: Optional[float] = Field(0.0, description="Parameter 2")
|
19
21
|
|
20
22
|
def get_model_metadata(self) -> Dict[str, Union[str, int]]:
|
21
23
|
'''
|
@@ -23,15 +25,18 @@ class TestBioModel(SysBioModel):
|
|
23
25
|
'''
|
24
26
|
return self.biomodel_id
|
25
27
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
28
|
+
def update_parameters(self, parameters):
|
29
|
+
'''
|
30
|
+
Update the model parameters.
|
31
|
+
'''
|
32
|
+
self.param1 = parameters.get('param1', 0.0)
|
33
|
+
self.param2 = parameters.get('param2', 0.0)
|
34
|
+
|
35
|
+
def simulate(self, duration: Union[int, float]) -> List[float]:
|
29
36
|
'''
|
30
37
|
Simulate the model.
|
31
38
|
'''
|
32
|
-
param1
|
33
|
-
param2 = parameters.get('param2', 0.0)
|
34
|
-
return [param1 + param2 * t for t in range(int(duration))]
|
39
|
+
return [self.param1 + self.param2 * t for t in range(int(duration))]
|
35
40
|
|
36
41
|
def test_get_model_metadata():
|
37
42
|
'''
|
@@ -53,5 +58,6 @@ def test_simulate():
|
|
53
58
|
Test the simulate method of the BioModel class.
|
54
59
|
'''
|
55
60
|
model = TestBioModel(biomodel_id=123, name="Test Model", description="A test model")
|
56
|
-
|
61
|
+
model.update_parameters({'param1': 1.0, 'param2': 2.0})
|
62
|
+
results = model.simulate(duration=4.0)
|
57
63
|
assert results == [1.0, 3.0, 5.0, 7.0]
|
@@ -47,8 +47,10 @@ class GetModelInfoTool(BaseTool):
|
|
47
47
|
"""
|
48
48
|
This tool ise used extract model information.
|
49
49
|
"""
|
50
|
-
name: str = "
|
51
|
-
description: str = "A tool for extracting
|
50
|
+
name: str = "get_modelinfo"
|
51
|
+
description: str = """A tool for extracting name,
|
52
|
+
description, species, parameters,
|
53
|
+
compartments, and units from a model."""
|
52
54
|
args_schema: Type[BaseModel] = GetModelInfoInput
|
53
55
|
|
54
56
|
def _run(self,
|
@@ -81,7 +83,7 @@ class GetModelInfoTool(BaseTool):
|
|
81
83
|
# Extract species from the model
|
82
84
|
if requested_model_info.species:
|
83
85
|
df_species = basico.model_info.get_species(model=model_obj.copasi_model)
|
84
|
-
dic_results['Species'] = df_species.
|
86
|
+
dic_results['Species'] = df_species['display_name'].tolist()
|
85
87
|
dic_results['Species'] = ','.join(dic_results['Species'])
|
86
88
|
|
87
89
|
# Extract parameters from the model
|
@@ -0,0 +1,292 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
"""
|
4
|
+
Tool for parameter scan.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import logging
|
8
|
+
from dataclasses import dataclass
|
9
|
+
from typing import Type, Union, List, Annotated
|
10
|
+
import pandas as pd
|
11
|
+
import basico
|
12
|
+
from pydantic import BaseModel, Field
|
13
|
+
from langgraph.types import Command
|
14
|
+
from langgraph.prebuilt import InjectedState
|
15
|
+
from langchain_core.tools import BaseTool
|
16
|
+
from langchain_core.messages import ToolMessage
|
17
|
+
from langchain_core.tools.base import InjectedToolCallId
|
18
|
+
from .load_biomodel import ModelData, load_biomodel
|
19
|
+
|
20
|
+
# Initialize logger
|
21
|
+
logging.basicConfig(level=logging.INFO)
|
22
|
+
logger = logging.getLogger(__name__)
|
23
|
+
|
24
|
+
@dataclass
|
25
|
+
class TimeData:
|
26
|
+
"""
|
27
|
+
Dataclass for storing the time data.
|
28
|
+
"""
|
29
|
+
duration: Union[int, float] = 100
|
30
|
+
interval: Union[int, float] = 10
|
31
|
+
|
32
|
+
@dataclass
|
33
|
+
class SpeciesData:
|
34
|
+
"""
|
35
|
+
Dataclass for storing the species data.
|
36
|
+
"""
|
37
|
+
species_name: List[str] = Field(description="species name", default=[])
|
38
|
+
species_concentration: List[Union[int, float]] = Field(
|
39
|
+
description="initial species concentration",
|
40
|
+
default=[])
|
41
|
+
|
42
|
+
@dataclass
|
43
|
+
class TimeSpeciesNameConcentration:
|
44
|
+
"""
|
45
|
+
Dataclass for storing the time, species name, and concentration data.
|
46
|
+
"""
|
47
|
+
time: Union[int, float] = Field(description="time point where the event occurs")
|
48
|
+
species_name: str = Field(description="species name")
|
49
|
+
species_concentration: Union[int, float] = Field(
|
50
|
+
description="species concentration at the time point")
|
51
|
+
|
52
|
+
@dataclass
|
53
|
+
class ReocurringData:
|
54
|
+
"""
|
55
|
+
Dataclass for species that reoccur. In other words, the concentration
|
56
|
+
of the species resets to a certain value after a certain time interval.
|
57
|
+
"""
|
58
|
+
data: List[TimeSpeciesNameConcentration] = Field(
|
59
|
+
description="time, name, and concentration data of species that reoccur",
|
60
|
+
default=[])
|
61
|
+
|
62
|
+
@dataclass
|
63
|
+
class ParameterScanData(BaseModel):
|
64
|
+
"""
|
65
|
+
Dataclass for storing the parameter scan data.
|
66
|
+
"""
|
67
|
+
species_names: List[str] = Field(description="species names to scan",
|
68
|
+
default=[])
|
69
|
+
parameter_name: str = Field(description="Parameter name to scan",
|
70
|
+
default_factory=None)
|
71
|
+
parameter_values: List[Union[int, float]] = Field(
|
72
|
+
description="Parameter values to scan",
|
73
|
+
default_factory=None)
|
74
|
+
|
75
|
+
@dataclass
|
76
|
+
class ArgumentData:
|
77
|
+
"""
|
78
|
+
Dataclass for storing the argument data.
|
79
|
+
"""
|
80
|
+
time_data: TimeData = Field(description="time data", default=None)
|
81
|
+
species_data: SpeciesData = Field(
|
82
|
+
description="species name and initial concentration data",
|
83
|
+
default=None)
|
84
|
+
reocurring_data: ReocurringData = Field(
|
85
|
+
description="""Concentration and time data of species that reoccur
|
86
|
+
For example, a species whose concentration resets to a certain value
|
87
|
+
after a certain time interval""")
|
88
|
+
parameter_scan_data: ParameterScanData = Field(
|
89
|
+
description="parameter scan data",
|
90
|
+
default=None)
|
91
|
+
scan_name: str = Field(
|
92
|
+
description="""An AI assigned `_` separated name of
|
93
|
+
the parameter scan experiment based on human query""")
|
94
|
+
|
95
|
+
def add_rec_events(model_object, reocurring_data):
|
96
|
+
"""
|
97
|
+
Add reocurring events to the model.
|
98
|
+
"""
|
99
|
+
for row in reocurring_data.data:
|
100
|
+
tp, sn, sc = row.time, row.species_name, row.species_concentration
|
101
|
+
basico.add_event(f'{sn}_{tp}',
|
102
|
+
f'Time > {tp}',
|
103
|
+
[[sn, str(sc)]],
|
104
|
+
model=model_object.copasi_model)
|
105
|
+
|
106
|
+
def make_list_dic_scanned_data(dic_param_scan, arg_data, sys_bio_model, tool_call_id):
|
107
|
+
"""
|
108
|
+
Prepare the list dictionary of scanned data
|
109
|
+
that will be passed to the state of the graph.
|
110
|
+
|
111
|
+
Args:
|
112
|
+
dic_param_scan: Dictionary of parameter scan results.
|
113
|
+
arg_data: The argument data.
|
114
|
+
sys_bio_model: The model data.
|
115
|
+
tool_call_id: The tool call ID.
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
list: List of dictionary of scanned data.
|
119
|
+
"""
|
120
|
+
list_dic_scanned_data = []
|
121
|
+
for species_name, df_param_scan in dic_param_scan.items():
|
122
|
+
logger.log(logging.INFO, "Parameter scan results for %s with shape %s",
|
123
|
+
species_name,
|
124
|
+
df_param_scan.shape)
|
125
|
+
# Prepare the list dictionary of scanned data
|
126
|
+
# that will be passed to the state of the graph
|
127
|
+
list_dic_scanned_data.append({
|
128
|
+
'name': arg_data.scan_name+':'+species_name,
|
129
|
+
'source': sys_bio_model.biomodel_id if sys_bio_model.biomodel_id else 'upload',
|
130
|
+
'tool_call_id': tool_call_id,
|
131
|
+
'data': df_param_scan.to_dict()
|
132
|
+
})
|
133
|
+
return list_dic_scanned_data
|
134
|
+
def run_parameter_scan(model_object, arg_data, dic_species_data, duration, interval) -> dict:
|
135
|
+
"""
|
136
|
+
Run parameter scan on the model.
|
137
|
+
|
138
|
+
Args:
|
139
|
+
model_object: The model object.
|
140
|
+
arg_data: The argument data.
|
141
|
+
dic_species_data: Dictionary of species data.
|
142
|
+
duration: Duration of the simulation.
|
143
|
+
interval: Interval between time points in the simulation.
|
144
|
+
|
145
|
+
Returns:
|
146
|
+
dict: Dictionary of parameter scan results. Each key is a species name
|
147
|
+
and each value is a DataFrame containing the results of the parameter scan.
|
148
|
+
"""
|
149
|
+
# Extract all parameter names from the model and verify if the given parameter name is valid
|
150
|
+
df_all_parameters = basico.model_info.get_parameters(model=model_object.copasi_model)
|
151
|
+
all_parameters = df_all_parameters.index.tolist()
|
152
|
+
if arg_data.parameter_scan_data.parameter_name not in all_parameters:
|
153
|
+
logger.error(
|
154
|
+
"Invalid parameter name: %s", arg_data.parameter_scan_data.parameter_name)
|
155
|
+
raise ValueError(
|
156
|
+
f"Invalid parameter name: {arg_data.parameter_scan_data.parameter_name}")
|
157
|
+
# Extract all species name from the model and verify if the given species name is valid
|
158
|
+
df_all_species = basico.model_info.get_species(model=model_object.copasi_model)
|
159
|
+
all_species = df_all_species['display_name'].tolist()
|
160
|
+
# Dictionary to store the parameter scan results
|
161
|
+
dic_param_scan_results = {}
|
162
|
+
for species_name in arg_data.parameter_scan_data.species_names:
|
163
|
+
if species_name not in all_species:
|
164
|
+
logger.error("Invalid species name: %s", species_name)
|
165
|
+
raise ValueError(f"Invalid species name: {species_name}")
|
166
|
+
# Update the fixed model species and parameters
|
167
|
+
# These are the initial conditions of the model
|
168
|
+
# set by the user
|
169
|
+
model_object.update_parameters(dic_species_data)
|
170
|
+
# Initialize empty DataFrame to store results
|
171
|
+
# of the parameter scan
|
172
|
+
df_param_scan = pd.DataFrame()
|
173
|
+
for param_value in arg_data.parameter_scan_data.parameter_values:
|
174
|
+
# Update the parameter value in the model
|
175
|
+
model_object.update_parameters(
|
176
|
+
{arg_data.parameter_scan_data.parameter_name: param_value})
|
177
|
+
# Simulate the model
|
178
|
+
model_object.simulate(duration=duration, interval=interval)
|
179
|
+
# If the column name 'Time' is not present in the results DataFrame
|
180
|
+
if 'Time' not in df_param_scan.columns:
|
181
|
+
df_param_scan['Time'] = model_object.simulation_results['Time']
|
182
|
+
# Add the simulation results to the results DataFrame
|
183
|
+
col_name = f"{arg_data.parameter_scan_data.parameter_name}_{param_value}"
|
184
|
+
df_param_scan[col_name] = model_object.simulation_results[species_name]
|
185
|
+
|
186
|
+
logger.log(logging.INFO, "Parameter scan results with shape %s", df_param_scan.shape)
|
187
|
+
# Add the results of the parameter scan to the dictionary
|
188
|
+
dic_param_scan_results[species_name] = df_param_scan
|
189
|
+
# return df_param_scan
|
190
|
+
return dic_param_scan_results
|
191
|
+
|
192
|
+
class ParameterScanInput(BaseModel):
|
193
|
+
"""
|
194
|
+
Input schema for the ParameterScan tool.
|
195
|
+
"""
|
196
|
+
sys_bio_model: ModelData = Field(description="model data",
|
197
|
+
default=None)
|
198
|
+
arg_data: ArgumentData = Field(description=
|
199
|
+
"""time, species, and reocurring data
|
200
|
+
as well as the parameter scan name and
|
201
|
+
data""",
|
202
|
+
default=None)
|
203
|
+
tool_call_id: Annotated[str, InjectedToolCallId]
|
204
|
+
state: Annotated[dict, InjectedState]
|
205
|
+
|
206
|
+
# Note: It's important that every field has type hints. BaseTool is a
|
207
|
+
# Pydantic class and not having type hints can lead to unexpected behavior.
|
208
|
+
class ParameterScanTool(BaseTool):
|
209
|
+
"""
|
210
|
+
Tool for parameter scan.
|
211
|
+
"""
|
212
|
+
name: str = "parameter_scan"
|
213
|
+
description: str = """A tool to perform parameter scan
|
214
|
+
of a list of parameter values for a given species."""
|
215
|
+
args_schema: Type[BaseModel] = ParameterScanInput
|
216
|
+
|
217
|
+
def _run(self,
|
218
|
+
tool_call_id: Annotated[str, InjectedToolCallId],
|
219
|
+
state: Annotated[dict, InjectedState],
|
220
|
+
sys_bio_model: ModelData = None,
|
221
|
+
arg_data: ArgumentData = None
|
222
|
+
) -> Command:
|
223
|
+
"""
|
224
|
+
Run the tool.
|
225
|
+
|
226
|
+
Args:
|
227
|
+
tool_call_id (str): The tool call ID. This is injected by the system.
|
228
|
+
state (dict): The state of the tool.
|
229
|
+
sys_bio_model (ModelData): The model data.
|
230
|
+
arg_data (ArgumentData): The argument data.
|
231
|
+
|
232
|
+
Returns:
|
233
|
+
Command: The updated state of the tool.
|
234
|
+
"""
|
235
|
+
logger.log(logging.INFO, "Calling parameter_scan tool %s, %s",
|
236
|
+
sys_bio_model, arg_data)
|
237
|
+
sbml_file_path = state['sbml_file_path'][-1] if len(state['sbml_file_path']) > 0 else None
|
238
|
+
model_object = load_biomodel(sys_bio_model,
|
239
|
+
sbml_file_path=sbml_file_path)
|
240
|
+
# Prepare the dictionary of species data
|
241
|
+
# that will be passed to the simulate method
|
242
|
+
# of the BasicoModel class
|
243
|
+
duration = 100.0
|
244
|
+
interval = 10
|
245
|
+
dic_species_data = {}
|
246
|
+
if arg_data:
|
247
|
+
# Prepare the dictionary of species data
|
248
|
+
if arg_data.species_data is not None:
|
249
|
+
dic_species_data = dict(zip(arg_data.species_data.species_name,
|
250
|
+
arg_data.species_data.species_concentration))
|
251
|
+
# Add reocurring events (if any) to the model
|
252
|
+
if arg_data.reocurring_data is not None:
|
253
|
+
add_rec_events(model_object, arg_data.reocurring_data)
|
254
|
+
# Set the duration and interval
|
255
|
+
if arg_data.time_data is not None:
|
256
|
+
duration = arg_data.time_data.duration
|
257
|
+
interval = arg_data.time_data.interval
|
258
|
+
|
259
|
+
# Run the parameter scan
|
260
|
+
dic_param_scan = run_parameter_scan(model_object,
|
261
|
+
arg_data,
|
262
|
+
dic_species_data,
|
263
|
+
duration,
|
264
|
+
interval)
|
265
|
+
|
266
|
+
logger.log(logging.INFO, "Parameter scan results ready")
|
267
|
+
# Prepare the list dictionary of scanned data
|
268
|
+
list_dic_scanned_data = make_list_dic_scanned_data(dic_param_scan,
|
269
|
+
arg_data,
|
270
|
+
sys_bio_model,
|
271
|
+
tool_call_id)
|
272
|
+
# Prepare the dictionary of updated state for the model
|
273
|
+
dic_updated_state_for_model = {}
|
274
|
+
for key, value in {
|
275
|
+
"model_id": [sys_bio_model.biomodel_id],
|
276
|
+
"sbml_file_path": [sbml_file_path],
|
277
|
+
"dic_scanned_data": list_dic_scanned_data,
|
278
|
+
}.items():
|
279
|
+
if value:
|
280
|
+
dic_updated_state_for_model[key] = value
|
281
|
+
# Return the updated state
|
282
|
+
return Command(
|
283
|
+
update=dic_updated_state_for_model|{
|
284
|
+
# update the message history
|
285
|
+
"messages": [
|
286
|
+
ToolMessage(
|
287
|
+
content=f"Parameter scan results of {arg_data.scan_name}",
|
288
|
+
tool_call_id=tool_call_id
|
289
|
+
)
|
290
|
+
],
|
291
|
+
}
|
292
|
+
)
|
@@ -138,7 +138,7 @@ class SimulateModelTool(BaseTool):
|
|
138
138
|
# of the BasicoModel class
|
139
139
|
duration = 100.0
|
140
140
|
interval = 10
|
141
|
-
dic_species_data =
|
141
|
+
dic_species_data = {}
|
142
142
|
if arg_data:
|
143
143
|
# Prepare the dictionary of species data
|
144
144
|
if arg_data.species_data is not None:
|
@@ -151,22 +151,21 @@ class SimulateModelTool(BaseTool):
|
|
151
151
|
if arg_data.time_data is not None:
|
152
152
|
duration = arg_data.time_data.duration
|
153
153
|
interval = arg_data.time_data.interval
|
154
|
-
|
154
|
+
# Update the model parameters
|
155
|
+
model_object.update_parameters(dic_species_data)
|
156
|
+
logger.log(logging.INFO,
|
157
|
+
"Following species/parameters updated in the model %s",
|
158
|
+
dic_species_data)
|
155
159
|
# Simulate the model
|
156
|
-
df = model_object.simulate(
|
157
|
-
|
158
|
-
duration=duration,
|
159
|
-
interval=interval
|
160
|
-
)
|
161
|
-
|
160
|
+
df = model_object.simulate(duration=duration, interval=interval)
|
161
|
+
logger.log(logging.INFO, "Simulation results ready with shape %s", df.shape)
|
162
162
|
dic_simulated_data = {
|
163
163
|
'name': arg_data.simulation_name,
|
164
164
|
'source': sys_bio_model.biomodel_id if sys_bio_model.biomodel_id else 'upload',
|
165
165
|
'tool_call_id': tool_call_id,
|
166
166
|
'data': df.to_dict()
|
167
167
|
}
|
168
|
-
|
169
|
-
# Prepare the dictionary of updated state for the model
|
168
|
+
# Prepare the dictionary of updated state
|
170
169
|
dic_updated_state_for_model = {}
|
171
170
|
for key, value in {
|
172
171
|
"model_id": [sys_bio_model.biomodel_id],
|
@@ -175,7 +174,6 @@ class SimulateModelTool(BaseTool):
|
|
175
174
|
}.items():
|
176
175
|
if value:
|
177
176
|
dic_updated_state_for_model[key] = value
|
178
|
-
|
179
177
|
# Return the updated state of the tool
|
180
178
|
return Command(
|
181
179
|
update=dic_updated_state_for_model|{
|
@@ -0,0 +1,39 @@
|
|
1
|
+
"""
|
2
|
+
Test cases for utils/enrichments/enrichments.py
|
3
|
+
"""
|
4
|
+
|
5
|
+
from ..utils.enrichments.enrichments import Enrichments
|
6
|
+
|
7
|
+
class TestEnrichments(Enrichments):
|
8
|
+
"""Test implementation of the Enrichments interface for testing purposes."""
|
9
|
+
|
10
|
+
def enrich_documents(self, texts: list[str]) -> list[list[float]]:
|
11
|
+
return [
|
12
|
+
f"Additional text description of {text} as the input." for text in texts
|
13
|
+
]
|
14
|
+
|
15
|
+
def enrich_documents_with_rag(self, texts, docs):
|
16
|
+
# Currently we don't have a RAG model to test this method.
|
17
|
+
# Thus, we will just call the enrich_documents method instead.
|
18
|
+
return self.enrich_documents(texts)
|
19
|
+
|
20
|
+
def test_enrich_documents():
|
21
|
+
"""Test enriching documents using the Enrichments interface."""
|
22
|
+
enrichments = TestEnrichments()
|
23
|
+
texts = ["text1", "text2"]
|
24
|
+
result = enrichments.enrich_documents(texts)
|
25
|
+
assert result == [
|
26
|
+
"Additional text description of text1 as the input.",
|
27
|
+
"Additional text description of text2 as the input.",
|
28
|
+
]
|
29
|
+
|
30
|
+
def test_enrich_documents_with_rag():
|
31
|
+
"""Test enriching documents with RAG using the Enrichments interface."""
|
32
|
+
enrichments = TestEnrichments()
|
33
|
+
texts = ["text1", "text2"]
|
34
|
+
docs = ["doc1", "doc2"]
|
35
|
+
result = enrichments.enrich_documents_with_rag(texts, docs)
|
36
|
+
assert result == [
|
37
|
+
"Additional text description of text1 as the input.",
|
38
|
+
"Additional text description of text2 as the input.",
|
39
|
+
]
|
@@ -0,0 +1,117 @@
|
|
1
|
+
"""
|
2
|
+
Test cases for utils/enrichments/ollama.py
|
3
|
+
"""
|
4
|
+
|
5
|
+
import pytest
|
6
|
+
import ollama
|
7
|
+
from ..utils.enrichments.ollama import EnrichmentWithOllama
|
8
|
+
|
9
|
+
@pytest.fixture(name="ollama_config")
|
10
|
+
def fixture_ollama_config():
|
11
|
+
"""Return a dictionary with Ollama configuration."""
|
12
|
+
return {
|
13
|
+
"model_name": "smollm2:360m",
|
14
|
+
"prompt_enrichment": """
|
15
|
+
Given the input as a list of strings, please return the list of addditional information of
|
16
|
+
each input terms using your prior knowledge.
|
17
|
+
|
18
|
+
Example:
|
19
|
+
Input: ['acetaminophen', 'aspirin']
|
20
|
+
Ouput: ['acetaminophen is a medication used to treat pain and fever',
|
21
|
+
'aspirin is a medication used to treat pain, fever, and inflammation']
|
22
|
+
|
23
|
+
Do not include any pretext as the output, only the list of strings enriched.
|
24
|
+
|
25
|
+
Input: {input}
|
26
|
+
""",
|
27
|
+
"temperature": 0.0,
|
28
|
+
"streaming": False,
|
29
|
+
}
|
30
|
+
|
31
|
+
def test_no_model_ollama(ollama_config):
|
32
|
+
"""Test the case when the Ollama model is not available."""
|
33
|
+
cfg = ollama_config
|
34
|
+
cfg_model = "smollm2:135m" # Choose a small model
|
35
|
+
|
36
|
+
# Delete the Ollama model
|
37
|
+
try:
|
38
|
+
ollama.delete(cfg_model)
|
39
|
+
except ollama.ResponseError:
|
40
|
+
pass
|
41
|
+
|
42
|
+
# Check if the model is available
|
43
|
+
with pytest.raises(
|
44
|
+
ValueError, match=f"Error: Pulled {cfg_model} model and restarted Ollama server."
|
45
|
+
):
|
46
|
+
EnrichmentWithOllama(
|
47
|
+
model_name=cfg_model,
|
48
|
+
prompt_enrichment=cfg["prompt_enrichment"],
|
49
|
+
temperature=cfg["temperature"],
|
50
|
+
streaming=cfg["streaming"],
|
51
|
+
)
|
52
|
+
ollama.delete(cfg_model)
|
53
|
+
|
54
|
+
def test_enrich_nodes_ollama(ollama_config):
|
55
|
+
"""Test the Ollama textual enrichment class for node enrichment."""
|
56
|
+
# Prepare enrichment model
|
57
|
+
cfg = ollama_config
|
58
|
+
enr_model = EnrichmentWithOllama(
|
59
|
+
model_name=cfg["model_name"],
|
60
|
+
prompt_enrichment=cfg["prompt_enrichment"],
|
61
|
+
temperature=cfg["temperature"],
|
62
|
+
streaming=cfg["streaming"],
|
63
|
+
)
|
64
|
+
|
65
|
+
# Perform enrichment for nodes
|
66
|
+
nodes = ["Adalimumab", "Infliximab"]
|
67
|
+
enriched_nodes = enr_model.enrich_documents(nodes)
|
68
|
+
# Check the enriched nodes
|
69
|
+
assert len(enriched_nodes) == 2
|
70
|
+
assert all(
|
71
|
+
enriched_nodes[i] != nodes[i] for i in range(len(nodes))
|
72
|
+
)
|
73
|
+
|
74
|
+
|
75
|
+
def test_enrich_relations_ollama(ollama_config):
|
76
|
+
"""Test the Ollama textual enrichment class for relation enrichment."""
|
77
|
+
# Prepare enrichment model
|
78
|
+
cfg = ollama_config
|
79
|
+
enr_model = EnrichmentWithOllama(
|
80
|
+
model_name=cfg["model_name"],
|
81
|
+
prompt_enrichment=cfg["prompt_enrichment"],
|
82
|
+
temperature=cfg["temperature"],
|
83
|
+
streaming=cfg["streaming"],
|
84
|
+
)
|
85
|
+
# Perform enrichment for relations
|
86
|
+
relations = [
|
87
|
+
"IL23R-gene causation disease-inflammatory bowel diseases",
|
88
|
+
"NOD2-gene causation disease-inflammatory bowel diseases",
|
89
|
+
]
|
90
|
+
enriched_relations = enr_model.enrich_documents(relations)
|
91
|
+
# Check the enriched relations
|
92
|
+
assert len(enriched_relations) == 2
|
93
|
+
assert all(
|
94
|
+
enriched_relations[i] != relations[i]
|
95
|
+
for i in range(len(relations))
|
96
|
+
)
|
97
|
+
|
98
|
+
|
99
|
+
def test_enrich_ollama_rag(ollama_config):
|
100
|
+
"""Test the Ollama textual enrichment class for enrichment with RAG (not implemented)."""
|
101
|
+
# Prepare enrichment model
|
102
|
+
cfg = ollama_config
|
103
|
+
enr_model = EnrichmentWithOllama(
|
104
|
+
model_name=cfg["model_name"],
|
105
|
+
prompt_enrichment=cfg["prompt_enrichment"],
|
106
|
+
temperature=cfg["temperature"],
|
107
|
+
streaming=cfg["streaming"],
|
108
|
+
)
|
109
|
+
# Perform enrichment for nodes
|
110
|
+
nodes = ["Adalimumab", "Infliximab"]
|
111
|
+
docs = [r"\path\to\doc1", r"\path\to\doc2"]
|
112
|
+
enriched_nodes = enr_model.enrich_documents_with_rag(nodes, docs)
|
113
|
+
# Check the enriched nodes
|
114
|
+
assert len(enriched_nodes) == 2
|
115
|
+
assert all(
|
116
|
+
enriched_nodes[i] != nodes[i] for i in range(len(nodes))
|
117
|
+
)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
"""
|
2
|
+
Enrichments interface
|
3
|
+
"""
|
4
|
+
|
5
|
+
from abc import ABC, abstractmethod
|
6
|
+
|
7
|
+
class Enrichments(ABC):
|
8
|
+
"""Interface for enrichment models.
|
9
|
+
|
10
|
+
This is an interface meant for implementing text enrichment models.
|
11
|
+
|
12
|
+
Enrichment models are used to enrich node or relation features in a given knowledge graph.
|
13
|
+
"""
|
14
|
+
|
15
|
+
@abstractmethod
|
16
|
+
def enrich_documents(self, texts: list[str]) -> list[list[str]]:
|
17
|
+
"""Enrich documents.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
texts: List of documents to enrich.
|
21
|
+
|
22
|
+
Returns:
|
23
|
+
List of enriched documents.
|
24
|
+
"""
|
25
|
+
|
26
|
+
@abstractmethod
|
27
|
+
def enrich_documents_with_rag(self, texts: list[str], docs: list[str]) -> list[str]:
|
28
|
+
"""Enrich documents with RAG.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
texts: List of documents to enrich.
|
32
|
+
docs: List of reference documents to enrich the input texts.
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
List of enriched documents with RAG.
|
36
|
+
"""
|
@@ -0,0 +1,123 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
"""
|
4
|
+
Enrichment class using Ollama model based on LangChain Enrichment class.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import time
|
8
|
+
from typing import List
|
9
|
+
import subprocess
|
10
|
+
import ast
|
11
|
+
import ollama
|
12
|
+
from langchain_ollama import ChatOllama
|
13
|
+
from langchain_core.prompts import ChatPromptTemplate
|
14
|
+
from langchain_core.output_parsers import StrOutputParser
|
15
|
+
from .enrichments import Enrichments
|
16
|
+
|
17
|
+
class EnrichmentWithOllama(Enrichments):
|
18
|
+
"""
|
19
|
+
Enrichment class using Ollama model based on the Enrichment abstract class.
|
20
|
+
"""
|
21
|
+
def __init__(
|
22
|
+
self,
|
23
|
+
model_name: str,
|
24
|
+
prompt_enrichment: str,
|
25
|
+
temperature: float,
|
26
|
+
streaming: bool,
|
27
|
+
):
|
28
|
+
"""
|
29
|
+
Initialize the EnrichmentWithOllama class.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
model_name: The name of the Ollama model to be used.
|
33
|
+
prompt_enrichment: The prompt enrichment template.
|
34
|
+
temperature: The temperature for the Ollama model.
|
35
|
+
streaming: The streaming flag for the Ollama model.
|
36
|
+
"""
|
37
|
+
# Setup the Ollama server
|
38
|
+
self.__setup(model_name)
|
39
|
+
|
40
|
+
# Set parameters
|
41
|
+
self.model_name = model_name
|
42
|
+
self.prompt_enrichment = prompt_enrichment
|
43
|
+
self.temperature = temperature
|
44
|
+
self.streaming = streaming
|
45
|
+
|
46
|
+
# Prepare prompt template
|
47
|
+
self.prompt_template = ChatPromptTemplate.from_messages(
|
48
|
+
[
|
49
|
+
("system", self.prompt_enrichment),
|
50
|
+
("human", "{input}"),
|
51
|
+
]
|
52
|
+
)
|
53
|
+
|
54
|
+
# Prepare model
|
55
|
+
self.model = ChatOllama(
|
56
|
+
model=self.model_name,
|
57
|
+
temperature=self.temperature,
|
58
|
+
streaming=self.streaming,
|
59
|
+
)
|
60
|
+
|
61
|
+
def __setup(self, model_name: str) -> None:
|
62
|
+
"""
|
63
|
+
Check if the Ollama model is available and run the Ollama server if needed.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
model_name: The name of the Ollama model to be used.
|
67
|
+
"""
|
68
|
+
try:
|
69
|
+
models_list = ollama.list()["models"]
|
70
|
+
if model_name not in [m['model'].replace(":latest", "") for m in models_list]:
|
71
|
+
ollama.pull(model_name)
|
72
|
+
time.sleep(30)
|
73
|
+
raise ValueError(f"Pulled {model_name} model")
|
74
|
+
except Exception as e:
|
75
|
+
with subprocess.Popen(
|
76
|
+
"ollama serve", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
77
|
+
):
|
78
|
+
time.sleep(10)
|
79
|
+
raise ValueError(f"Error: {e} and restarted Ollama server.") from e
|
80
|
+
|
81
|
+
def enrich_documents(self, texts: List[str]) -> List[str]:
|
82
|
+
"""
|
83
|
+
Enrich a list of input texts with additional textual features using OLLAMA model.
|
84
|
+
Important: Make sure the input is a list of texts based on the defined prompt template
|
85
|
+
with 'input' as the variable name.
|
86
|
+
|
87
|
+
Args:
|
88
|
+
texts: The list of texts to be enriched.
|
89
|
+
|
90
|
+
Returns:
|
91
|
+
The list of enriched texts.
|
92
|
+
"""
|
93
|
+
|
94
|
+
# Perform enrichment
|
95
|
+
chain = self.prompt_template | self.model | StrOutputParser()
|
96
|
+
|
97
|
+
# Generate the enriched node
|
98
|
+
# Important: Make sure the input is a list of texts based on the defined prompt template
|
99
|
+
# with 'input' as the variable name
|
100
|
+
enriched_texts = chain.invoke({"input": "[" + ", ".join(texts) + "]"})
|
101
|
+
|
102
|
+
# Convert the enriched nodes to a list of dictionary
|
103
|
+
enriched_texts = ast.literal_eval(enriched_texts.replace("```", ""))
|
104
|
+
|
105
|
+
# Final check for the enriched texts
|
106
|
+
assert len(enriched_texts) == len(texts)
|
107
|
+
|
108
|
+
return enriched_texts
|
109
|
+
|
110
|
+
def enrich_documents_with_rag(self, texts, docs):
|
111
|
+
"""
|
112
|
+
Enrich a list of input texts with additional textual features using OLLAMA model with RAG.
|
113
|
+
As of now, we don't have a RAG model to test this method yet.
|
114
|
+
Thus, we will just call the enrich_documents method instead.
|
115
|
+
|
116
|
+
Args:
|
117
|
+
texts: The list of texts to be enriched.
|
118
|
+
docs: The list of reference documents to enrich the input texts.
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
The list of enriched texts
|
122
|
+
"""
|
123
|
+
return self.enrich_documents(texts)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: aiagents4pharma
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.12.0
|
4
4
|
Summary: AI Agents for drug discovery, drug development, and other pharmaceutical R&D
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
@@ -20,9 +20,11 @@ Requires-Dist: langchain-community==0.3.5
|
|
20
20
|
Requires-Dist: langchain-core==0.3.31
|
21
21
|
Requires-Dist: langchain-experimental==0.3.3
|
22
22
|
Requires-Dist: langchain-openai==0.2.5
|
23
|
+
Requires-Dist: langchain_ollama==0.2.2
|
23
24
|
Requires-Dist: langgraph==0.2.66
|
24
25
|
Requires-Dist: matplotlib==3.9.2
|
25
26
|
Requires-Dist: openai==1.59.4
|
27
|
+
Requires-Dist: ollama==0.4.6
|
26
28
|
Requires-Dist: pandas==2.2.3
|
27
29
|
Requires-Dist: plotly==5.24.1
|
28
30
|
Requires-Dist: pydantic==2.9.2
|
@@ -74,6 +76,7 @@ Our toolkit currently consists of three intelligent agents, each designed to sim
|
|
74
76
|
- Forward simulation of both internal and open-source models (BioModels).
|
75
77
|
- Adjust parameters within the model to simulate different conditions.
|
76
78
|
- Query simulation results.
|
79
|
+
- Extract model information such as species, parameters, units and description.
|
77
80
|
|
78
81
|
### 2. Talk2Cells _(Work in Progress)_
|
79
82
|
|
@@ -87,11 +90,7 @@ Our toolkit currently consists of three intelligent agents, each designed to sim
|
|
87
90
|
|
88
91
|
## Getting Started
|
89
92
|
|
90
|
-
|
91
|
-
|
92
|
-
- **Python 3.10+**
|
93
|
-
- **Git**
|
94
|
-
- Required libraries specified in `requirements.txt`
|
93
|
+

|
95
94
|
|
96
95
|
### Installation
|
97
96
|
|
@@ -7,23 +7,24 @@ aiagents4pharma/configs/talk2biomodels/agents/t2b_agent/__init__.py,sha256=-fAOR
|
|
7
7
|
aiagents4pharma/configs/talk2biomodels/agents/t2b_agent/default.yaml,sha256=yD7qZCneaM-JE5PdZjDmDoTRUdsFrzeCKZsBx1b-f20,293
|
8
8
|
aiagents4pharma/talk2biomodels/__init__.py,sha256=qUw3qXrENqSCLIKSLy_qtNPwPDTb1wdZ8fZispcHb3g,141
|
9
9
|
aiagents4pharma/talk2biomodels/agents/__init__.py,sha256=sn5-fREjMdEvb-OUan3iOqrgYGjplNx3J8hYOaW0Po8,128
|
10
|
-
aiagents4pharma/talk2biomodels/agents/t2b_agent.py,sha256=
|
10
|
+
aiagents4pharma/talk2biomodels/agents/t2b_agent.py,sha256=6Im4YFcdykN7wpEM8y9qi_x4lTg02WJpb0SEWh8TPLo,3188
|
11
11
|
aiagents4pharma/talk2biomodels/models/__init__.py,sha256=5fTHHm3PVloYPNKXbgNlcPgv3-u28ZquxGydFYDfhJA,122
|
12
|
-
aiagents4pharma/talk2biomodels/models/basico_model.py,sha256=
|
13
|
-
aiagents4pharma/talk2biomodels/models/sys_bio_model.py,sha256=
|
12
|
+
aiagents4pharma/talk2biomodels/models/basico_model.py,sha256=PH25FTOuUjsmw_UUxoRb-4kptOYpicEn4GqS0phS3nk,4807
|
13
|
+
aiagents4pharma/talk2biomodels/models/sys_bio_model.py,sha256=JeoiGQAvQABHnG0wKR2XBmmxqQdtgO6kxaLDUTUmr1s,2001
|
14
14
|
aiagents4pharma/talk2biomodels/states/__init__.py,sha256=YLg1-N0D9qyRRLRqwqfLCLAqZYDtMVZTfI8Y0b_4tbA,139
|
15
|
-
aiagents4pharma/talk2biomodels/states/state_talk2biomodels.py,sha256=
|
15
|
+
aiagents4pharma/talk2biomodels/states/state_talk2biomodels.py,sha256=Dlsnh9dW1mCXTBXmlDAlOox7f4azFbLBG_2k3YPielM,824
|
16
16
|
aiagents4pharma/talk2biomodels/tests/__init__.py,sha256=Jbw5tJxSrjGoaK5IX3pJWDCNzhrVQ10lkYq2oQ_KQD8,45
|
17
|
-
aiagents4pharma/talk2biomodels/tests/test_basico_model.py,sha256=
|
18
|
-
aiagents4pharma/talk2biomodels/tests/test_langgraph.py,sha256=
|
19
|
-
aiagents4pharma/talk2biomodels/tests/test_sys_bio_model.py,sha256=
|
20
|
-
aiagents4pharma/talk2biomodels/tools/__init__.py,sha256=
|
17
|
+
aiagents4pharma/talk2biomodels/tests/test_basico_model.py,sha256=y82fpTJMPHwtXxlle1cGQ_2Bewwpxi0aJSVrVAYLhN0,2060
|
18
|
+
aiagents4pharma/talk2biomodels/tests/test_langgraph.py,sha256=_71UZS1zucn6Nus4oCH7tINQVRvJEFnL0UIZ6-sUd3I,11967
|
19
|
+
aiagents4pharma/talk2biomodels/tests/test_sys_bio_model.py,sha256=HSmBBViMi0jYf4gWX21IbppAfDzG0nr_S3KtKS9fZVQ,2165
|
20
|
+
aiagents4pharma/talk2biomodels/tools/__init__.py,sha256=SMTMlGHxTuJI7gjwGaTMv0XmilJ73-r5dp568hD3Fw0,266
|
21
21
|
aiagents4pharma/talk2biomodels/tools/ask_question.py,sha256=uxCQ4ON8--D0ACPvT14t6x_aqm9LP6woBA4GM7bPXc4,3061
|
22
22
|
aiagents4pharma/talk2biomodels/tools/custom_plotter.py,sha256=HWwKTX3o4dk0GcRVTO2hPrFSu98mtJ4TKC_hbHXOe1c,4018
|
23
|
-
aiagents4pharma/talk2biomodels/tools/get_modelinfo.py,sha256=
|
23
|
+
aiagents4pharma/talk2biomodels/tools/get_modelinfo.py,sha256=qA-4FOI-O728Nmn7s8JJ8HKwxvA9MZbst7NkPKTAMV4,5391
|
24
24
|
aiagents4pharma/talk2biomodels/tools/load_biomodel.py,sha256=pyVzLQoMnuJYEwsjeOlqcUrbU1F1Z-pNlgkhFaoKpy0,689
|
25
|
+
aiagents4pharma/talk2biomodels/tools/parameter_scan.py,sha256=aIyL_m46s3Q74ieJOZjZBM34VCjBKSMpEtckhdZofbE,12139
|
25
26
|
aiagents4pharma/talk2biomodels/tools/search_models.py,sha256=Iq2ddofOOfZYtAurCISq3bAq5rbwB3l_rL1lgEFyFCI,2653
|
26
|
-
aiagents4pharma/talk2biomodels/tools/simulate_model.py,sha256=
|
27
|
+
aiagents4pharma/talk2biomodels/tools/simulate_model.py,sha256=sWmFVnVvJbdXXTqn_7gQl5UW0tv4FyU5yLXWLweLs_M,7059
|
27
28
|
aiagents4pharma/talk2cells/__init__.py,sha256=zmOP5RAhabgKIQP-W4P4qKME2tG3fhAXM3MeO5_H8kE,120
|
28
29
|
aiagents4pharma/talk2cells/agents/__init__.py,sha256=38nK2a_lEFRjO3qD6Fo9a3983ZCYat6hmJKWY61y2Mo,128
|
29
30
|
aiagents4pharma/talk2cells/agents/scp_agent.py,sha256=gDMfhUNWHa_XWOqm1Ql6yLAdI_7bnIk5sRYn43H2sYk,3090
|
@@ -50,7 +51,7 @@ aiagents4pharma/talk2competitors/tools/s2/display_results.py,sha256=B8JJGohi1Eyx
|
|
50
51
|
aiagents4pharma/talk2competitors/tools/s2/multi_paper_rec.py,sha256=FYLt47DAk6WOKfEk1Gj9zVvJGNyxA283PCp8IKW9U5M,4262
|
51
52
|
aiagents4pharma/talk2competitors/tools/s2/search.py,sha256=pppjrQv5-8ep4fnqgTSBNgnbSnQsVIcNrRrH0p2TP1o,4025
|
52
53
|
aiagents4pharma/talk2competitors/tools/s2/single_paper_rec.py,sha256=dAfUQxI7T5eu0eDxK8VAl7-JH0Wnw24CVkOQqwj-hXc,4810
|
53
|
-
aiagents4pharma/talk2knowledgegraphs/__init__.py,sha256=
|
54
|
+
aiagents4pharma/talk2knowledgegraphs/__init__.py,sha256=4smVQoSMM6rflVnNkABqlDAAlSn4bYsq7rMVWjRGvis,103
|
54
55
|
aiagents4pharma/talk2knowledgegraphs/datasets/__init__.py,sha256=L3gPuHskSegmtXskVrLIYr7FXe_ibKgJ2GGr1_Wok6k,173
|
55
56
|
aiagents4pharma/talk2knowledgegraphs/datasets/biobridge_primekg.py,sha256=QlzDXmXREoa9MA6-GwzqRjdzndQeGBAF11Td6NFk_9Y,23426
|
56
57
|
aiagents4pharma/talk2knowledgegraphs/datasets/dataset.py,sha256=-LaPLse8BkALqwFetNK7wch2dt9Dz6QKGKZKBKM6bIk,409
|
@@ -64,14 +65,19 @@ aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_starkqa_primekg.py,sha2
|
|
64
65
|
aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_embeddings.py,sha256=uYFoE_6zeU10_1mLLAHUr5c4S2XZMSc0Q_860o-KWEw,1517
|
65
66
|
aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_huggingface.py,sha256=EINWyXg_3AMHF3WzFLhIUiFDuaEhTVHBvVAJr8VtMDg,1624
|
66
67
|
aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_sentencetransformer.py,sha256=Qxo6WeIDRy8aLh1tNKw0kSlzmUj3MtTak63oW2YwB24,1327
|
67
|
-
aiagents4pharma/talk2knowledgegraphs/
|
68
|
+
aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_enrichments.py,sha256=N6HRr4lWHXY7bTHe2uXJe4D_EG9WqZPibZne6qLl9_k,1447
|
69
|
+
aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_ollama.py,sha256=kMuB_vci6hKr2qJgXBmcje7yxeJ2nY2ImXw-NSJpts0,3912
|
70
|
+
aiagents4pharma/talk2knowledgegraphs/utils/__init__.py,sha256=X8kSpmDTMkeE0fA5D0CWMsQ52YKoM5rRSXrjnali3IM,97
|
68
71
|
aiagents4pharma/talk2knowledgegraphs/utils/kg_utils.py,sha256=6vQnPkeOWae_8jePjhma3sJuMTngy0I0tqzdFt6OqKg,2507
|
69
72
|
aiagents4pharma/talk2knowledgegraphs/utils/embeddings/__init__.py,sha256=xRb0x7SoAb0nSVZYgjrqxWvENOMDuqIdL43NMjoOaCs,153
|
70
73
|
aiagents4pharma/talk2knowledgegraphs/utils/embeddings/embeddings.py,sha256=1nGznrAj-xT0xuSMBGz2dOujJ7M_IwSR84njxtxsy9A,2523
|
71
74
|
aiagents4pharma/talk2knowledgegraphs/utils/embeddings/huggingface.py,sha256=2vi_elf6EgzfagFAO5QnL3a_aXZyN7B1EBziu44MTfM,3806
|
72
75
|
aiagents4pharma/talk2knowledgegraphs/utils/embeddings/sentence_transformer.py,sha256=36iKlisOpMtGR5xfTAlSHXWvPqVC_Jbezod8kbBBMVg,2136
|
73
|
-
aiagents4pharma
|
74
|
-
aiagents4pharma
|
75
|
-
aiagents4pharma
|
76
|
-
aiagents4pharma-1.
|
77
|
-
aiagents4pharma-1.
|
76
|
+
aiagents4pharma/talk2knowledgegraphs/utils/enrichments/__init__.py,sha256=tW426knki2DBIHcWyF_K04iMMdbpIn_e_TpPmTgz2dI,113
|
77
|
+
aiagents4pharma/talk2knowledgegraphs/utils/enrichments/enrichments.py,sha256=Bx8x6zzk5614ApWB90N_iv4_Y_Uq0-KwUeBwYSdQMU4,924
|
78
|
+
aiagents4pharma/talk2knowledgegraphs/utils/enrichments/ollama.py,sha256=8eoxR-VHo0G7ReQIwje7xEhE-SJlHdef7_wJRpnvFIc,4116
|
79
|
+
aiagents4pharma-1.12.0.dist-info/LICENSE,sha256=IcIbyB1Hyk5ZDah03VNQvJkbNk2hkBCDqQ8qtnCvB4Q,1077
|
80
|
+
aiagents4pharma-1.12.0.dist-info/METADATA,sha256=Okx1RPc3qMmJ2a7ca2YvSFr7-gxqAR51LVLkOTh5O2k,8609
|
81
|
+
aiagents4pharma-1.12.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
82
|
+
aiagents4pharma-1.12.0.dist-info/top_level.txt,sha256=-AH8rMmrSnJtq7HaAObS78UU-cTCwvX660dSxeM7a0A,16
|
83
|
+
aiagents4pharma-1.12.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|