aiagents4pharma 1.13.1__py3-none-any.whl → 1.14.1__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.
Files changed (34) hide show
  1. aiagents4pharma/configs/config.yaml +2 -1
  2. aiagents4pharma/configs/talk2biomodels/__init__.py +1 -0
  3. aiagents4pharma/configs/talk2biomodels/agents/t2b_agent/default.yaml +2 -3
  4. aiagents4pharma/configs/talk2biomodels/tools/__init__.py +4 -0
  5. aiagents4pharma/configs/talk2biomodels/tools/ask_question/__init__.py +3 -0
  6. aiagents4pharma/talk2biomodels/__init__.py +1 -0
  7. aiagents4pharma/talk2biomodels/agents/t2b_agent.py +4 -2
  8. aiagents4pharma/talk2biomodels/api/__init__.py +6 -0
  9. aiagents4pharma/talk2biomodels/api/kegg.py +83 -0
  10. aiagents4pharma/talk2biomodels/api/ols.py +72 -0
  11. aiagents4pharma/talk2biomodels/api/uniprot.py +35 -0
  12. aiagents4pharma/talk2biomodels/states/state_talk2biomodels.py +21 -6
  13. aiagents4pharma/talk2biomodels/tests/test_api.py +57 -0
  14. aiagents4pharma/talk2biomodels/tests/test_ask_question.py +44 -0
  15. aiagents4pharma/talk2biomodels/tests/test_get_annotation.py +171 -0
  16. aiagents4pharma/talk2biomodels/tests/test_getmodelinfo.py +26 -0
  17. aiagents4pharma/talk2biomodels/tests/test_integration.py +126 -0
  18. aiagents4pharma/talk2biomodels/tests/test_param_scan.py +68 -0
  19. aiagents4pharma/talk2biomodels/tests/test_search_models.py +28 -0
  20. aiagents4pharma/talk2biomodels/tests/test_simulate_model.py +39 -0
  21. aiagents4pharma/talk2biomodels/tests/test_steady_state.py +90 -0
  22. aiagents4pharma/talk2biomodels/tools/__init__.py +1 -0
  23. aiagents4pharma/talk2biomodels/tools/ask_question.py +29 -8
  24. aiagents4pharma/talk2biomodels/tools/get_annotation.py +304 -0
  25. aiagents4pharma/talk2biomodels/tools/load_arguments.py +114 -0
  26. aiagents4pharma/talk2biomodels/tools/parameter_scan.py +91 -96
  27. aiagents4pharma/talk2biomodels/tools/simulate_model.py +14 -81
  28. aiagents4pharma/talk2biomodels/tools/steady_state.py +48 -89
  29. {aiagents4pharma-1.13.1.dist-info → aiagents4pharma-1.14.1.dist-info}/METADATA +1 -1
  30. {aiagents4pharma-1.13.1.dist-info → aiagents4pharma-1.14.1.dist-info}/RECORD +33 -17
  31. aiagents4pharma/talk2biomodels/tests/test_langgraph.py +0 -384
  32. {aiagents4pharma-1.13.1.dist-info → aiagents4pharma-1.14.1.dist-info}/LICENSE +0 -0
  33. {aiagents4pharma-1.13.1.dist-info → aiagents4pharma-1.14.1.dist-info}/WHEEL +0 -0
  34. {aiagents4pharma-1.13.1.dist-info → aiagents4pharma-1.14.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,126 @@
1
+ '''
2
+ Test cases for Talk2Biomodels.
3
+ '''
4
+
5
+ import pandas as pd
6
+ from langchain_core.messages import HumanMessage, ToolMessage
7
+ from ..agents.t2b_agent import get_app
8
+
9
+ def test_integration():
10
+ '''
11
+ Test the integration of the tools.
12
+ '''
13
+ unique_id = 1234567
14
+ app = get_app(unique_id)
15
+ config = {"configurable": {"thread_id": unique_id}}
16
+ app.update_state(config, {"llm_model": "gpt-4o-mini"})
17
+ # ##########################################
18
+ # ## Test simulate_model tool
19
+ # ##########################################
20
+ prompt = '''Simulate the model 537 for 100 hours and intervals
21
+ 100 with an initial concentration of `DoseQ2W`
22
+ set to 300 and `Dose` set to 0. Reset the concentration
23
+ of `Ab{serum}` to 100 every 25 hours.'''
24
+ # Test the tool get_modelinfo
25
+ response = app.invoke(
26
+ {"messages": [HumanMessage(content=prompt)]},
27
+ config=config
28
+ )
29
+ assistant_msg = response["messages"][-1].content
30
+ print (assistant_msg)
31
+ # Check if the assistant message is a string
32
+ assert isinstance(assistant_msg, str)
33
+ ##########################################
34
+ # Test ask_question tool when simulation
35
+ # results are available
36
+ ##########################################
37
+ # Update state
38
+ app.update_state(config, {"llm_model": "gpt-4o-mini"})
39
+ prompt = """What is the concentration of CRP in serum after 100 hours?
40
+ Round off the value to 2 decimal places."""
41
+ # Test the tool get_modelinfo
42
+ response = app.invoke(
43
+ {"messages": [HumanMessage(content=prompt)]},
44
+ config=config
45
+ )
46
+ assistant_msg = response["messages"][-1].content
47
+ # print (assistant_msg)
48
+ # Check if the assistant message is a string
49
+ assert '211' in assistant_msg
50
+
51
+ ##########################################
52
+ # Test custom_plotter tool when the
53
+ # simulation results are available
54
+ ##########################################
55
+ prompt = "Plot only CRP related species."
56
+
57
+ # Update state
58
+ app.update_state(config, {"llm_model": "gpt-4o-mini"}
59
+ )
60
+ # Test the tool get_modelinfo
61
+ response = app.invoke(
62
+ {"messages": [HumanMessage(content=prompt)]},
63
+ config=config
64
+ )
65
+ assistant_msg = response["messages"][-1].content
66
+ current_state = app.get_state(config)
67
+ # Get the messages from the current state
68
+ # and reverse the order
69
+ reversed_messages = current_state.values["messages"][::-1]
70
+ # Loop through the reversed messages
71
+ # until a ToolMessage is found.
72
+ expected_header = ['Time', 'CRP{serum}', 'CRPExtracellular']
73
+ expected_header += ['CRP Suppression (%)', 'CRP (% of baseline)']
74
+ expected_header += ['CRP{liver}']
75
+ predicted_artifact = []
76
+ for msg in reversed_messages:
77
+ if isinstance(msg, ToolMessage):
78
+ # Work on the message if it is a ToolMessage
79
+ # These may contain additional visuals that
80
+ # need to be displayed to the user.
81
+ if msg.name == "custom_plotter":
82
+ predicted_artifact = msg.artifact
83
+ break
84
+ # Convert the artifact into a pandas dataframe
85
+ # for easy comparison
86
+ df = pd.DataFrame(predicted_artifact)
87
+ # Extract the headers from the dataframe
88
+ predicted_header = df.columns.tolist()
89
+ # Check if the header is in the expected_header
90
+ # assert expected_header in predicted_artifact
91
+ assert set(expected_header).issubset(set(predicted_header))
92
+ ##########################################
93
+ # Test custom_plotter tool when the
94
+ # simulation results are available but
95
+ # the species is not available
96
+ ##########################################
97
+ prompt = """Make a custom plot showing the
98
+ concentration of the species `TP53` over
99
+ time. Do not show any other species."""
100
+ # Update state
101
+ app.update_state(config, {"llm_model": "gpt-4o-mini"}
102
+ )
103
+ # Test the tool get_modelinfo
104
+ response = app.invoke(
105
+ {"messages": [HumanMessage(content=prompt)]},
106
+ config=config
107
+ )
108
+ assistant_msg = response["messages"][-1].content
109
+ # print (response["messages"])
110
+ current_state = app.get_state(config)
111
+ # Get the messages from the current state
112
+ # and reverse the order
113
+ reversed_messages = current_state.values["messages"][::-1]
114
+ # Loop through the reversed messages until a
115
+ # ToolMessage is found.
116
+ predicted_artifact = []
117
+ for msg in reversed_messages:
118
+ if isinstance(msg, ToolMessage):
119
+ # Work on the message if it is a ToolMessage
120
+ # These may contain additional visuals that
121
+ # need to be displayed to the user.
122
+ if msg.name == "custom_plotter":
123
+ predicted_artifact = msg.artifact
124
+ break
125
+ # Check if the the predicted artifact is `None`
126
+ assert predicted_artifact is None
@@ -0,0 +1,68 @@
1
+ '''
2
+ Test cases for Talk2Biomodels parameter scan tool.
3
+ '''
4
+
5
+ import pandas as pd
6
+ from langchain_core.messages import HumanMessage, ToolMessage
7
+ from ..agents.t2b_agent import get_app
8
+
9
+ def test_param_scan_tool():
10
+ '''
11
+ In this test, we will test the parameter_scan tool.
12
+ We will prompt it to scan the parameter `kIL6RBind`
13
+ from 1 to 100 in steps of 10, record the changes
14
+ in the concentration of the species `Ab{serum}` in
15
+ model 537.
16
+
17
+ We will pass the inaccuarate parameter (`KIL6Rbind`)
18
+ and species names (just `Ab`) to the tool to test
19
+ if it can deal with it.
20
+
21
+ We expect the agent to first invoke the parameter_scan
22
+ tool and raise an error. It will then invoke another
23
+ tool get_modelinfo to get the correct parameter
24
+ and species names. Finally, the agent will reinvoke
25
+ the parameter_scan tool with the correct parameter
26
+ and species names.
27
+
28
+ '''
29
+ unique_id = 1234
30
+ app = get_app(unique_id)
31
+ config = {"configurable": {"thread_id": unique_id}}
32
+ app.update_state(config, {"llm_model": "gpt-4o-mini"})
33
+ prompt = """How will the value of Ab in serum in model 537 change
34
+ if the param kIL6Rbind is varied from 1 to 100 in steps of 10?
35
+ Set the initial `DoseQ2W` concentration to 300. Assume
36
+ that the model is simulated for 2016 hours with an interval of 50."""
37
+ # Invoke the agent
38
+ app.invoke(
39
+ {"messages": [HumanMessage(content=prompt)]},
40
+ config=config
41
+ )
42
+ current_state = app.get_state(config)
43
+ reversed_messages = current_state.values["messages"][::-1]
44
+ # Loop through the reversed messages until a
45
+ # ToolMessage is found.
46
+ df = pd.DataFrame(columns=['name', 'status', 'content'])
47
+ names = []
48
+ statuses = []
49
+ contents = []
50
+ for msg in reversed_messages:
51
+ # Assert that the message is a ToolMessage
52
+ # and its status is "error"
53
+ if not isinstance(msg, ToolMessage):
54
+ continue
55
+ names.append(msg.name)
56
+ statuses.append(msg.status)
57
+ contents.append(msg.content)
58
+ df = pd.DataFrame({'name': names, 'status': statuses, 'content': contents})
59
+ # print (df)
60
+ assert any((df["status"] == "error") &
61
+ (df["name"] == "parameter_scan") &
62
+ (df["content"].str.startswith(
63
+ "Error: ValueError('Invalid species or parameter name:")))
64
+ assert any((df["status"] == "success") &
65
+ (df["name"] == "parameter_scan") &
66
+ (df["content"].str.startswith("Parameter scan results of")))
67
+ assert any((df["status"] == "success") &
68
+ (df["name"] == "get_modelinfo"))
@@ -0,0 +1,28 @@
1
+ '''
2
+ Test cases for Talk2Biomodels search models tool.
3
+ '''
4
+
5
+ from langchain_core.messages import HumanMessage
6
+ from ..agents.t2b_agent import get_app
7
+
8
+ def test_search_models_tool():
9
+ '''
10
+ Test the search_models tool.
11
+ '''
12
+ unique_id = 12345
13
+ app = get_app(unique_id)
14
+ config = {"configurable": {"thread_id": unique_id}}
15
+ # Update state
16
+ app.update_state(config, {"llm_model": "gpt-4o-mini"})
17
+ prompt = "Search for models on Crohn's disease."
18
+ # Test the tool get_modelinfo
19
+ response = app.invoke(
20
+ {"messages": [HumanMessage(content=prompt)]},
21
+ config=config
22
+ )
23
+ assistant_msg = response["messages"][-1].content
24
+ # Check if the assistant message is a string
25
+ assert isinstance(assistant_msg, str)
26
+ # Check if the assistant message contains the
27
+ # biomodel id BIO0000000537
28
+ assert "BIOMD0000000537" in assistant_msg
@@ -0,0 +1,39 @@
1
+ '''
2
+ Test cases for Talk2Biomodels.
3
+ '''
4
+
5
+ from langchain_core.messages import HumanMessage
6
+ from ..agents.t2b_agent import get_app
7
+
8
+ def test_simulate_model_tool():
9
+ '''
10
+ Test the simulate_model tool when simulating
11
+ multiple models.
12
+ '''
13
+ unique_id = 123
14
+ app = get_app(unique_id)
15
+ config = {"configurable": {"thread_id": unique_id}}
16
+ app.update_state(config, {"llm_model": "gpt-4o-mini"})
17
+ # Upload a model to the state
18
+ app.update_state(config,
19
+ {"sbml_file_path": ["aiagents4pharma/talk2biomodels/tests/BIOMD0000000449_url.xml"]})
20
+ prompt = "Simulate model 64 and the uploaded model"
21
+ # Invoke the agent
22
+ app.invoke(
23
+ {"messages": [HumanMessage(content=prompt)]},
24
+ config=config
25
+ )
26
+ current_state = app.get_state(config)
27
+ dic_simulated_data = current_state.values["dic_simulated_data"]
28
+ # Check if the dic_simulated_data is a list
29
+ assert isinstance(dic_simulated_data, list)
30
+ # Check if the length of the dic_simulated_data is 2
31
+ assert len(dic_simulated_data) == 2
32
+ # Check if the source of the first model is 64
33
+ assert dic_simulated_data[0]['source'] == 64
34
+ # Check if the source of the second model is upload
35
+ assert dic_simulated_data[1]['source'] == "upload"
36
+ # Check if the data of the first model contains
37
+ assert '1,3-bisphosphoglycerate' in dic_simulated_data[0]['data']
38
+ # Check if the data of the second model contains
39
+ assert 'mTORC2' in dic_simulated_data[1]['data']
@@ -0,0 +1,90 @@
1
+ '''
2
+ Test cases for Talk2Biomodels steady state tool.
3
+ '''
4
+
5
+ from langchain_core.messages import HumanMessage, ToolMessage
6
+ from ..agents.t2b_agent import get_app
7
+
8
+ def test_steady_state_tool():
9
+ '''
10
+ Test the steady_state tool.
11
+ '''
12
+ unique_id = 123
13
+ app = get_app(unique_id)
14
+ config = {"configurable": {"thread_id": unique_id}}
15
+ app.update_state(config, {"llm_model": "gpt-4o-mini"})
16
+ #########################################################
17
+ # In this case, we will test if the tool returns an error
18
+ # when the model does not achieve a steady state. The tool
19
+ # status should be "error".
20
+ prompt = """Run a steady state analysis of model 537."""
21
+ # Invoke the agent
22
+ app.invoke(
23
+ {"messages": [HumanMessage(content=prompt)]},
24
+ config=config
25
+ )
26
+ current_state = app.get_state(config)
27
+ reversed_messages = current_state.values["messages"][::-1]
28
+ tool_msg_status = None
29
+ for msg in reversed_messages:
30
+ # Assert that the status of the
31
+ # ToolMessage is "error"
32
+ if isinstance(msg, ToolMessage):
33
+ # print (msg)
34
+ tool_msg_status = msg.status
35
+ break
36
+ assert tool_msg_status == "error"
37
+ #########################################################
38
+ # In this case, we will test if the tool is indeed invoked
39
+ # successfully
40
+ prompt = """Run a steady state analysis of model 64.
41
+ Set the initial concentration of `Pyruvate` to 0.2. The
42
+ concentration of `NAD` resets to 100 every 2 time units."""
43
+ # Invoke the agent
44
+ app.invoke(
45
+ {"messages": [HumanMessage(content=prompt)]},
46
+ config=config
47
+ )
48
+ # Loop through the reversed messages until a
49
+ # ToolMessage is found.
50
+ current_state = app.get_state(config)
51
+ reversed_messages = current_state.values["messages"][::-1]
52
+ steady_state_invoked = False
53
+ for msg in reversed_messages:
54
+ # Assert that the message is a ToolMessage
55
+ # and its status is "error"
56
+ if isinstance(msg, ToolMessage):
57
+ print (msg)
58
+ if msg.name == "steady_state" and msg.status != "error":
59
+ steady_state_invoked = True
60
+ break
61
+ assert steady_state_invoked
62
+ #########################################################
63
+ # In this case, we will test if the `ask_question` tool is
64
+ # invoked upon asking a question about the already generated
65
+ # steady state results
66
+ prompt = """What is the Phosphoenolpyruvate concentration
67
+ at the steady state? Show only the concentration, rounded
68
+ to 2 decimal places. For example, if the concentration is
69
+ 0.123456, your response should be `0.12`. Do not return
70
+ any other information."""
71
+ # Invoke the agent
72
+ response = app.invoke(
73
+ {"messages": [HumanMessage(content=prompt)]},
74
+ config=config
75
+ )
76
+ assistant_msg = response["messages"][-1].content
77
+ current_state = app.get_state(config)
78
+ reversed_messages = current_state.values["messages"][::-1]
79
+ # Loop through the reversed messages until a
80
+ # ToolMessage is found.
81
+ ask_questool_invoked = False
82
+ for msg in reversed_messages:
83
+ # Assert that the message is a ToolMessage
84
+ # and its status is "error"
85
+ if isinstance(msg, ToolMessage):
86
+ if msg.name == "ask_question":
87
+ ask_questool_invoked = True
88
+ break
89
+ assert ask_questool_invoked
90
+ assert "0.06" in assistant_msg
@@ -9,3 +9,4 @@ from . import get_modelinfo
9
9
  from . import parameter_scan
10
10
  from . import steady_state
11
11
  from . import load_biomodel
12
+ from . import get_annotation
@@ -6,10 +6,11 @@ Tool for asking a question about the simulation results.
6
6
 
7
7
  import logging
8
8
  from typing import Type, Annotated, Literal
9
+ import hydra
10
+ import basico
9
11
  import pandas as pd
10
12
  from pydantic import BaseModel, Field
11
13
  from langchain_core.tools.base import BaseTool
12
- from langchain.agents.agent_types import AgentType
13
14
  from langchain_experimental.agents import create_pandas_dataframe_agent
14
15
  from langchain_openai import ChatOpenAI
15
16
  from langgraph.prebuilt import InjectedState
@@ -64,31 +65,51 @@ class AskQuestionTool(BaseTool):
64
65
  question,
65
66
  question_context,
66
67
  experiment_name)
