aiagents4pharma 1.14.0__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 (26) 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/states/state_talk2biomodels.py +21 -7
  7. aiagents4pharma/talk2biomodels/tests/test_ask_question.py +44 -0
  8. aiagents4pharma/talk2biomodels/tests/test_get_annotation.py +67 -69
  9. aiagents4pharma/talk2biomodels/tests/test_getmodelinfo.py +26 -0
  10. aiagents4pharma/talk2biomodels/tests/test_integration.py +126 -0
  11. aiagents4pharma/talk2biomodels/tests/test_param_scan.py +68 -0
  12. aiagents4pharma/talk2biomodels/tests/test_search_models.py +28 -0
  13. aiagents4pharma/talk2biomodels/tests/test_simulate_model.py +39 -0
  14. aiagents4pharma/talk2biomodels/tests/test_steady_state.py +90 -0
  15. aiagents4pharma/talk2biomodels/tools/ask_question.py +29 -8
  16. aiagents4pharma/talk2biomodels/tools/get_annotation.py +24 -9
  17. aiagents4pharma/talk2biomodels/tools/load_arguments.py +114 -0
  18. aiagents4pharma/talk2biomodels/tools/parameter_scan.py +91 -96
  19. aiagents4pharma/talk2biomodels/tools/simulate_model.py +14 -81
  20. aiagents4pharma/talk2biomodels/tools/steady_state.py +48 -89
  21. {aiagents4pharma-1.14.0.dist-info → aiagents4pharma-1.14.1.dist-info}/METADATA +1 -1
  22. {aiagents4pharma-1.14.0.dist-info → aiagents4pharma-1.14.1.dist-info}/RECORD +25 -16
  23. aiagents4pharma/talk2biomodels/tests/test_langgraph.py +0 -384
  24. {aiagents4pharma-1.14.0.dist-info → aiagents4pharma-1.14.1.dist-info}/LICENSE +0 -0
  25. {aiagents4pharma-1.14.0.dist-info → aiagents4pharma-1.14.1.dist-info}/WHEEL +0 -0
  26. {aiagents4pharma-1.14.0.dist-info → aiagents4pharma-1.14.1.dist-info}/top_level.txt +0 -0
