aiagents4pharma 1.8.1__tar.gz → 1.8.3__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.
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/PKG-INFO +3 -3
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/__init__.py +1 -0
- aiagents4pharma-1.8.3/aiagents4pharma/configs/__init__.py +5 -0
- aiagents4pharma-1.8.3/aiagents4pharma/configs/config.yaml +3 -0
- aiagents4pharma-1.8.3/aiagents4pharma/configs/talk2biomodels/__init__.py +5 -0
- aiagents4pharma-1.8.3/aiagents4pharma/configs/talk2biomodels/agents/__init__.py +5 -0
- aiagents4pharma-1.8.3/aiagents4pharma/configs/talk2biomodels/agents/t2b_agent/__init__.py +3 -0
- aiagents4pharma-1.8.3/aiagents4pharma/configs/talk2biomodels/agents/t2b_agent/default.yaml +6 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2biomodels/agents/__init__.py +5 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2biomodels/agents/t2b_agent.py +93 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2biomodels/states/__init__.py +5 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2biomodels/states/state_talk2biomodels.py +24 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2biomodels/tests/__init__.py +3 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2biomodels/tests/test_basico_model.py +55 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2biomodels/tests/test_langgraph.py +189 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2biomodels/tests/test_sys_bio_model.py +57 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2cells/tests/scp_agent/test_scp_agent.py +23 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2cells/tools/scp_agent/__init__.py +6 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2cells/tools/scp_agent/display_studies.py +25 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2cells/tools/scp_agent/search_studies.py +79 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2knowledgegraphs/tests/__init__.py +0 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_biobridge_primekg.py +242 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_dataset.py +29 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_primekg.py +73 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_starkqa_primekg.py +116 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_embeddings.py +47 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_huggingface.py +45 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_sentencetransformer.py +40 -0
- aiagents4pharma-1.8.3/aiagents4pharma/talk2knowledgegraphs/utils/__init__.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma.egg-info/PKG-INFO +3 -3
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma.egg-info/SOURCES.txt +27 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma.egg-info/requires.txt +2 -2
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/pyproject.toml +11 -18
- aiagents4pharma-1.8.3/release_version.txt +1 -0
- aiagents4pharma-1.8.1/release_version.txt +0 -1
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/LICENSE +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/README.md +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2biomodels/__init__.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2biomodels/models/__init__.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2biomodels/models/basico_model.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2biomodels/models/sys_bio_model.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2biomodels/tools/__init__.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2biomodels/tools/ask_question.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2biomodels/tools/custom_plotter.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2biomodels/tools/get_modelinfo.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2biomodels/tools/load_biomodel.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2biomodels/tools/search_models.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2biomodels/tools/simulate_model.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2cells/__init__.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2cells/agents/__init__.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2cells/agents/scp_agent.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2cells/states/__init__.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2cells/states/state_talk2cells.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2cells/tools/__init__.py +0 -0
- {aiagents4pharma-1.8.1/aiagents4pharma/talk2knowledgegraphs/utils → aiagents4pharma-1.8.3/aiagents4pharma/talk2competitors}/__init__.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2knowledgegraphs/__init__.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2knowledgegraphs/datasets/__init__.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2knowledgegraphs/datasets/biobridge_primekg.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2knowledgegraphs/datasets/dataset.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2knowledgegraphs/datasets/primekg.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2knowledgegraphs/datasets/starkqa_primekg.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2knowledgegraphs/utils/embeddings/__init__.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2knowledgegraphs/utils/embeddings/embeddings.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2knowledgegraphs/utils/embeddings/huggingface.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2knowledgegraphs/utils/embeddings/sentence_transformer.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma/talk2knowledgegraphs/utils/kg_utils.py +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma.egg-info/dependency_links.txt +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/aiagents4pharma.egg-info/top_level.txt +0 -0
- {aiagents4pharma-1.8.1 → aiagents4pharma-1.8.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: aiagents4pharma
|
3
|
-
Version: 1.8.
|
3
|
+
Version: 1.8.3
|
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
|
@@ -17,10 +17,10 @@ Requires-Dist: hydra-core==1.3.2
|
|
17
17
|
Requires-Dist: joblib==1.4.2
|
18
18
|
Requires-Dist: langchain==0.3.7
|
19
19
|
Requires-Dist: langchain-community==0.3.5
|
20
|
-
Requires-Dist: langchain-core==0.3.
|
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: langgraph==0.2.
|
23
|
+
Requires-Dist: langgraph==0.2.66
|
24
24
|
Requires-Dist: matplotlib==3.9.2
|
25
25
|
Requires-Dist: openai==1.59.4
|
26
26
|
Requires-Dist: pandas==2.2.3
|
@@ -0,0 +1,93 @@
|
|
1
|
+
#/usr/bin/env python3
|
2
|
+
|
3
|
+
'''
|
4
|
+
This is the agent file for the Talk2BioModels agent.
|
5
|
+
'''
|
6
|
+
|
7
|
+
import logging
|
8
|
+
from typing import Annotated
|
9
|
+
import hydra
|
10
|
+
from langchain_openai import ChatOpenAI
|
11
|
+
from langgraph.checkpoint.memory import MemorySaver
|
12
|
+
from langgraph.graph import START, StateGraph
|
13
|
+
from langgraph.prebuilt import create_react_agent, ToolNode, InjectedState
|
14
|
+
from ..tools.search_models import SearchModelsTool
|
15
|
+
from ..tools.get_modelinfo import GetModelInfoTool
|
16
|
+
from ..tools.simulate_model import SimulateModelTool
|
17
|
+
from ..tools.custom_plotter import CustomPlotterTool
|
18
|
+
from ..tools.ask_question import AskQuestionTool
|
19
|
+
from ..states.state_talk2biomodels import Talk2Biomodels
|
20
|
+
|
21
|
+
# Initialize logger
|
22
|
+
logging.basicConfig(level=logging.INFO)
|
23
|
+
logger = logging.getLogger(__name__)
|
24
|
+
|
25
|
+
def get_app(uniq_id, llm_model='gpt-4o-mini'):
|
26
|
+
'''
|
27
|
+
This function returns the langraph app.
|
28
|
+
'''
|
29
|
+
def agent_t2b_node(state: Annotated[dict, InjectedState]):
|
30
|
+
'''
|
31
|
+
This function calls the model.
|
32
|
+
'''
|
33
|
+
logger.log(logging.INFO, "Calling t2b_agent node with thread_id %s", uniq_id)
|
34
|
+
response = model.invoke(state, {"configurable": {"thread_id": uniq_id}})
|
35
|
+
return response
|
36
|
+
|
37
|
+
# Define the tools
|
38
|
+
simulate_model = SimulateModelTool()
|
39
|
+
custom_plotter = CustomPlotterTool()
|
40
|
+
ask_question = AskQuestionTool()
|
41
|
+
search_model = SearchModelsTool()
|
42
|
+
get_modelinfo = GetModelInfoTool()
|
43
|
+
tools = ToolNode([
|
44
|
+
simulate_model,
|
45
|
+
ask_question,
|
46
|
+
custom_plotter,
|
47
|
+
search_model,
|
48
|
+
get_modelinfo
|
49
|
+
])
|
50
|
+
|
51
|
+
# Define the model
|
52
|
+
llm = ChatOpenAI(model=llm_model, temperature=0)
|
53
|
+
# Load hydra configuration
|
54
|
+
logger.log(logging.INFO, "Load Hydra configuration for Talk2BioModels agent.")
|
55
|
+
with hydra.initialize(version_base=None, config_path="../../configs"):
|
56
|
+
cfg = hydra.compose(config_name='config',
|
57
|
+
overrides=['talk2biomodels/agents/t2b_agent=default'])
|
58
|
+
cfg = cfg.talk2biomodels.agents.t2b_agent
|
59
|
+
logger.log(logging.INFO, "state_modifier: %s", cfg.state_modifier)
|
60
|
+
# Create the agent
|
61
|
+
model = create_react_agent(
|
62
|
+
llm,
|
63
|
+
tools=tools,
|
64
|
+
state_schema=Talk2Biomodels,
|
65
|
+
state_modifier=cfg.state_modifier,
|
66
|
+
checkpointer=MemorySaver()
|
67
|
+
)
|
68
|
+
|
69
|
+
# Define a new graph
|
70
|
+
workflow = StateGraph(Talk2Biomodels)
|
71
|
+
|
72
|
+
# Define the two nodes we will cycle between
|
73
|
+
workflow.add_node("agent_t2b", agent_t2b_node)
|
74
|
+
|
75
|
+
# Set the entrypoint as the first node
|
76
|
+
# This means that this node is the first one called
|
77
|
+
workflow.add_edge(START, "agent_t2b")
|
78
|
+
|
79
|
+
# Initialize memory to persist state between graph runs
|
80
|
+
checkpointer = MemorySaver()
|
81
|
+
|
82
|
+
# Finally, we compile it!
|
83
|
+
# This compiles it into a LangChain Runnable,
|
84
|
+
# meaning you can use it as you would any other runnable.
|
85
|
+
# Note that we're (optionally) passing the memory
|
86
|
+
# when compiling the graph
|
87
|
+
app = workflow.compile(checkpointer=checkpointer)
|
88
|
+
logger.log(logging.INFO,
|
89
|
+
"Compiled the graph with thread_id %s and llm_model %s",
|
90
|
+
uniq_id,
|
91
|
+
llm_model)
|
92
|
+
|
93
|
+
return app
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
'''
|
4
|
+
This is the state file for the Talk2BioModels agent.
|
5
|
+
'''
|
6
|
+
|
7
|
+
from typing import Annotated
|
8
|
+
import operator
|
9
|
+
from langgraph.prebuilt.chat_agent_executor import AgentState
|
10
|
+
|
11
|
+
class Talk2Biomodels(AgentState):
|
12
|
+
"""
|
13
|
+
The state for the Talk2BioModels agent.
|
14
|
+
"""
|
15
|
+
model_id: Annotated[list, operator.add]
|
16
|
+
# sbml_file_path: str
|
17
|
+
# A StateGraph may receive a concurrent updates
|
18
|
+
# which is not supported by the StateGraph.
|
19
|
+
# Therefore, we need to use Annotated to specify
|
20
|
+
# the operator for the sbml_file_path field.
|
21
|
+
# https://langchain-ai.github.io/langgraph/troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE/
|
22
|
+
sbml_file_path: Annotated[list, operator.add]
|
23
|
+
dic_simulated_data: dict
|
24
|
+
llm_model: str
|
@@ -0,0 +1,55 @@
|
|
1
|
+
'''
|
2
|
+
A test BasicoModel class for pytest unit testing.
|
3
|
+
'''
|
4
|
+
|
5
|
+
import pandas as pd
|
6
|
+
import pytest
|
7
|
+
import basico
|
8
|
+
from ..models.basico_model import BasicoModel
|
9
|
+
|
10
|
+
@pytest.fixture(name="model")
|
11
|
+
def model_fixture():
|
12
|
+
"""
|
13
|
+
A fixture for the BasicoModel class.
|
14
|
+
"""
|
15
|
+
return BasicoModel(biomodel_id=64, species={"Pyruvate": 100}, duration=2, interval=2)
|
16
|
+
|
17
|
+
def test_with_biomodel_id(model):
|
18
|
+
"""
|
19
|
+
Test initialization of BasicoModel with biomodel_id.
|
20
|
+
"""
|
21
|
+
assert model.biomodel_id == 64
|
22
|
+
# 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)
|
29
|
+
assert model.description == basico.biomodels.get_model_info(model.biomodel_id)["description"]
|
30
|
+
|
31
|
+
def test_with_sbml_file():
|
32
|
+
"""
|
33
|
+
Test initialization of BasicoModel with sbml_file_path.
|
34
|
+
"""
|
35
|
+
model_object = BasicoModel(sbml_file_path="./BIOMD0000000064_url.xml")
|
36
|
+
assert model_object.sbml_file_path == "./BIOMD0000000064_url.xml"
|
37
|
+
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
|
+
|
41
|
+
def test_check_biomodel_id_or_sbml_file_path():
|
42
|
+
'''
|
43
|
+
Test the check_biomodel_id_or_sbml_file_path method of the BioModel class.
|
44
|
+
'''
|
45
|
+
with pytest.raises(ValueError):
|
46
|
+
BasicoModel(species={"Pyruvate": 100}, duration=2, interval=2)
|
47
|
+
|
48
|
+
def test_get_model_metadata():
|
49
|
+
"""
|
50
|
+
Test the get_model_metadata method of the BasicoModel class.
|
51
|
+
"""
|
52
|
+
model = BasicoModel(biomodel_id=64)
|
53
|
+
metadata = model.get_model_metadata()
|
54
|
+
assert metadata["Model Type"] == "SBML Model (COPASI)"
|
55
|
+
assert metadata["Parameter Count"] == len(basico.get_parameters())
|
@@ -0,0 +1,189 @@
|
|
1
|
+
'''
|
2
|
+
Test cases
|
3
|
+
'''
|
4
|
+
|
5
|
+
from langchain_core.messages import HumanMessage, ToolMessage
|
6
|
+
from ..agents.t2b_agent import get_app
|
7
|
+
|
8
|
+
def test_get_modelinfo_tool():
|
9
|
+
'''
|
10
|
+
Test the get_modelinfo 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,{"sbml_file_path": ["BIOMD0000000537.xml"]})
|
17
|
+
prompt = "Extract all relevant information from the uploaded model."
|
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
|
+
|
27
|
+
def test_search_models_tool():
|
28
|
+
'''
|
29
|
+
Test the search_models tool.
|
30
|
+
'''
|
31
|
+
unique_id = 12345
|
32
|
+
app = get_app(unique_id)
|
33
|
+
config = {"configurable": {"thread_id": unique_id}}
|
34
|
+
# Update state
|
35
|
+
app.update_state(config, {"llm_model": "gpt-4o-mini"})
|
36
|
+
prompt = "Search for models on Crohn's disease."
|
37
|
+
# Test the tool get_modelinfo
|
38
|
+
response = app.invoke(
|
39
|
+
{"messages": [HumanMessage(content=prompt)]},
|
40
|
+
config=config
|
41
|
+
)
|
42
|
+
assistant_msg = response["messages"][-1].content
|
43
|
+
# Check if the assistant message is a string
|
44
|
+
assert isinstance(assistant_msg, str)
|
45
|
+
# Check if the assistant message contains the
|
46
|
+
# biomodel id BIO0000000537
|
47
|
+
assert "BIOMD0000000537" in assistant_msg
|
48
|
+
|
49
|
+
def test_ask_question_tool():
|
50
|
+
'''
|
51
|
+
Test the ask_question tool without the simulation results.
|
52
|
+
'''
|
53
|
+
unique_id = 12345
|
54
|
+
app = get_app(unique_id, llm_model='gpt-4o-mini')
|
55
|
+
config = {"configurable": {"thread_id": unique_id}}
|
56
|
+
|
57
|
+
##########################################
|
58
|
+
# Test ask_question tool when simulation
|
59
|
+
# results are not available
|
60
|
+
##########################################
|
61
|
+
# Update state
|
62
|
+
app.update_state(config, {"llm_model": "gpt-4o-mini"})
|
63
|
+
prompt = "Call the ask_question tool to answer the "
|
64
|
+
prompt += "question: What is the concentration of CRP "
|
65
|
+
prompt += "in serum at 1000 hours?"
|
66
|
+
|
67
|
+
# Test the tool get_modelinfo
|
68
|
+
response = app.invoke(
|
69
|
+
{"messages": [HumanMessage(content=prompt)]},
|
70
|
+
config=config
|
71
|
+
)
|
72
|
+
assistant_msg = response["messages"][-1].content
|
73
|
+
# Check if the assistant message is a string
|
74
|
+
assert isinstance(assistant_msg, str)
|
75
|
+
|
76
|
+
def test_simulate_model_tool():
|
77
|
+
'''
|
78
|
+
Test the simulate_model tool.
|
79
|
+
'''
|
80
|
+
unique_id = 123
|
81
|
+
app = get_app(unique_id)
|
82
|
+
config = {"configurable": {"thread_id": unique_id}}
|
83
|
+
app.update_state(config, {"llm_model": "gpt-4o-mini"})
|
84
|
+
# ##########################################
|
85
|
+
# ## Test simulate_model tool
|
86
|
+
# ##########################################
|
87
|
+
prompt = "Simulate the model 537 for 2016 hours and intervals"
|
88
|
+
prompt += " 2016 with an initial concentration of `DoseQ2W` "
|
89
|
+
prompt += "set to 300 and `Dose` set to 0. Reset the concentration"
|
90
|
+
prompt += " of `NAD` to 100 every 500 hours."
|
91
|
+
# Test the tool get_modelinfo
|
92
|
+
response = app.invoke(
|
93
|
+
{"messages": [HumanMessage(content=prompt)]},
|
94
|
+
config=config
|
95
|
+
)
|
96
|
+
assistant_msg = response["messages"][-1].content
|
97
|
+
print (assistant_msg)
|
98
|
+
# Check if the assistant message is a string
|
99
|
+
assert isinstance(assistant_msg, str)
|
100
|
+
##########################################
|
101
|
+
# Test ask_question tool when simulation
|
102
|
+
# results are available
|
103
|
+
##########################################
|
104
|
+
# Update state
|
105
|
+
app.update_state(config, {"llm_model": "gpt-4o-mini"})
|
106
|
+
prompt = "What is the concentration of CRP in serum at 1000 hours? "
|
107
|
+
# prompt += "Show only the concentration, rounded to 1 decimal place."
|
108
|
+
# prompt += "For example, if the concentration is 0.123456, "
|
109
|
+
# prompt += "your response should be `0.1`. Do not return any other information."
|
110
|
+
# Test the tool get_modelinfo
|
111
|
+
response = app.invoke(
|
112
|
+
{"messages": [HumanMessage(content=prompt)]},
|
113
|
+
config=config
|
114
|
+
)
|
115
|
+
assistant_msg = response["messages"][-1].content
|
116
|
+
# print (assistant_msg)
|
117
|
+
# Check if the assistant message is a string
|
118
|
+
assert "1.7" in assistant_msg
|
119
|
+
|
120
|
+
##########################################
|
121
|
+
# Test custom_plotter tool when the
|
122
|
+
# simulation results are available
|
123
|
+
##########################################
|
124
|
+
prompt = "Plot only CRP related species."
|
125
|
+
|
126
|
+
# Update state
|
127
|
+
app.update_state(config, {"llm_model": "gpt-4o-mini"}
|
128
|
+
)
|
129
|
+
# Test the tool get_modelinfo
|
130
|
+
response = app.invoke(
|
131
|
+
{"messages": [HumanMessage(content=prompt)]},
|
132
|
+
config=config
|
133
|
+
)
|
134
|
+
assistant_msg = response["messages"][-1].content
|
135
|
+
current_state = app.get_state(config)
|
136
|
+
# Get the messages from the current state
|
137
|
+
# and reverse the order
|
138
|
+
reversed_messages = current_state.values["messages"][::-1]
|
139
|
+
# Loop through the reversed messages
|
140
|
+
# until a ToolMessage is found.
|
141
|
+
expected_artifact = ['CRP[serum]', 'CRPExtracellular']
|
142
|
+
expected_artifact += ['CRP Suppression (%)', 'CRP (% of baseline)']
|
143
|
+
expected_artifact += ['CRP[liver]']
|
144
|
+
predicted_artifact = []
|
145
|
+
for msg in reversed_messages:
|
146
|
+
if isinstance(msg, ToolMessage):
|
147
|
+
# Work on the message if it is a ToolMessage
|
148
|
+
# These may contain additional visuals that
|
149
|
+
# need to be displayed to the user.
|
150
|
+
if msg.name == "custom_plotter":
|
151
|
+
predicted_artifact = msg.artifact
|
152
|
+
break
|
153
|
+
# Check if the two artifacts are equal
|
154
|
+
# assert expected_artifact in predicted_artifact
|
155
|
+
assert set(expected_artifact).issubset(set(predicted_artifact))
|
156
|
+
##########################################
|
157
|
+
# Test custom_plotter tool when the
|
158
|
+
# simulation results are available but
|
159
|
+
# the species is not available
|
160
|
+
##########################################
|
161
|
+
prompt = "Plot the species `TP53`."
|
162
|
+
|
163
|
+
# Update state
|
164
|
+
app.update_state(config, {"llm_model": "gpt-4o-mini"}
|
165
|
+
)
|
166
|
+
# Test the tool get_modelinfo
|
167
|
+
response = app.invoke(
|
168
|
+
{"messages": [HumanMessage(content=prompt)]},
|
169
|
+
config=config
|
170
|
+
)
|
171
|
+
assistant_msg = response["messages"][-1].content
|
172
|
+
# print (response["messages"])
|
173
|
+
current_state = app.get_state(config)
|
174
|
+
# Get the messages from the current state
|
175
|
+
# and reverse the order
|
176
|
+
reversed_messages = current_state.values["messages"][::-1]
|
177
|
+
# Loop through the reversed messages until a
|
178
|
+
# ToolMessage is found.
|
179
|
+
predicted_artifact = []
|
180
|
+
for msg in reversed_messages:
|
181
|
+
if isinstance(msg, ToolMessage):
|
182
|
+
# Work on the message if it is a ToolMessage
|
183
|
+
# These may contain additional visuals that
|
184
|
+
# need to be displayed to the user.
|
185
|
+
if msg.name == "custom_plotter":
|
186
|
+
predicted_artifact = msg.artifact
|
187
|
+
break
|
188
|
+
# Check if the the predicted artifact is `None`
|
189
|
+
assert predicted_artifact is None
|
@@ -0,0 +1,57 @@
|
|
1
|
+
'''
|
2
|
+
This file contains the unit tests for the BioModel class.
|
3
|
+
'''
|
4
|
+
|
5
|
+
from typing import List, Dict, Union, Optional
|
6
|
+
from pydantic import Field
|
7
|
+
import pytest
|
8
|
+
from ..models.sys_bio_model import SysBioModel
|
9
|
+
|
10
|
+
class TestBioModel(SysBioModel):
|
11
|
+
'''
|
12
|
+
A test BioModel class for unit testing.
|
13
|
+
'''
|
14
|
+
|
15
|
+
biomodel_id: Optional[int] = Field(None, description="BioModel ID of the model")
|
16
|
+
sbml_file_path: Optional[str] = Field(None, description="Path to an SBML file")
|
17
|
+
name: Optional[str] = Field(..., description="Name of the model")
|
18
|
+
description: Optional[str] = Field("", description="Description of the model")
|
19
|
+
|
20
|
+
def get_model_metadata(self) -> Dict[str, Union[str, int]]:
|
21
|
+
'''
|
22
|
+
Get the metadata of the model.
|
23
|
+
'''
|
24
|
+
return self.biomodel_id
|
25
|
+
|
26
|
+
def simulate(self,
|
27
|
+
parameters: Dict[str, Union[float, int]],
|
28
|
+
duration: Union[int, float]) -> List[float]:
|
29
|
+
'''
|
30
|
+
Simulate the model.
|
31
|
+
'''
|
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))]
|
35
|
+
|
36
|
+
def test_get_model_metadata():
|
37
|
+
'''
|
38
|
+
Test the get_model_metadata method of the BioModel class.
|
39
|
+
'''
|
40
|
+
model = TestBioModel(biomodel_id=123, name="Test Model", description="A test model")
|
41
|
+
metadata = model.get_model_metadata()
|
42
|
+
assert metadata == 123
|
43
|
+
|
44
|
+
def test_check_biomodel_id_or_sbml_file_path():
|
45
|
+
'''
|
46
|
+
Test the check_biomodel_id_or_sbml_file_path method of the BioModel class.
|
47
|
+
'''
|
48
|
+
with pytest.raises(ValueError):
|
49
|
+
TestBioModel(name="Test Model", description="A test model")
|
50
|
+
|
51
|
+
def test_simulate():
|
52
|
+
'''
|
53
|
+
Test the simulate method of the BioModel class.
|
54
|
+
'''
|
55
|
+
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)
|
57
|
+
assert results == [1.0, 3.0, 5.0, 7.0]
|
@@ -0,0 +1,23 @@
|
|
1
|
+
'''
|
2
|
+
Test cases for the search_studies
|
3
|
+
'''
|
4
|
+
|
5
|
+
# from ..tools.search_studies import search_studies
|
6
|
+
from aiagents4pharma.talk2cells.agents.scp_agent import get_app
|
7
|
+
from langchain_core.messages import HumanMessage
|
8
|
+
|
9
|
+
def test_agent_scp():
|
10
|
+
'''
|
11
|
+
Test the agent_scp.
|
12
|
+
'''
|
13
|
+
unique_id = 12345
|
14
|
+
app = get_app(unique_id)
|
15
|
+
config = {"configurable": {"thread_id": unique_id}}
|
16
|
+
prompt = "Search for studies on Crohns Disease."
|
17
|
+
response = app.invoke(
|
18
|
+
{"messages": [HumanMessage(content=prompt)]},
|
19
|
+
config=config
|
20
|
+
)
|
21
|
+
assistant_msg = response["messages"][-1].content
|
22
|
+
# Check if the assistant message is a string
|
23
|
+
assert isinstance(assistant_msg, str)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
'''
|
4
|
+
This tool is used to display the table of studies.
|
5
|
+
'''
|
6
|
+
|
7
|
+
import logging
|
8
|
+
from typing import Annotated
|
9
|
+
from langchain_core.tools import tool
|
10
|
+
from langgraph.prebuilt import InjectedState
|
11
|
+
|
12
|
+
# Initialize logger
|
13
|
+
logging.basicConfig(level=logging.INFO)
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
@tool('display_studies')
|
17
|
+
def display_studies(state: Annotated[dict, InjectedState]):
|
18
|
+
"""
|
19
|
+
Display the table of studies.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
state (dict): The state of the agent.
|
23
|
+
"""
|
24
|
+
logger.log(logging.INFO, "Calling the tool display_studies")
|
25
|
+
return state["search_table"]
|
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
'''
|
4
|
+
A tool to fetch studies from the Single Cell Portal.
|
5
|
+
'''
|
6
|
+
|
7
|
+
import logging
|
8
|
+
from typing import Annotated
|
9
|
+
import requests
|
10
|
+
from langchain_core.tools import tool
|
11
|
+
from langchain_core.tools.base import InjectedToolCallId
|
12
|
+
from langchain_core.messages import ToolMessage
|
13
|
+
from langgraph.types import Command
|
14
|
+
import pandas as pd
|
15
|
+
|
16
|
+
# Initialize logger
|
17
|
+
logging.basicConfig(level=logging.INFO)
|
18
|
+
logger = logging.getLogger(__name__)
|
19
|
+
|
20
|
+
@tool('search_studies')
|
21
|
+
def search_studies(search_term: str,
|
22
|
+
tool_call_id: Annotated[str, InjectedToolCallId],
|
23
|
+
limit: int = 5):
|
24
|
+
"""
|
25
|
+
Fetch studies from single cell portal
|
26
|
+
|
27
|
+
Args:
|
28
|
+
search_term (str): The search term to use. Example: "COVID-19", "cancer", etc.
|
29
|
+
limit (int): The number of papers to return. Default is 5.
|
30
|
+
|
31
|
+
"""
|
32
|
+
logger.log(logging.INFO, "Calling the tool search_studies")
|
33
|
+
scp_endpoint = 'https://singlecell.broadinstitute.org/single_cell/api/v1/search?type=study'
|
34
|
+
# params = {'terms': search_term, 'facets': 'MONDO_0005011'}
|
35
|
+
params = {'terms': search_term}
|
36
|
+
status_code = 0
|
37
|
+
while status_code != 200:
|
38
|
+
# Make a GET request to the single cell portal
|
39
|
+
search_response = requests.get(scp_endpoint,
|
40
|
+
params=params,
|
41
|
+
timeout=10,
|
42
|
+
verify=False)
|
43
|
+
status_code = search_response.status_code
|
44
|
+
logger.log(logging.INFO, "Status code %s received from SCP")
|
45
|
+
|
46
|
+
# Select the columns to display in the table
|
47
|
+
selected_columns = ["study_source", "name", "study_url", "gene_count", "cell_count"]
|
48
|
+
|
49
|
+
# Extract the data from the response
|
50
|
+
# with the selected columns
|
51
|
+
df = pd.DataFrame(search_response.json()['studies'])[selected_columns]
|
52
|
+
|
53
|
+
# Convert column 'Study Name' into clickable
|
54
|
+
# hyperlinks from the column 'Study URL'
|
55
|
+
scp_api_url = 'https://singlecell.broadinstitute.org'
|
56
|
+
df['name'] = df.apply(
|
57
|
+
lambda x: f"<a href=\"{scp_api_url}/{x['study_url']}\">{x['name']}</a>",
|
58
|
+
axis=1)
|
59
|
+
|
60
|
+
# Excldue the column 'Study URL' from the dataframe
|
61
|
+
df = df.drop(columns=['study_url'])
|
62
|
+
|
63
|
+
# Add a new column a the beginning of the dataframe with row numbers
|
64
|
+
df.insert(0, 'S/N', range(1, 1 + len(df)))
|
65
|
+
|
66
|
+
# Update the state key 'search_table' with the dataframe in markdown format
|
67
|
+
return Command(
|
68
|
+
update={
|
69
|
+
# update the state keys
|
70
|
+
"search_table": df.to_markdown(tablefmt="grid"),
|
71
|
+
# update the message history
|
72
|
+
"messages": [
|
73
|
+
ToolMessage(
|
74
|
+
f"Successfully fetched {limit} studies on {search_term}.",
|
75
|
+
tool_call_id=tool_call_id
|
76
|
+
)
|
77
|
+
],
|
78
|
+
}
|
79
|
+
)
|
File without changes
|