67
- # print (f'Calling ask_question tool {question}, {question_context}, {experiment_name}')
68
+ # Load hydra configuration
69
+ with hydra.initialize(version_base=None, config_path="../../configs"):
70
+ cfg = hydra.compose(config_name='config',
71
+ overrides=['talk2biomodels/tools/ask_question=default'])
72
+ cfg = cfg.talk2biomodels.tools.ask_question
73
+ # Get the context of the question
74
+ # and based on the context, get the data
75
+ # and prompt content to ask the question
68
76
  if question_context == "steady_state":
69
77
  dic_context = state["dic_steady_state_data"]
78
+ prompt_content = cfg.steady_state_prompt
70
79
  else:
71
80
  dic_context = state["dic_simulated_data"]
81
+ prompt_content = cfg.simulation_prompt
82
+ # Extract the
72
83
  dic_data = {}
73
84
  for data in dic_context:
74
85
  for key in data:
75
86
  if key not in dic_data:
76
87
  dic_data[key] = []
77
88
  dic_data[key] += [data[key]]
78
- # print (dic_data)
89
+ # Create a pandas dataframe of the data
79
90
  df_data = pd.DataFrame.from_dict(dic_data)
91
+ # Extract the data for the experiment
92
+ # matching the experiment name
80
93
  df = pd.DataFrame(
81
94
  df_data[df_data['name'] == experiment_name]['data'].iloc[0]
82
95
  )
83
- prompt_content = None
84
- # if run_manager and 'prompt' in run_manager.metadata:
85
- # prompt_content = run_manager.metadata['prompt']
86
- # Create a pandas dataframe agent with OpenAI
96
+ logger.log(logging.INFO, "Shape of the dataframe: %s", df.shape)
97
+ # # Extract the model units
98
+ # model_units = basico.model_info.get_model_units()
99
+ # Update the prompt content with the model units
100
+ prompt_content += "Following are the model units:\n"
101
+ prompt_content += f"{basico.model_info.get_model_units()}\n\n"
102
+ # Create a pandas dataframe agent
87
103
  df_agent = create_pandas_dataframe_agent(
88
104
  ChatOpenAI(model=state['llm_model']),
89
105
  allow_dangerous_code=True,
90
- agent_type=AgentType.OPENAI_FUNCTIONS,
106
+ agent_type='tool-calling',
91
107
  df=df,
108
+ max_iterations=5,
109
+ include_df_in_prompt=True,
110
+ number_of_head_rows=df.shape[0],
111
+ verbose=True,
92
112
  prefix=prompt_content)
113
+ # Invoke the agent with the question
93
114
  llm_result = df_agent.invoke(question)
94
115
  return llm_result["output"]