@@ -1,384 +0,0 @@
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_get_modelinfo_tool():
10
- '''
11
- Test the get_modelinfo tool.
12
- '''
13
- unique_id = 12345
14
- app = get_app(unique_id)
15
- config = {"configurable": {"thread_id": unique_id}}
16
- # Update state
17
- app.update_state(config,
18
- {"sbml_file_path": ["aiagents4pharma/talk2biomodels/tests/BIOMD0000000449_url.xml"]})
19
- prompt = "Extract all relevant information from the uploaded model."
20
- # Test the tool get_modelinfo
21
- response = app.invoke(
22
- {"messages": [HumanMessage(content=prompt)]},
23
- config=config
24
- )
25
- assistant_msg = response["messages"][-1].content
26
- # Check if the assistant message is a string
27
- assert isinstance(assistant_msg, str)
28
-
29
- def test_search_models_tool():
30
- '''
31
- Test the search_models tool.
32
- '''
33
- unique_id = 12345
34
- app = get_app(unique_id)
35
- config = {"configurable": {"thread_id": unique_id}}
36
- # Update state
37
- app.update_state(config, {"llm_model": "gpt-4o-mini"})
38
- prompt = "Search for models on Crohn's disease."
39
- # Test the tool get_modelinfo
40
- response = app.invoke(
41
- {"messages": [HumanMessage(content=prompt)]},
42
- config=config
43
- )
44
- assistant_msg = response["messages"][-1].content
45
- # Check if the assistant message is a string
46
- assert isinstance(assistant_msg, str)
47
- # Check if the assistant message contains the
48
- # biomodel id BIO0000000537
49
- assert "BIOMD0000000537" in assistant_msg
50
-
51
- def test_ask_question_tool():
52
- '''
53
- Test the ask_question tool without the simulation results.
54
- '''
55
- unique_id = 12345
56
- app = get_app(unique_id, llm_model='gpt-4o-mini')
57
- config = {"configurable": {"thread_id": unique_id}}
58
-
59
- ##########################################
60
- # Test ask_question tool when simulation
61
- # results are not available i.e. the
62
- # simulation has not been run. In this
63
- # case, the tool should return an error
64
- ##########################################
65
- # Update state
66
- app.update_state(config, {"llm_model": "gpt-4o-mini"})
67
- # Define the prompt
68
- prompt = "Call the ask_question tool to answer the "
69
- prompt += "question: What is the concentration of CRP "
70
- prompt += "in serum at 1000 hours? The simulation name "
71
- prompt += "is `simulation_name`."
72
- # Invoke the tool
73
- app.invoke(
74
- {"messages": [HumanMessage(content=prompt)]},
75
- config=config
76
- )
77
- # Get the messages from the current state
78
- # and reverse the order
79
- current_state = app.get_state(config)
80
- reversed_messages = current_state.values["messages"][::-1]
81
- # Loop through the reversed messages until a
82
- # ToolMessage is found.
83
- for msg in reversed_messages:
84
- # Assert that the message is a ToolMessage
85
- # and its status is "error"
86
- if isinstance(msg, ToolMessage):
87
- assert msg.status == "error"
88
-
89
- def test_simulate_model_tool():
90
- '''
91
- Test the simulate_model tool when simulating
92
- multiple models.
93
- '''
94
- unique_id = 123
95
- app = get_app(unique_id)
96
- config = {"configurable": {"thread_id": unique_id}}
97
- app.update_state(config, {"llm_model": "gpt-4o-mini"})
98
- # Upload a model to the state
99
- app.update_state(config,
100
- {"sbml_file_path": ["aiagents4pharma/talk2biomodels/tests/BIOMD0000000449_url.xml"]})
101
- prompt = "Simulate model 64 and the uploaded model"
102
- # Invoke the agent
103
- app.invoke(
104
- {"messages": [HumanMessage(content=prompt)]},
105
- config=config
106
- )
107
- current_state = app.get_state(config)
108
- dic_simulated_data = current_state.values["dic_simulated_data"]
109
- # Check if the dic_simulated_data is a list
110
- assert isinstance(dic_simulated_data, list)
111
- # Check if the length of the dic_simulated_data is 2
112
- assert len(dic_simulated_data) == 2
113
- # Check if the source of the first model is 64
114
- assert dic_simulated_data[0]['source'] == 64
115
- # Check if the source of the second model is upload
116
- assert dic_simulated_data[1]['source'] == "upload"
117
- # Check if the data of the first model contains
118
- assert '1,3-bisphosphoglycerate' in dic_simulated_data[0]['data']
119
- # Check if the data of the second model contains
120
- assert 'mTORC2' in dic_simulated_data[1]['data']
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. Also, reset
149
- the IL6{serum} concentration to 100 every 500 hours and assume
150
- that the model is simulated for 2016 hours with an interval of 2016."""
151
- # Invoke the agent
152
- app.invoke(
153
- {"messages": [HumanMessage(content=prompt)]},
154
- config=config
155
- )
156
- current_state = app.get_state(config)
157
- reversed_messages = current_state.values["messages"][::-1]
158
- # Loop through the reversed messages until a
159
- # ToolMessage is found.
160
- df = pd.DataFrame(columns=['name', 'status', 'content'])
161
- names = []
162
- statuses = []
163
- contents = []
164
- for msg in reversed_messages:
165
- # Assert that the message is a ToolMessage
166
- # and its status is "error"
167
- if not isinstance(msg, ToolMessage):
168
- continue
169
- names.append(msg.name)
170
- statuses.append(msg.status)
171
- contents.append(msg.content)
172
- df = pd.DataFrame({'name': names, 'status': statuses, 'content': contents})
173
- # print (df)
174
- assert any((df["status"] == "error") &
175
- (df["name"] == "parameter_scan") &
176
- (df["content"].str.startswith("Error: ValueError('Invalid parameter name:")))
177
- assert any((df["status"] == "success") &
178
- (df["name"] == "parameter_scan") &
179
- (df["content"].str.startswith("Parameter scan results of")))
180
- assert any((df["status"] == "success") &
181
- (df["name"] == "get_modelinfo"))
182
-
183
- def test_steady_state_tool():
184
- '''
185
- Test the steady_state tool.
186
- '''
187
- unique_id = 123
188
- app = get_app(unique_id)
189
- config = {"configurable": {"thread_id": unique_id}}
190
- app.update_state(config, {"llm_model": "gpt-4o-mini"})
191
- #########################################################
192
- # In this case, we will test if the tool returns an error
193
- # when the model does not achieve a steady state. The tool
194
- # status should be "error".
195
- prompt = """Run a steady state analysis of model 537."""
196
- # Invoke the agent
197
- app.invoke(
198
- {"messages": [HumanMessage(content=prompt)]},
199
- config=config
200
- )
201
- current_state = app.get_state(config)
202
- reversed_messages = current_state.values["messages"][::-1]
203
- tool_msg_status = None
204
- for msg in reversed_messages:
205
- # Assert that the status of the
206
- # ToolMessage is "error"
207
- if isinstance(msg, ToolMessage):
208
- # print (msg)
209
- tool_msg_status = msg.status
210
- break
211
- assert tool_msg_status == "error"
212
- #########################################################
213
- # In this case, we will test if the tool is indeed invoked
214
- # successfully
215
- prompt = """Run a steady state analysis of model 64.
216
- Set the initial concentration of `Pyruvate` to 0.2. The
217
- concentration of `NAD` resets to 100 every 2 time units."""
218
- # Invoke the agent
219
- app.invoke(
220
- {"messages": [HumanMessage(content=prompt)]},
221
- config=config
222
- )
223
- # Loop through the reversed messages until a
224
- # ToolMessage is found.
225
- current_state = app.get_state(config)
226
- reversed_messages = current_state.values["messages"][::-1]
227
- steady_state_invoked = False
228
- for msg in reversed_messages:
229
- # Assert that the message is a ToolMessage
230
- # and its status is "error"
231
- if isinstance(msg, ToolMessage):
232
- print (msg)
233
- if msg.name == "steady_state" and msg.status != "error":
234
- steady_state_invoked = True
235
- break
236
- assert steady_state_invoked
237
- #########################################################
238
- # In this case, we will test if the `ask_question` tool is
239
- # invoked upon asking a question about the already generated
240
- # steady state results
241
- prompt = """What is the Phosphoenolpyruvate concentration
242
- at the steady state? Show onlyconcentration, rounded to
243
- 2 decimal places. For example, if the concentration is
244
- 0.123456, your response should be `0.12`. Do not return
245
- any other information."""
246
- # Invoke the agent
247
- response = app.invoke(
248
- {"messages": [HumanMessage(content=prompt)]},
249
- config=config
250
- )
251
- assistant_msg = response["messages"][-1].content
252
- current_state = app.get_state(config)
253
- reversed_messages = current_state.values["messages"][::-1]
254
- # Loop through the reversed messages until a
255
- # ToolMessage is found.
256
- ask_questool_invoked = False
257
- for msg in reversed_messages:
258
- # Assert that the message is a ToolMessage
259
- # and its status is "error"
260
- if isinstance(msg, ToolMessage):
261
- if msg.name == "ask_question":
262
- ask_questool_invoked = True
263
- break
264
- assert ask_questool_invoked
265
- assert "0.06" in assistant_msg
266
-
267
- def test_integration():
268
- '''
269
- Test the integration of the tools.
270
- '''
271
- unique_id = 1234567
272
- app = get_app(unique_id)
273
- config = {"configurable": {"thread_id": unique_id}}
274
- app.update_state(config, {"llm_model": "gpt-4o-mini"})
275
- # ##########################################
276
- # ## Test simulate_model tool
277
- # ##########################################
278
- prompt = "Simulate the model 537 for 2016 hours and intervals"
279
- prompt += " 2016 with an initial concentration of `DoseQ2W` "
280
- prompt += "set to 300 and `Dose` set to 0. Reset the concentration"
281
- prompt += " of `NAD` to 100 every 500 hours."
282
- # Test the tool get_modelinfo
283
- response = app.invoke(
284
- {"messages": [HumanMessage(content=prompt)]},
285
- config=config
286
- )
287
- assistant_msg = response["messages"][-1].content
288
- print (assistant_msg)
289
- # Check if the assistant message is a string
290
- assert isinstance(assistant_msg, str)
291
- ##########################################
292
- # Test ask_question tool when simulation
293
- # results are available
294
- ##########################################
295
- # Update state
296
- app.update_state(config, {"llm_model": "gpt-4o-mini"})
297
- prompt = """What is the concentration of CRP
298
- in serum after 1000 time points?"""
299
- # Test the tool get_modelinfo
300
- response = app.invoke(
301
- {"messages": [HumanMessage(content=prompt)]},
302
- config=config
303
- )
304
- assistant_msg = response["messages"][-1].content
305
- # print (assistant_msg)
306
- # Check if the assistant message is a string
307
- assert "1.7" in assistant_msg
308
-
309
- ##########################################
310
- # Test custom_plotter tool when the
311
- # simulation results are available
312
- ##########################################
313
- prompt = "Plot only CRP related species."
314
-
315
- # Update state
316
- app.update_state(config, {"llm_model": "gpt-4o-mini"}
317
- )
318
- # Test the tool get_modelinfo
319
- response = app.invoke(
320
- {"messages": [HumanMessage(content=prompt)]},
321
- config=config
322
- )
323
- assistant_msg = response["messages"][-1].content
324
- current_state = app.get_state(config)
325
- # Get the messages from the current state
326
- # and reverse the order
327
- reversed_messages = current_state.values["messages"][::-1]
328
- # Loop through the reversed messages
329
- # until a ToolMessage is found.
330
- expected_header = ['Time', 'CRP{serum}', 'CRPExtracellular']
331
- expected_header += ['CRP Suppression (%)', 'CRP (% of baseline)']
332
- expected_header += ['CRP{liver}']
333
- predicted_artifact = []
334
- for msg in reversed_messages:
335
- if isinstance(msg, ToolMessage):
336
- # Work on the message if it is a ToolMessage
337
- # These may contain additional visuals that
338
- # need to be displayed to the user.
339
- if msg.name == "custom_plotter":
340
- predicted_artifact = msg.artifact
341
- break
342
- # Convert the artifact into a pandas dataframe
343
- # for easy comparison
344
- df = pd.DataFrame(predicted_artifact)
345
- # Extract the headers from the dataframe
346
- predicted_header = df.columns.tolist()
347
- # Check if the header is in the expected_header
348
- # assert expected_header in predicted_artifact
349
- assert set(expected_header).issubset(set(predicted_header))
350
- ##########################################
351
- # Test custom_plotter tool when the
352
- # simulation results are available but
353
- # the species is not available
354
- ##########################################
355
- prompt = """Make a custom plot showing the
356
- concentration of the species `TP53` over
357
- time. Do not show any other species."""
358
- # Update state
359
- app.update_state(config, {"llm_model": "gpt-4o-mini"}
360
- )
361
- # Test the tool get_modelinfo
362
- response = app.invoke(
363
- {"messages": [HumanMessage(content=prompt)]},
364
- config=config
365
- )
366
- assistant_msg = response["messages"][-1].content
367
- # print (response["messages"])
368
- current_state = app.get_state(config)
369
- # Get the messages from the current state
370
- # and reverse the order
371
- reversed_messages = current_state.values["messages"][::-1]
372
- # Loop through the reversed messages until a
373
- # ToolMessage is found.
374
- predicted_artifact = []
375
- for msg in reversed_messages:
376
- if isinstance(msg, ToolMessage):
377
- # Work on the message if it is a ToolMessage
378
- # These may contain additional visuals that
379
- # need to be displayed to the user.
380
- if msg.name == "custom_plotter":
381
- predicted_artifact = msg.artifact
382
- break
383
- # Check if the the predicted artifact is `None`
384
- assert predicted_artifact is None