aiagents4pharma 1.10.0__tar.gz → 1.11.0__tar.gz

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.
Files changed (85) hide show
  1. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/PKG-INFO +3 -6
  2. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/README.md +2 -5
  3. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/agents/t2b_agent.py +7 -10
  4. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/models/basico_model.py +29 -32
  5. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/models/sys_bio_model.py +9 -6
  6. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/states/state_talk2biomodels.py +3 -3
  7. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/tests/test_basico_model.py +7 -8
  8. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/tests/test_langgraph.py +64 -2
  9. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/tests/test_sys_bio_model.py +13 -7
  10. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/tools/__init__.py +1 -0
  11. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/tools/get_modelinfo.py +5 -3
  12. aiagents4pharma-1.11.0/aiagents4pharma/talk2biomodels/tools/parameter_scan.py +292 -0
  13. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/tools/simulate_model.py +9 -11
  14. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma.egg-info/PKG-INFO +3 -6
  15. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma.egg-info/SOURCES.txt +1 -0
  16. aiagents4pharma-1.11.0/release_version.txt +1 -0
  17. aiagents4pharma-1.10.0/release_version.txt +0 -1
  18. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/LICENSE +0 -0
  19. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/__init__.py +0 -0
  20. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/configs/__init__.py +0 -0
  21. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/configs/config.yaml +0 -0
  22. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/configs/talk2biomodels/__init__.py +0 -0
  23. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/configs/talk2biomodels/agents/__init__.py +0 -0
  24. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/configs/talk2biomodels/agents/t2b_agent/__init__.py +0 -0
  25. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/configs/talk2biomodels/agents/t2b_agent/default.yaml +0 -0
  26. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/__init__.py +0 -0
  27. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/agents/__init__.py +0 -0
  28. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/models/__init__.py +0 -0
  29. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/states/__init__.py +0 -0
  30. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/tests/__init__.py +0 -0
  31. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/tools/ask_question.py +0 -0
  32. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/tools/custom_plotter.py +0 -0
  33. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/tools/load_biomodel.py +0 -0
  34. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2biomodels/tools/search_models.py +0 -0
  35. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2cells/__init__.py +0 -0
  36. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2cells/agents/__init__.py +0 -0
  37. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2cells/agents/scp_agent.py +0 -0
  38. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2cells/states/__init__.py +0 -0
  39. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2cells/states/state_talk2cells.py +0 -0
  40. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2cells/tests/scp_agent/test_scp_agent.py +0 -0
  41. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2cells/tools/__init__.py +0 -0
  42. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2cells/tools/scp_agent/__init__.py +0 -0
  43. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2cells/tools/scp_agent/display_studies.py +0 -0
  44. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2cells/tools/scp_agent/search_studies.py +0 -0
  45. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2competitors/__init__.py +0 -0
  46. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2competitors/agents/__init__.py +0 -0
  47. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2competitors/agents/main_agent.py +0 -0
  48. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2competitors/agents/s2_agent.py +0 -0
  49. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2competitors/config/__init__.py +0 -0
  50. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2competitors/config/config.py +0 -0
  51. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2competitors/state/__init__.py +0 -0
  52. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2competitors/state/state_talk2competitors.py +0 -0
  53. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2competitors/tests/__init__.py +0 -0
  54. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2competitors/tests/test_langgraph.py +0 -0
  55. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2competitors/tools/__init__.py +0 -0
  56. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2competitors/tools/s2/__init__.py +0 -0
  57. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2competitors/tools/s2/display_results.py +0 -0
  58. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2competitors/tools/s2/multi_paper_rec.py +0 -0
  59. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2competitors/tools/s2/search.py +0 -0
  60. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2competitors/tools/s2/single_paper_rec.py +0 -0
  61. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/__init__.py +0 -0
  62. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/datasets/__init__.py +0 -0
  63. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/datasets/biobridge_primekg.py +0 -0
  64. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/datasets/dataset.py +0 -0
  65. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/datasets/primekg.py +0 -0
  66. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/datasets/starkqa_primekg.py +0 -0
  67. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/tests/__init__.py +0 -0
  68. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_biobridge_primekg.py +0 -0
  69. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_dataset.py +0 -0
  70. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_primekg.py +0 -0
  71. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_starkqa_primekg.py +0 -0
  72. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_embeddings.py +0 -0
  73. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_huggingface.py +0 -0
  74. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_sentencetransformer.py +0 -0
  75. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/utils/__init__.py +0 -0
  76. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/utils/embeddings/__init__.py +0 -0
  77. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/utils/embeddings/embeddings.py +0 -0
  78. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/utils/embeddings/huggingface.py +0 -0
  79. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/utils/embeddings/sentence_transformer.py +0 -0
  80. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma/talk2knowledgegraphs/utils/kg_utils.py +0 -0
  81. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma.egg-info/dependency_links.txt +0 -0
  82. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma.egg-info/requires.txt +0 -0
  83. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/aiagents4pharma.egg-info/top_level.txt +0 -0
  84. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/pyproject.toml +0 -0
  85. {aiagents4pharma-1.10.0 → aiagents4pharma-1.11.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: aiagents4pharma
3
- Version: 1.10.0
3
+ Version: 1.11.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
@@ -74,6 +74,7 @@ Our toolkit currently consists of three intelligent agents, each designed to sim
74
74
  - Forward simulation of both internal and open-source models (BioModels).
75
75
  - Adjust parameters within the model to simulate different conditions.
76
76
  - Query simulation results.
77
+ - Extract model information such as species, parameters, units and description.
77
78
 
78
79
  ### 2. Talk2Cells _(Work in Progress)_
79
80
 
@@ -87,11 +88,7 @@ Our toolkit currently consists of three intelligent agents, each designed to sim
87
88
 
88
89
  ## Getting Started
89
90
 
90
- ### Prerequisites
91
-
92
- - **Python 3.10+**
93
- - **Git**
94
- - Required libraries specified in `requirements.txt`
91
+ ![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2FVirtualPatientEngine%2FAIAgents4Pharma%2Frefs%2Fheads%2Fmain%2Fpyproject.toml)
95
92
 
96
93
  ### Installation
97
94
 
@@ -28,6 +28,7 @@ Our toolkit currently consists of three intelligent agents, each designed to sim
28
28
  - Forward simulation of both internal and open-source models (BioModels).
29
29
  - Adjust parameters within the model to simulate different conditions.
30
30
  - Query simulation results.
31
+ - Extract model information such as species, parameters, units and description.
31
32
 
32
33
  ### 2. Talk2Cells _(Work in Progress)_
33
34
 
@@ -41,11 +42,7 @@ Our toolkit currently consists of three intelligent agents, each designed to sim
41
42
 
42
43
  ## Getting Started
43
44
 
44
- ### Prerequisites
45
-
46
- - **Python 3.10+**
47
- - **Git**
48
- - Required libraries specified in `requirements.txt`
45
+ ![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2FVirtualPatientEngine%2FAIAgents4Pharma%2Frefs%2Fheads%2Fmain%2Fpyproject.toml)
49
46
 
50
47
  ### Installation
51
48
 
@@ -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
- simulate_model,
45
- ask_question,
46
- custom_plotter,
47
- search_model,
48
- get_modelinfo
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 simulate(self,
52
- parameters: Optional[Dict[str, Union[float, int]]] = None,
53
- duration: Union[int, float] = 10,
54
- interval: int = 10
55
- ) -> pd.DataFrame:
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
- str.replace('}', ']', regex=False)
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
- model_id: Annotated[list, operator.add]
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
- llm_model: str
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(parameters={'Pyruvate': 0.5, 'KmPFKF6P': 1.5},
24
- duration=2,
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[serum]', 'CRPExtracellular']
249
+ expected_header = ['Time', 'CRP{serum}', 'CRPExtracellular']
188
250
  expected_header += ['CRP Suppression (%)', 'CRP (% of baseline)']
189
- expected_header += ['CRP[liver]']
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 simulate(self,
27
- parameters: Dict[str, Union[float, int]],
28
- duration: Union[int, float]) -> List[float]:
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 = parameters.get('param1', 0.0)
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
- results = model.simulate(parameters={'param1': 1.0, 'param2': 2.0}, duration=4.0)
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]
@@ -6,4 +6,5 @@ from . import simulate_model
6
6
  from . import ask_question
7
7
  from . import custom_plotter
8
8
  from . import get_modelinfo
9
+ from . import parameter_scan
9
10
  from . import load_biomodel
@@ -47,8 +47,10 @@ class GetModelInfoTool(BaseTool):
47
47
  """
48
48
  This tool ise used extract model information.
49
49
  """
50
- name: str = "get_parameters"
51
- description: str = "A tool for extracting model information."
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.index.tolist()
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 = None
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
- parameters=dic_species_data,
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|{
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: aiagents4pharma
3
- Version: 1.10.0
3
+ Version: 1.11.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
@@ -74,6 +74,7 @@ Our toolkit currently consists of three intelligent agents, each designed to sim
74
74
  - Forward simulation of both internal and open-source models (BioModels).
75
75
  - Adjust parameters within the model to simulate different conditions.
76
76
  - Query simulation results.
77
+ - Extract model information such as species, parameters, units and description.
77
78
 
78
79
  ### 2. Talk2Cells _(Work in Progress)_
79
80
 
@@ -87,11 +88,7 @@ Our toolkit currently consists of three intelligent agents, each designed to sim
87
88
 
88
89
  ## Getting Started
89
90
 
90
- ### Prerequisites
91
-
92
- - **Python 3.10+**
93
- - **Git**
94
- - Required libraries specified in `requirements.txt`
91
+ ![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2FVirtualPatientEngine%2FAIAgents4Pharma%2Frefs%2Fheads%2Fmain%2Fpyproject.toml)
95
92
 
96
93
  ### Installation
97
94
 
@@ -31,6 +31,7 @@ aiagents4pharma/talk2biomodels/tools/ask_question.py
31
31
  aiagents4pharma/talk2biomodels/tools/custom_plotter.py
32
32
  aiagents4pharma/talk2biomodels/tools/get_modelinfo.py
33
33
  aiagents4pharma/talk2biomodels/tools/load_biomodel.py
34
+ aiagents4pharma/talk2biomodels/tools/parameter_scan.py
34
35
  aiagents4pharma/talk2biomodels/tools/search_models.py
35
36
  aiagents4pharma/talk2biomodels/tools/simulate_model.py
36
37
  aiagents4pharma/talk2cells/__init__.py
@@ -0,0 +1 @@
1
+ v1.11.0
@@ -1 +0,0 @@
1
- v1.10.0