aiagents4pharma 1.8.0__py3-none-any.whl → 1.15.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/__init__.py +9 -5
- aiagents4pharma/configs/__init__.py +5 -0
- aiagents4pharma/configs/config.yaml +4 -0
- aiagents4pharma/configs/talk2biomodels/__init__.py +6 -0
- aiagents4pharma/configs/talk2biomodels/agents/__init__.py +5 -0
- aiagents4pharma/configs/talk2biomodels/agents/t2b_agent/__init__.py +3 -0
- aiagents4pharma/configs/talk2biomodels/agents/t2b_agent/default.yaml +14 -0
- aiagents4pharma/configs/talk2biomodels/tools/__init__.py +4 -0
- aiagents4pharma/configs/talk2biomodels/tools/ask_question/__init__.py +3 -0
- aiagents4pharma/talk2biomodels/__init__.py +3 -0
- aiagents4pharma/talk2biomodels/agents/__init__.py +5 -0
- aiagents4pharma/talk2biomodels/agents/t2b_agent.py +96 -0
- aiagents4pharma/talk2biomodels/api/__init__.py +6 -0
- aiagents4pharma/talk2biomodels/api/kegg.py +83 -0
- aiagents4pharma/talk2biomodels/api/ols.py +72 -0
- aiagents4pharma/talk2biomodels/api/uniprot.py +35 -0
- aiagents4pharma/talk2biomodels/models/basico_model.py +29 -32
- aiagents4pharma/talk2biomodels/models/sys_bio_model.py +9 -6
- aiagents4pharma/talk2biomodels/states/__init__.py +5 -0
- aiagents4pharma/talk2biomodels/states/state_talk2biomodels.py +41 -0
- aiagents4pharma/talk2biomodels/tests/__init__.py +3 -0
- aiagents4pharma/talk2biomodels/tests/test_api.py +57 -0
- aiagents4pharma/talk2biomodels/tests/test_ask_question.py +44 -0
- aiagents4pharma/talk2biomodels/tests/test_basico_model.py +54 -0
- aiagents4pharma/talk2biomodels/tests/test_get_annotation.py +171 -0
- aiagents4pharma/talk2biomodels/tests/test_getmodelinfo.py +26 -0
- aiagents4pharma/talk2biomodels/tests/test_integration.py +126 -0
- aiagents4pharma/talk2biomodels/tests/test_param_scan.py +68 -0
- aiagents4pharma/talk2biomodels/tests/test_query_article.py +76 -0
- aiagents4pharma/talk2biomodels/tests/test_search_models.py +28 -0
- aiagents4pharma/talk2biomodels/tests/test_simulate_model.py +39 -0
- aiagents4pharma/talk2biomodels/tests/test_steady_state.py +90 -0
- aiagents4pharma/talk2biomodels/tests/test_sys_bio_model.py +63 -0
- aiagents4pharma/talk2biomodels/tools/__init__.py +5 -0
- aiagents4pharma/talk2biomodels/tools/ask_question.py +61 -18
- aiagents4pharma/talk2biomodels/tools/custom_plotter.py +20 -14
- aiagents4pharma/talk2biomodels/tools/get_annotation.py +304 -0
- aiagents4pharma/talk2biomodels/tools/get_modelinfo.py +11 -9
- aiagents4pharma/talk2biomodels/tools/load_arguments.py +114 -0
- aiagents4pharma/talk2biomodels/tools/load_biomodel.py +0 -1
- aiagents4pharma/talk2biomodels/tools/parameter_scan.py +287 -0
- aiagents4pharma/talk2biomodels/tools/query_article.py +59 -0
- aiagents4pharma/talk2biomodels/tools/simulate_model.py +35 -90
- aiagents4pharma/talk2biomodels/tools/steady_state.py +167 -0
- aiagents4pharma/talk2cells/tests/scp_agent/test_scp_agent.py +23 -0
- aiagents4pharma/talk2cells/tools/scp_agent/__init__.py +6 -0
- aiagents4pharma/talk2cells/tools/scp_agent/display_studies.py +25 -0
- aiagents4pharma/talk2cells/tools/scp_agent/search_studies.py +79 -0
- aiagents4pharma/talk2competitors/__init__.py +5 -0
- aiagents4pharma/talk2competitors/agents/__init__.py +6 -0
- aiagents4pharma/talk2competitors/agents/main_agent.py +130 -0
- aiagents4pharma/talk2competitors/agents/s2_agent.py +75 -0
- aiagents4pharma/talk2competitors/config/__init__.py +5 -0
- aiagents4pharma/talk2competitors/config/config.py +110 -0
- aiagents4pharma/talk2competitors/state/__init__.py +5 -0
- aiagents4pharma/talk2competitors/state/state_talk2competitors.py +32 -0
- aiagents4pharma/talk2competitors/tests/__init__.py +3 -0
- aiagents4pharma/talk2competitors/tests/test_langgraph.py +274 -0
- aiagents4pharma/talk2competitors/tools/__init__.py +7 -0
- aiagents4pharma/talk2competitors/tools/s2/__init__.py +8 -0
- aiagents4pharma/talk2competitors/tools/s2/display_results.py +25 -0
- aiagents4pharma/talk2competitors/tools/s2/multi_paper_rec.py +132 -0
- aiagents4pharma/talk2competitors/tools/s2/search.py +119 -0
- aiagents4pharma/talk2competitors/tools/s2/single_paper_rec.py +141 -0
- aiagents4pharma/talk2knowledgegraphs/__init__.py +2 -1
- aiagents4pharma/talk2knowledgegraphs/tests/__init__.py +0 -0
- aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_biobridge_primekg.py +242 -0
- aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_dataset.py +29 -0
- aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_primekg.py +73 -0
- aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_starkqa_primekg.py +116 -0
- aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_embeddings.py +47 -0
- aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_huggingface.py +45 -0
- aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_sentencetransformer.py +40 -0
- 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.8.0.dist-info → aiagents4pharma-1.15.0.dist-info}/METADATA +44 -25
- aiagents4pharma-1.15.0.dist-info/RECORD +102 -0
- aiagents4pharma-1.8.0.dist-info/RECORD +0 -35
- {aiagents4pharma-1.8.0.dist-info → aiagents4pharma-1.15.0.dist-info}/LICENSE +0 -0
- {aiagents4pharma-1.8.0.dist-info → aiagents4pharma-1.15.0.dist-info}/WHEEL +0 -0
- {aiagents4pharma-1.8.0.dist-info → aiagents4pharma-1.15.0.dist-info}/top_level.txt +0 -0
aiagents4pharma/__init__.py
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
-
|
1
|
+
"""
|
2
2
|
This file is used to import aiagents4pharma modules.
|
3
|
-
|
3
|
+
"""
|
4
4
|
|
5
|
-
from . import
|
6
|
-
|
7
|
-
|
5
|
+
from . import (
|
6
|
+
configs,
|
7
|
+
talk2biomodels,
|
8
|
+
talk2cells,
|
9
|
+
talk2competitors,
|
10
|
+
talk2knowledgegraphs,
|
11
|
+
)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
_target_: talk2biomodels.agents.t2b_agent.get_app
|
2
|
+
state_modifier: >
|
3
|
+
You are Talk2BioModels agent.
|
4
|
+
If the user asks for the uploaded model,
|
5
|
+
then pass the use_uploaded_model argument
|
6
|
+
as True. If the user asks for simulation
|
7
|
+
or param_scan or steady state, suggest a
|
8
|
+
value for the `experiment_name` argument.
|
9
|
+
|
10
|
+
If the user asks question related to the
|
11
|
+
uploaded document/pdf/article/document,
|
12
|
+
use the tool `query_article` to answer the
|
13
|
+
question. Please note that the `experiment_name`
|
14
|
+
argument may be unrelated to the question asked.
|
@@ -0,0 +1,96 @@
|
|
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.get_annotation import GetAnnotationTool
|
19
|
+
from ..tools.ask_question import AskQuestionTool
|
20
|
+
from ..tools.parameter_scan import ParameterScanTool
|
21
|
+
from ..tools.steady_state import SteadyStateTool
|
22
|
+
from ..tools.query_article import QueryArticle
|
23
|
+
from ..states.state_talk2biomodels import Talk2Biomodels
|
24
|
+
|
25
|
+
# Initialize logger
|
26
|
+
logging.basicConfig(level=logging.INFO)
|
27
|
+
logger = logging.getLogger(__name__)
|
28
|
+
|
29
|
+
def get_app(uniq_id, llm_model='gpt-4o-mini'):
|
30
|
+
'''
|
31
|
+
This function returns the langraph app.
|
32
|
+
'''
|
33
|
+
def agent_t2b_node(state: Annotated[dict, InjectedState]):
|
34
|
+
'''
|
35
|
+
This function calls the model.
|
36
|
+
'''
|
37
|
+
logger.log(logging.INFO, "Calling t2b_agent node with thread_id %s", uniq_id)
|
38
|
+
response = model.invoke(state, {"configurable": {"thread_id": uniq_id}})
|
39
|
+
return response
|
40
|
+
|
41
|
+
# Define the tools
|
42
|
+
tools = ToolNode([
|
43
|
+
SimulateModelTool(),
|
44
|
+
AskQuestionTool(),
|
45
|
+
CustomPlotterTool(),
|
46
|
+
SearchModelsTool(),
|
47
|
+
GetModelInfoTool(),
|
48
|
+
SteadyStateTool(),
|
49
|
+
ParameterScanTool(),
|
50
|
+
GetAnnotationTool(),
|
51
|
+
QueryArticle()
|
52
|
+
])
|
53
|
+
|
54
|
+
# Define the model
|
55
|
+
llm = ChatOpenAI(model=llm_model, temperature=0)
|
56
|
+
# Load hydra configuration
|
57
|
+
logger.log(logging.INFO, "Load Hydra configuration for Talk2BioModels agent.")
|
58
|
+
with hydra.initialize(version_base=None, config_path="../../configs"):
|
59
|
+
cfg = hydra.compose(config_name='config',
|
60
|
+
overrides=['talk2biomodels/agents/t2b_agent=default'])
|
61
|
+
cfg = cfg.talk2biomodels.agents.t2b_agent
|
62
|
+
logger.log(logging.INFO, "state_modifier: %s", cfg.state_modifier)
|
63
|
+
# Create the agent
|
64
|
+
model = create_react_agent(
|
65
|
+
llm,
|
66
|
+
tools=tools,
|
67
|
+
state_schema=Talk2Biomodels,
|
68
|
+
state_modifier=cfg.state_modifier,
|
69
|
+
checkpointer=MemorySaver()
|
70
|
+
)
|
71
|
+
|
72
|
+
# Define a new graph
|
73
|
+
workflow = StateGraph(Talk2Biomodels)
|
74
|
+
|
75
|
+
# Define the two nodes we will cycle between
|
76
|
+
workflow.add_node("agent_t2b", agent_t2b_node)
|
77
|
+
|
78
|
+
# Set the entrypoint as the first node
|
79
|
+
# This means that this node is the first one called
|
80
|
+
workflow.add_edge(START, "agent_t2b")
|
81
|
+
|
82
|
+
# Initialize memory to persist state between graph runs
|
83
|
+
checkpointer = MemorySaver()
|
84
|
+
|
85
|
+
# Finally, we compile it!
|
86
|
+
# This compiles it into a LangChain Runnable,
|
87
|
+
# meaning you can use it as you would any other runnable.
|
88
|
+
# Note that we're (optionally) passing the memory
|
89
|
+
# when compiling the graph
|
90
|
+
app = workflow.compile(checkpointer=checkpointer)
|
91
|
+
logger.log(logging.INFO,
|
92
|
+
"Compiled the graph with thread_id %s and llm_model %s",
|
93
|
+
uniq_id,
|
94
|
+
llm_model)
|
95
|
+
|
96
|
+
return app
|
@@ -0,0 +1,83 @@
|
|
1
|
+
"""
|
2
|
+
This module contains the API for fetching Kegg database
|
3
|
+
"""
|
4
|
+
import re
|
5
|
+
from typing import List, Dict
|
6
|
+
import requests
|
7
|
+
|
8
|
+
def fetch_from_api(base_url: str, query: str) -> str:
|
9
|
+
"""Fetch data from the given API endpoint."""
|
10
|
+
try:
|
11
|
+
response = requests.get(base_url + query, timeout=10)
|
12
|
+
response.raise_for_status()
|
13
|
+
return response.text
|
14
|
+
except requests.exceptions.RequestException as e:
|
15
|
+
print(f"Error fetching data for query {query}: {e}")
|
16
|
+
return ""
|
17
|
+
|
18
|
+
def fetch_kegg_names(ids: List[str], batch_size: int = 10) -> Dict[str, str]:
|
19
|
+
"""
|
20
|
+
Fetch the names of multiple KEGG entries using the KEGG REST API in batches.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
ids (List[str]): List of KEGG IDs.
|
24
|
+
batch_size (int): Maximum number of IDs to include in a single request.
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
Dict[str, str]: A mapping of KEGG IDs to their names.
|
28
|
+
"""
|
29
|
+
if not ids:
|
30
|
+
return {}
|
31
|
+
|
32
|
+
base_url = "https://rest.kegg.jp/get/"
|
33
|
+
entry_name_map = {}
|
34
|
+
|
35
|
+
# Process IDs in batches
|
36
|
+
for i in range(0, len(ids), batch_size):
|
37
|
+
batch = ids[i:i + batch_size]
|
38
|
+
query = "+".join(batch)
|
39
|
+
entry_data = fetch_from_api(base_url, query)
|
40
|
+
|
41
|
+
# if not entry_data:
|
42
|
+
# continue
|
43
|
+
entries = entry_data.split("///")
|
44
|
+
for entry in entries:
|
45
|
+
if not entry.strip():
|
46
|
+
continue
|
47
|
+
lines = entry.strip().split("\n")
|
48
|
+
entry_line = next((line for line in lines
|
49
|
+
if line.startswith("ENTRY")), None)
|
50
|
+
name_line = next((line for line in lines
|
51
|
+
if line.startswith("NAME")), None)
|
52
|
+
|
53
|
+
# if not entry_line and not name_line:
|
54
|
+
# continue
|
55
|
+
entry_id = entry_line.split()[1]
|
56
|
+
# Split multiple names in the NAME field and clean them
|
57
|
+
names = [
|
58
|
+
re.sub(r'[^a-zA-Z0-9\s]', '', name).strip()
|
59
|
+
for name in name_line.replace("NAME", "").strip().split(";")
|
60
|
+
]
|
61
|
+
# Join cleaned names into a single string
|
62
|
+
entry_name_map[entry_id] = " ".join(names).strip()
|
63
|
+
|
64
|
+
return entry_name_map
|
65
|
+
|
66
|
+
def fetch_kegg_annotations(data: List[Dict[str, str]],
|
67
|
+
batch_size: int = 10) -> Dict[str, Dict[str, str]]:
|
68
|
+
"""Fetch KEGG entry descriptions grouped by database type."""
|
69
|
+
grouped_data = {}
|
70
|
+
for entry in data:
|
71
|
+
db_type = entry["Database"].lower()
|
72
|
+
grouped_data.setdefault(db_type, []).append(entry["Id"])
|
73
|
+
|
74
|
+
results = {}
|
75
|
+
for db_type, ids in grouped_data.items():
|
76
|
+
results[db_type] = fetch_kegg_names(ids, batch_size=batch_size)
|
77
|
+
|
78
|
+
return results
|
79
|
+
|
80
|
+
# def get_protein_name_or_label(data: List[Dict[str, str]],
|
81
|
+
# batch_size: int = 10) -> Dict[str, Dict[str, str]]:
|
82
|
+
# """Fetch descriptions for KEGG-related identifiers."""
|
83
|
+
# return fetch_kegg_annotations(data, batch_size=batch_size)
|
@@ -0,0 +1,72 @@
|
|
1
|
+
"""
|
2
|
+
This module contains the API for fetching ols database
|
3
|
+
"""
|
4
|
+
from typing import List, Dict
|
5
|
+
import requests
|
6
|
+
|
7
|
+
def fetch_from_ols(term: str) -> str:
|
8
|
+
"""
|
9
|
+
Fetch the label for a single term from OLS.
|
10
|
+
|
11
|
+
Args:
|
12
|
+
term (str): The term in the format "ONTOLOGY:TERM_ID".
|
13
|
+
|
14
|
+
Returns:
|
15
|
+
str: The label for the term or an error message.
|
16
|
+
"""
|
17
|
+
try:
|
18
|
+
ontology, _ = term.split(":")
|
19
|
+
base_url = f"https://www.ebi.ac.uk/ols4/api/ontologies/{ontology.lower()}/terms"
|
20
|
+
params = {"obo_id": term}
|
21
|
+
response = requests.get(
|
22
|
+
base_url,
|
23
|
+
params=params,
|
24
|
+
headers={"Accept": "application/json"},
|
25
|
+
timeout=10
|
26
|
+
)
|
27
|
+
response.raise_for_status()
|
28
|
+
data = response.json()
|
29
|
+
label = '-'
|
30
|
+
# Extract and return the label
|
31
|
+
if "_embedded" in data and "terms" in data["_embedded"] \
|
32
|
+
and len(data["_embedded"]["terms"]) > 0:
|
33
|
+
label = data["_embedded"]["terms"][0].get("label", "Label not found")
|
34
|
+
return label
|
35
|
+
except (requests.exceptions.RequestException, KeyError, IndexError) as e:
|
36
|
+
return f"Error: {str(e)}"
|
37
|
+
|
38
|
+
def fetch_ols_labels(terms: List[str]) -> Dict[str, str]:
|
39
|
+
"""
|
40
|
+
Fetch labels for multiple terms from OLS.
|
41
|
+
|
42
|
+
Args:
|
43
|
+
terms (List[str]): A list of terms in the format "ONTOLOGY:TERM_ID".
|
44
|
+
|
45
|
+
Returns:
|
46
|
+
Dict[str, str]: A mapping of term IDs to their labels or error messages.
|
47
|
+
"""
|
48
|
+
results = {}
|
49
|
+
for term in terms:
|
50
|
+
results[term] = fetch_from_ols(term)
|
51
|
+
return results
|
52
|
+
|
53
|
+
def search_ols_labels(data: List[Dict[str, str]]) -> Dict[str, Dict[str, str]]:
|
54
|
+
"""
|
55
|
+
Fetch OLS annotations grouped by ontology type.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
data (List[Dict[str, str]]): A list of dictionaries containing 'Id' and 'Database'.
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
Dict[str, Dict[str, str]]: A mapping of ontology type to term labels.
|
62
|
+
"""
|
63
|
+
grouped_data = {}
|
64
|
+
for entry in data:
|
65
|
+
ontology = entry["Database"].lower()
|
66
|
+
grouped_data.setdefault(ontology, []).append(entry["Id"])
|
67
|
+
|
68
|
+
results = {}
|
69
|
+
for ontology, terms in grouped_data.items():
|
70
|
+
results[ontology] = fetch_ols_labels(terms)
|
71
|
+
|
72
|
+
return results
|
@@ -0,0 +1,35 @@
|
|
1
|
+
"""
|
2
|
+
This module contains the API for fetching uniprot database
|
3
|
+
"""
|
4
|
+
from typing import List, Dict
|
5
|
+
import requests
|
6
|
+
|
7
|
+
def search_uniprot_labels(identifiers: List[str]) -> Dict[str, str]:
|
8
|
+
"""
|
9
|
+
Fetch protein names or labels for a list of UniProt identifiers by making sequential requests.
|
10
|
+
|
11
|
+
Args:
|
12
|
+
identifiers (List[str]): A list of UniProt identifiers.
|
13
|
+
|
14
|
+
Returns:
|
15
|
+
Dict[str, str]: A mapping of UniProt identifiers to their protein names or error messages.
|
16
|
+
"""
|
17
|
+
results = {}
|
18
|
+
base_url = "https://www.uniprot.org/uniprot/"
|
19
|
+
|
20
|
+
for identifier in identifiers:
|
21
|
+
url = f"{base_url}{identifier}.json"
|
22
|
+
try:
|
23
|
+
response = requests.get(url, timeout=10)
|
24
|
+
response.raise_for_status()
|
25
|
+
data = response.json()
|
26
|
+
protein_name = (
|
27
|
+
data.get('proteinDescription', {})
|
28
|
+
.get('recommendedName', {})
|
29
|
+
.get('fullName', {})
|
30
|
+
.get('value', 'Name not found')
|
31
|
+
)
|
32
|
+
results[identifier] = protein_name
|
33
|
+
except requests.exceptions.RequestException as e:
|
34
|
+
results[identifier] = f"Error: {str(e)}"
|
35
|
+
return results
|
@@ -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:
|
@@ -0,0 +1,41 @@
|
|
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
|
+
def add_data(data1: dict, data2: dict) -> dict:
|
12
|
+
"""
|
13
|
+
A reducer function to merge two dictionaries.
|
14
|
+
"""
|
15
|
+
left_idx_by_name = {data['name']: idx for idx, data in enumerate(data1)}
|
16
|
+
merged = data1.copy()
|
17
|
+
for data in data2:
|
18
|
+
idx = left_idx_by_name.get(data['name'])
|
19
|
+
if idx is not None:
|
20
|
+
merged[idx] = data
|
21
|
+
else:
|
22
|
+
merged.append(data)
|
23
|
+
return merged
|
24
|
+
|
25
|
+
class Talk2Biomodels(AgentState):
|
26
|
+
"""
|
27
|
+
The state for the Talk2BioModels agent.
|
28
|
+
"""
|
29
|
+
llm_model: str
|
30
|
+
pdf_file_name: str
|
31
|
+
# A StateGraph may receive a concurrent updates
|
32
|
+
# which is not supported by the StateGraph. Hence,
|
33
|
+
# we need to add a reducer function to handle the
|
34
|
+
# concurrent updates.
|
35
|
+
# https://langchain-ai.github.io/langgraph/troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE/
|
36
|
+
model_id: Annotated[list, operator.add]
|
37
|
+
sbml_file_path: Annotated[list, operator.add]
|
38
|
+
dic_simulated_data: Annotated[list[dict], add_data]
|
39
|
+
dic_scanned_data: Annotated[list[dict], add_data]
|
40
|
+
dic_steady_state_data: Annotated[list[dict], add_data]
|
41
|
+
dic_annotations_data : Annotated[list[dict], add_data]
|
@@ -0,0 +1,57 @@
|
|
1
|
+
'''
|
2
|
+
Test cases for Talk2Biomodels.
|
3
|
+
'''
|
4
|
+
|
5
|
+
from ..api.uniprot import search_uniprot_labels
|
6
|
+
from ..api.ols import fetch_from_ols
|
7
|
+
from ..api.kegg import fetch_kegg_names, fetch_from_api
|
8
|
+
|
9
|
+
def test_search_uniprot_labels():
|
10
|
+
'''
|
11
|
+
Test the search_uniprot_labels function.
|
12
|
+
'''
|
13
|
+
# "P61764" = Positive result, "P0000Q" = negative result
|
14
|
+
identifiers = ["P61764", "P0000Q"]
|
15
|
+
results = search_uniprot_labels(identifiers)
|
16
|
+
assert results["P61764"] == "Syntaxin-binding protein 1"
|
17
|
+
assert results["P0000Q"].startswith("Error: 400")
|
18
|
+
|
19
|
+
def test_fetch_from_ols():
|
20
|
+
'''
|
21
|
+
Test the fetch_from_ols function.
|
22
|
+
'''
|
23
|
+
term_1 = "GO:0005886" #Positive result
|
24
|
+
term_2 = "GO:ABC123" #Negative result
|
25
|
+
label_1 = fetch_from_ols(term_1)
|
26
|
+
label_2 = fetch_from_ols(term_2)
|
27
|
+
assert isinstance(label_1, str), f"Expected string, got {type(label_1)}"
|
28
|
+
assert isinstance(label_2, str), f"Expected string, got {type(label_2)}"
|
29
|
+
assert label_1 == "plasma membrane"
|
30
|
+
assert label_2.startswith("Error: 404")
|
31
|
+
|
32
|
+
def test_fetch_kegg_names():
|
33
|
+
'''
|
34
|
+
Test the fetch_kegg_names function.
|
35
|
+
'''
|
36
|
+
ids = ["C00001", "C00002"]
|
37
|
+
results = fetch_kegg_names(ids)
|
38
|
+
assert results["C00001"] == "H2O"
|
39
|
+
assert results["C00002"] == "ATP"
|
40
|
+
|
41
|
+
# Try with an empty list
|
42
|
+
results = fetch_kegg_names([])
|
43
|
+
assert not results
|
44
|
+
|
45
|
+
def test_fetch_from_api():
|
46
|
+
'''
|
47
|
+
Test the fetch_from_api function.
|
48
|
+
'''
|
49
|
+
base_url = "https://rest.kegg.jp/get/"
|
50
|
+
query = "C00001"
|
51
|
+
entry_data = fetch_from_api(base_url, query)
|
52
|
+
assert entry_data.startswith("ENTRY C00001")
|
53
|
+
|
54
|
+
# Try with an invalid query
|
55
|
+
query = "C0000Q"
|
56
|
+
entry_data = fetch_from_api(base_url, query)
|
57
|
+
assert not entry_data
|
@@ -0,0 +1,44 @@
|
|
1
|
+
'''
|
2
|
+
Test cases for Talk2Biomodels.
|
3
|
+
'''
|
4
|
+
|
5
|
+
from langchain_core.messages import HumanMessage, ToolMessage
|
6
|
+
from ..agents.t2b_agent import get_app
|
7
|
+
|
8
|
+
def test_ask_question_tool():
|
9
|
+
'''
|
10
|
+
Test the ask_question tool without the simulation results.
|
11
|
+
'''
|
12
|
+
unique_id = 12345
|
13
|
+
app = get_app(unique_id, llm_model='gpt-4o-mini')
|
14
|
+
config = {"configurable": {"thread_id": unique_id}}
|
15
|
+
|
16
|
+
##########################################
|
17
|
+
# Test ask_question tool when simulation
|
18
|
+
# results are not available i.e. the
|
19
|
+
# simulation has not been run. In this
|
20
|
+
# case, the tool should return an error
|
21
|
+
##########################################
|
22
|
+
# Update state
|
23
|
+
app.update_state(config, {"llm_model": "gpt-4o-mini"})
|
24
|
+
# Define the prompt
|
25
|
+
prompt = "Call the ask_question tool to answer the "
|
26
|
+
prompt += "question: What is the concentration of CRP "
|
27
|
+
prompt += "in serum at 1000 hours? The simulation name "
|
28
|
+
prompt += "is `simulation_name`."
|
29
|
+
# Invoke the tool
|
30
|
+
app.invoke(
|
31
|
+
{"messages": [HumanMessage(content=prompt)]},
|
32
|
+
config=config
|
33
|
+
)
|
34
|
+
# Get the messages from the current state
|
35
|
+
# and reverse the order
|
36
|
+
current_state = app.get_state(config)
|
37
|
+
reversed_messages = current_state.values["messages"][::-1]
|
38
|
+
# Loop through the reversed messages until a
|
39
|
+
# ToolMessage is found.
|
40
|
+
for msg in reversed_messages:
|
41
|
+
# Assert that the message is a ToolMessage
|
42
|
+
# and its status is "error"
|
43
|
+
if isinstance(msg, ToolMessage):
|
44
|
+
assert msg.status == "error"
|