aiagents4pharma 1.43.0__py3-none-any.whl → 1.45.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.
Files changed (290) hide show
  1. aiagents4pharma/__init__.py +2 -2
  2. aiagents4pharma/talk2aiagents4pharma/.dockerignore +13 -0
  3. aiagents4pharma/talk2aiagents4pharma/Dockerfile +105 -0
  4. aiagents4pharma/talk2aiagents4pharma/README.md +1 -0
  5. aiagents4pharma/talk2aiagents4pharma/__init__.py +4 -5
  6. aiagents4pharma/talk2aiagents4pharma/agents/__init__.py +3 -2
  7. aiagents4pharma/talk2aiagents4pharma/agents/main_agent.py +24 -23
  8. aiagents4pharma/talk2aiagents4pharma/configs/__init__.py +2 -2
  9. aiagents4pharma/talk2aiagents4pharma/configs/agents/__init__.py +2 -2
  10. aiagents4pharma/talk2aiagents4pharma/configs/agents/main_agent/default.yaml +2 -2
  11. aiagents4pharma/talk2aiagents4pharma/configs/config.yaml +1 -1
  12. aiagents4pharma/talk2aiagents4pharma/docker-compose/cpu/.env.example +23 -0
  13. aiagents4pharma/talk2aiagents4pharma/docker-compose/cpu/docker-compose.yml +93 -0
  14. aiagents4pharma/talk2aiagents4pharma/docker-compose/gpu/.env.example +23 -0
  15. aiagents4pharma/talk2aiagents4pharma/docker-compose/gpu/docker-compose.yml +108 -0
  16. aiagents4pharma/talk2aiagents4pharma/install.md +127 -0
  17. aiagents4pharma/talk2aiagents4pharma/states/__init__.py +3 -2
  18. aiagents4pharma/talk2aiagents4pharma/states/state_talk2aiagents4pharma.py +5 -3
  19. aiagents4pharma/talk2aiagents4pharma/tests/__init__.py +2 -2
  20. aiagents4pharma/talk2aiagents4pharma/tests/test_main_agent.py +72 -50
  21. aiagents4pharma/talk2biomodels/.dockerignore +13 -0
  22. aiagents4pharma/talk2biomodels/Dockerfile +104 -0
  23. aiagents4pharma/talk2biomodels/README.md +1 -0
  24. aiagents4pharma/talk2biomodels/__init__.py +4 -8
  25. aiagents4pharma/talk2biomodels/agents/__init__.py +3 -2
  26. aiagents4pharma/talk2biomodels/agents/t2b_agent.py +47 -42
  27. aiagents4pharma/talk2biomodels/api/__init__.py +4 -5
  28. aiagents4pharma/talk2biomodels/api/kegg.py +14 -10
  29. aiagents4pharma/talk2biomodels/api/ols.py +13 -10
  30. aiagents4pharma/talk2biomodels/api/uniprot.py +7 -6
  31. aiagents4pharma/talk2biomodels/configs/__init__.py +3 -4
  32. aiagents4pharma/talk2biomodels/configs/agents/__init__.py +2 -2
  33. aiagents4pharma/talk2biomodels/configs/agents/t2b_agent/__init__.py +2 -2
  34. aiagents4pharma/talk2biomodels/configs/agents/t2b_agent/default.yaml +1 -1
  35. aiagents4pharma/talk2biomodels/configs/config.yaml +1 -1
  36. aiagents4pharma/talk2biomodels/configs/tools/__init__.py +4 -5
  37. aiagents4pharma/talk2biomodels/configs/tools/ask_question/__init__.py +2 -2
  38. aiagents4pharma/talk2biomodels/configs/tools/ask_question/default.yaml +1 -2
  39. aiagents4pharma/talk2biomodels/configs/tools/custom_plotter/__init__.py +2 -2
  40. aiagents4pharma/talk2biomodels/configs/tools/custom_plotter/default.yaml +1 -1
  41. aiagents4pharma/talk2biomodels/configs/tools/get_annotation/__init__.py +2 -2
  42. aiagents4pharma/talk2biomodels/configs/tools/get_annotation/default.yaml +1 -1
  43. aiagents4pharma/talk2biomodels/install.md +63 -0
  44. aiagents4pharma/talk2biomodels/models/__init__.py +4 -4
  45. aiagents4pharma/talk2biomodels/models/basico_model.py +36 -28
  46. aiagents4pharma/talk2biomodels/models/sys_bio_model.py +13 -10
  47. aiagents4pharma/talk2biomodels/states/__init__.py +3 -2
  48. aiagents4pharma/talk2biomodels/states/state_talk2biomodels.py +12 -8
  49. aiagents4pharma/talk2biomodels/tests/BIOMD0000000449_url.xml +1585 -0
  50. aiagents4pharma/talk2biomodels/tests/__init__.py +2 -2
  51. aiagents4pharma/talk2biomodels/tests/article_on_model_537.pdf +0 -0
  52. aiagents4pharma/talk2biomodels/tests/test_api.py +18 -14
  53. aiagents4pharma/talk2biomodels/tests/test_ask_question.py +8 -9
  54. aiagents4pharma/talk2biomodels/tests/test_basico_model.py +15 -9
  55. aiagents4pharma/talk2biomodels/tests/test_get_annotation.py +54 -55
  56. aiagents4pharma/talk2biomodels/tests/test_getmodelinfo.py +28 -27
  57. aiagents4pharma/talk2biomodels/tests/test_integration.py +21 -33
  58. aiagents4pharma/talk2biomodels/tests/test_load_biomodel.py +14 -11
  59. aiagents4pharma/talk2biomodels/tests/test_param_scan.py +21 -20
  60. aiagents4pharma/talk2biomodels/tests/test_query_article.py +129 -29
  61. aiagents4pharma/talk2biomodels/tests/test_search_models.py +9 -13
  62. aiagents4pharma/talk2biomodels/tests/test_simulate_model.py +16 -15
  63. aiagents4pharma/talk2biomodels/tests/test_steady_state.py +12 -22
  64. aiagents4pharma/talk2biomodels/tests/test_sys_bio_model.py +33 -29
  65. aiagents4pharma/talk2biomodels/tools/__init__.py +15 -12
  66. aiagents4pharma/talk2biomodels/tools/ask_question.py +42 -32
  67. aiagents4pharma/talk2biomodels/tools/custom_plotter.py +51 -43
  68. aiagents4pharma/talk2biomodels/tools/get_annotation.py +99 -75
  69. aiagents4pharma/talk2biomodels/tools/get_modelinfo.py +57 -51
  70. aiagents4pharma/talk2biomodels/tools/load_arguments.py +52 -32
  71. aiagents4pharma/talk2biomodels/tools/load_biomodel.py +8 -2
  72. aiagents4pharma/talk2biomodels/tools/parameter_scan.py +107 -90
  73. aiagents4pharma/talk2biomodels/tools/query_article.py +14 -13
  74. aiagents4pharma/talk2biomodels/tools/search_models.py +37 -26
  75. aiagents4pharma/talk2biomodels/tools/simulate_model.py +47 -37
  76. aiagents4pharma/talk2biomodels/tools/steady_state.py +76 -58
  77. aiagents4pharma/talk2biomodels/tools/utils.py +4 -3
  78. aiagents4pharma/talk2cells/README.md +1 -0
  79. aiagents4pharma/talk2cells/__init__.py +4 -5
  80. aiagents4pharma/talk2cells/agents/__init__.py +3 -2
  81. aiagents4pharma/talk2cells/agents/scp_agent.py +21 -19
  82. aiagents4pharma/talk2cells/states/__init__.py +3 -2
  83. aiagents4pharma/talk2cells/states/state_talk2cells.py +4 -2
  84. aiagents4pharma/talk2cells/tests/scp_agent/test_scp_agent.py +8 -9
  85. aiagents4pharma/talk2cells/tools/__init__.py +3 -2
  86. aiagents4pharma/talk2cells/tools/scp_agent/__init__.py +4 -4
  87. aiagents4pharma/talk2cells/tools/scp_agent/display_studies.py +5 -3
  88. aiagents4pharma/talk2cells/tools/scp_agent/search_studies.py +21 -22
  89. aiagents4pharma/talk2knowledgegraphs/.dockerignore +13 -0
  90. aiagents4pharma/talk2knowledgegraphs/Dockerfile +103 -0
  91. aiagents4pharma/talk2knowledgegraphs/README.md +1 -0
  92. aiagents4pharma/talk2knowledgegraphs/__init__.py +4 -7
  93. aiagents4pharma/talk2knowledgegraphs/agents/__init__.py +3 -2
  94. aiagents4pharma/talk2knowledgegraphs/agents/t2kg_agent.py +40 -30
  95. aiagents4pharma/talk2knowledgegraphs/configs/__init__.py +3 -6
  96. aiagents4pharma/talk2knowledgegraphs/configs/agents/t2kg_agent/__init__.py +2 -2
  97. aiagents4pharma/talk2knowledgegraphs/configs/agents/t2kg_agent/default.yaml +8 -8
  98. aiagents4pharma/talk2knowledgegraphs/configs/app/__init__.py +3 -2
  99. aiagents4pharma/talk2knowledgegraphs/configs/app/frontend/__init__.py +2 -2
  100. aiagents4pharma/talk2knowledgegraphs/configs/app/frontend/default.yaml +1 -1
  101. aiagents4pharma/talk2knowledgegraphs/configs/config.yaml +1 -1
  102. aiagents4pharma/talk2knowledgegraphs/configs/tools/__init__.py +4 -5
  103. aiagents4pharma/talk2knowledgegraphs/configs/tools/graphrag_reasoning/__init__.py +2 -2
  104. aiagents4pharma/talk2knowledgegraphs/configs/tools/graphrag_reasoning/default.yaml +1 -1
  105. aiagents4pharma/talk2knowledgegraphs/configs/tools/multimodal_subgraph_extraction/default.yaml +17 -2
  106. aiagents4pharma/talk2knowledgegraphs/configs/tools/subgraph_extraction/__init__.py +2 -2
  107. aiagents4pharma/talk2knowledgegraphs/configs/tools/subgraph_extraction/default.yaml +1 -1
  108. aiagents4pharma/talk2knowledgegraphs/configs/tools/subgraph_summarization/__init__.py +2 -2
  109. aiagents4pharma/talk2knowledgegraphs/configs/tools/subgraph_summarization/default.yaml +1 -1
  110. aiagents4pharma/talk2knowledgegraphs/configs/utils/enrichments/ols_terms/default.yaml +1 -1
  111. aiagents4pharma/talk2knowledgegraphs/configs/utils/enrichments/reactome_pathways/default.yaml +1 -1
  112. aiagents4pharma/talk2knowledgegraphs/configs/utils/enrichments/uniprot_proteins/default.yaml +1 -1
  113. aiagents4pharma/talk2knowledgegraphs/configs/utils/pubchem_utils/default.yaml +1 -1
  114. aiagents4pharma/talk2knowledgegraphs/datasets/__init__.py +4 -6
  115. aiagents4pharma/talk2knowledgegraphs/datasets/biobridge_primekg.py +115 -67
  116. aiagents4pharma/talk2knowledgegraphs/datasets/dataset.py +2 -0
  117. aiagents4pharma/talk2knowledgegraphs/datasets/primekg.py +35 -24
  118. aiagents4pharma/talk2knowledgegraphs/datasets/starkqa_primekg.py +29 -21
  119. aiagents4pharma/talk2knowledgegraphs/docker-compose/cpu/.env.example +23 -0
  120. aiagents4pharma/talk2knowledgegraphs/docker-compose/cpu/docker-compose.yml +93 -0
  121. aiagents4pharma/talk2knowledgegraphs/docker-compose/gpu/.env.example +23 -0
  122. aiagents4pharma/talk2knowledgegraphs/docker-compose/gpu/docker-compose.yml +108 -0
  123. aiagents4pharma/talk2knowledgegraphs/entrypoint.sh +190 -0
  124. aiagents4pharma/talk2knowledgegraphs/install.md +140 -0
  125. aiagents4pharma/talk2knowledgegraphs/milvus_data_dump.py +31 -65
  126. aiagents4pharma/talk2knowledgegraphs/states/__init__.py +3 -2
  127. aiagents4pharma/talk2knowledgegraphs/states/state_talk2knowledgegraphs.py +1 -0
  128. aiagents4pharma/talk2knowledgegraphs/tests/test_agents_t2kg_agent.py +65 -40
  129. aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_biobridge_primekg.py +54 -48
  130. aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_dataset.py +4 -0
  131. aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_primekg.py +17 -4
  132. aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_starkqa_primekg.py +33 -24
  133. aiagents4pharma/talk2knowledgegraphs/tests/test_tools_graphrag_reasoning.py +116 -69
  134. aiagents4pharma/talk2knowledgegraphs/tests/test_tools_milvus_multimodal_subgraph_extraction.py +736 -413
  135. aiagents4pharma/talk2knowledgegraphs/tests/test_tools_multimodal_subgraph_extraction.py +22 -15
  136. aiagents4pharma/talk2knowledgegraphs/tests/test_tools_subgraph_extraction.py +19 -12
  137. aiagents4pharma/talk2knowledgegraphs/tests/test_tools_subgraph_summarization.py +95 -48
  138. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_embeddings.py +4 -0
  139. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_huggingface.py +5 -0
  140. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_nim_molmim.py +13 -18
  141. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_ollama.py +10 -3
  142. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_enrichments.py +4 -3
  143. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_ollama.py +3 -2
  144. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_ols.py +1 -0
  145. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_pubchem.py +9 -4
  146. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_reactome.py +6 -6
  147. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_uniprot.py +4 -0
  148. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_extractions_milvus_multimodal_pcst.py +442 -42
  149. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_kg_utils.py +3 -4
  150. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_pubchem_utils.py +10 -6
  151. aiagents4pharma/talk2knowledgegraphs/tools/__init__.py +10 -7
  152. aiagents4pharma/talk2knowledgegraphs/tools/graphrag_reasoning.py +15 -20
  153. aiagents4pharma/talk2knowledgegraphs/tools/milvus_multimodal_subgraph_extraction.py +245 -205
  154. aiagents4pharma/talk2knowledgegraphs/tools/multimodal_subgraph_extraction.py +92 -90
  155. aiagents4pharma/talk2knowledgegraphs/tools/subgraph_extraction.py +25 -37
  156. aiagents4pharma/talk2knowledgegraphs/tools/subgraph_summarization.py +10 -13
  157. aiagents4pharma/talk2knowledgegraphs/utils/__init__.py +4 -7
  158. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/__init__.py +4 -7
  159. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/embeddings.py +4 -0
  160. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/huggingface.py +11 -14
  161. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/nim_molmim.py +7 -7
  162. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/ollama.py +12 -6
  163. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/sentence_transformer.py +8 -6
  164. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/__init__.py +9 -6
  165. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/enrichments.py +1 -0
  166. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/ollama.py +15 -9
  167. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/ols_terms.py +23 -20
  168. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/pubchem_strings.py +12 -10
  169. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/reactome_pathways.py +16 -10
  170. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/uniprot_proteins.py +26 -18
  171. aiagents4pharma/talk2knowledgegraphs/utils/extractions/__init__.py +4 -5
  172. aiagents4pharma/talk2knowledgegraphs/utils/extractions/milvus_multimodal_pcst.py +218 -81
  173. aiagents4pharma/talk2knowledgegraphs/utils/extractions/multimodal_pcst.py +53 -47
  174. aiagents4pharma/talk2knowledgegraphs/utils/extractions/pcst.py +18 -14
  175. aiagents4pharma/talk2knowledgegraphs/utils/kg_utils.py +22 -23
  176. aiagents4pharma/talk2knowledgegraphs/utils/pubchem_utils.py +11 -10
  177. aiagents4pharma/talk2scholars/.dockerignore +13 -0
  178. aiagents4pharma/talk2scholars/Dockerfile +104 -0
  179. aiagents4pharma/talk2scholars/README.md +1 -0
  180. aiagents4pharma/talk2scholars/agents/__init__.py +1 -5
  181. aiagents4pharma/talk2scholars/agents/main_agent.py +6 -4
  182. aiagents4pharma/talk2scholars/agents/paper_download_agent.py +5 -4
  183. aiagents4pharma/talk2scholars/agents/pdf_agent.py +4 -2
  184. aiagents4pharma/talk2scholars/agents/s2_agent.py +2 -2
  185. aiagents4pharma/talk2scholars/agents/zotero_agent.py +10 -11
  186. aiagents4pharma/talk2scholars/configs/__init__.py +1 -3
  187. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/__init__.py +1 -4
  188. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/main_agent/default.yaml +1 -1
  189. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/pdf_agent/default.yaml +1 -1
  190. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/s2_agent/default.yaml +8 -8
  191. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/zotero_agent/default.yaml +7 -7
  192. aiagents4pharma/talk2scholars/configs/tools/__init__.py +8 -6
  193. aiagents4pharma/talk2scholars/docker-compose/cpu/.env.example +21 -0
  194. aiagents4pharma/talk2scholars/docker-compose/cpu/docker-compose.yml +90 -0
  195. aiagents4pharma/talk2scholars/docker-compose/gpu/.env.example +21 -0
  196. aiagents4pharma/talk2scholars/docker-compose/gpu/docker-compose.yml +105 -0
  197. aiagents4pharma/talk2scholars/install.md +122 -0
  198. aiagents4pharma/talk2scholars/state/state_talk2scholars.py +8 -8
  199. aiagents4pharma/talk2scholars/tests/{test_main_agent.py → test_agents_main_agent.py} +41 -23
  200. aiagents4pharma/talk2scholars/tests/{test_paper_download_agent.py → test_agents_paper_agents_download_agent.py} +10 -16
  201. aiagents4pharma/talk2scholars/tests/{test_pdf_agent.py → test_agents_pdf_agent.py} +6 -10
  202. aiagents4pharma/talk2scholars/tests/{test_s2_agent.py → test_agents_s2_agent.py} +8 -16
  203. aiagents4pharma/talk2scholars/tests/{test_zotero_agent.py → test_agents_zotero_agent.py} +5 -7
  204. aiagents4pharma/talk2scholars/tests/{test_s2_display_dataframe.py → test_s2_tools_display_dataframe.py} +6 -7
  205. aiagents4pharma/talk2scholars/tests/{test_s2_query_dataframe.py → test_s2_tools_query_dataframe.py} +5 -15
  206. aiagents4pharma/talk2scholars/tests/{test_paper_downloader.py → test_tools_paper_downloader.py} +25 -63
  207. aiagents4pharma/talk2scholars/tests/{test_question_and_answer_tool.py → test_tools_question_and_answer_tool.py} +2 -6
  208. aiagents4pharma/talk2scholars/tests/{test_s2_multi.py → test_tools_s2_multi.py} +5 -5
  209. aiagents4pharma/talk2scholars/tests/{test_s2_retrieve.py → test_tools_s2_retrieve.py} +2 -1
  210. aiagents4pharma/talk2scholars/tests/{test_s2_search.py → test_tools_s2_search.py} +5 -5
  211. aiagents4pharma/talk2scholars/tests/{test_s2_single.py → test_tools_s2_single.py} +5 -5
  212. aiagents4pharma/talk2scholars/tests/{test_arxiv_downloader.py → test_utils_arxiv_downloader.py} +16 -25
  213. aiagents4pharma/talk2scholars/tests/{test_base_paper_downloader.py → test_utils_base_paper_downloader.py} +25 -47
  214. aiagents4pharma/talk2scholars/tests/{test_biorxiv_downloader.py → test_utils_biorxiv_downloader.py} +14 -42
  215. aiagents4pharma/talk2scholars/tests/{test_medrxiv_downloader.py → test_utils_medrxiv_downloader.py} +15 -49
  216. aiagents4pharma/talk2scholars/tests/{test_nvidia_nim_reranker.py → test_utils_nvidia_nim_reranker.py} +6 -16
  217. aiagents4pharma/talk2scholars/tests/{test_pdf_answer_formatter.py → test_utils_pdf_answer_formatter.py} +1 -0
  218. aiagents4pharma/talk2scholars/tests/{test_pdf_batch_processor.py → test_utils_pdf_batch_processor.py} +6 -15
  219. aiagents4pharma/talk2scholars/tests/{test_pdf_collection_manager.py → test_utils_pdf_collection_manager.py} +34 -11
  220. aiagents4pharma/talk2scholars/tests/{test_pdf_document_processor.py → test_utils_pdf_document_processor.py} +2 -3
  221. aiagents4pharma/talk2scholars/tests/{test_pdf_generate_answer.py → test_utils_pdf_generate_answer.py} +3 -6
  222. aiagents4pharma/talk2scholars/tests/{test_pdf_gpu_detection.py → test_utils_pdf_gpu_detection.py} +5 -16
  223. aiagents4pharma/talk2scholars/tests/{test_pdf_rag_pipeline.py → test_utils_pdf_rag_pipeline.py} +7 -17
  224. aiagents4pharma/talk2scholars/tests/{test_pdf_retrieve_chunks.py → test_utils_pdf_retrieve_chunks.py} +4 -11
  225. aiagents4pharma/talk2scholars/tests/{test_pdf_singleton_manager.py → test_utils_pdf_singleton_manager.py} +26 -23
  226. aiagents4pharma/talk2scholars/tests/{test_pdf_vector_normalization.py → test_utils_pdf_vector_normalization.py} +1 -1
  227. aiagents4pharma/talk2scholars/tests/{test_pdf_vector_store.py → test_utils_pdf_vector_store.py} +27 -55
  228. aiagents4pharma/talk2scholars/tests/{test_pubmed_downloader.py → test_utils_pubmed_downloader.py} +31 -91
  229. aiagents4pharma/talk2scholars/tests/{test_read_helper_utils.py → test_utils_read_helper_utils.py} +2 -6
  230. aiagents4pharma/talk2scholars/tests/{test_s2_utils_ext_ids.py → test_utils_s2_utils_ext_ids.py} +5 -15
  231. aiagents4pharma/talk2scholars/tests/{test_zotero_human_in_the_loop.py → test_utils_zotero_human_in_the_loop.py} +6 -13
  232. aiagents4pharma/talk2scholars/tests/{test_zotero_path.py → test_utils_zotero_path.py} +53 -45
  233. aiagents4pharma/talk2scholars/tests/{test_zotero_read.py → test_utils_zotero_read.py} +30 -91
  234. aiagents4pharma/talk2scholars/tests/{test_zotero_write.py → test_utils_zotero_write.py} +6 -16
  235. aiagents4pharma/talk2scholars/tools/__init__.py +1 -4
  236. aiagents4pharma/talk2scholars/tools/paper_download/paper_downloader.py +20 -35
  237. aiagents4pharma/talk2scholars/tools/paper_download/utils/__init__.py +7 -5
  238. aiagents4pharma/talk2scholars/tools/paper_download/utils/arxiv_downloader.py +9 -11
  239. aiagents4pharma/talk2scholars/tools/paper_download/utils/base_paper_downloader.py +14 -21
  240. aiagents4pharma/talk2scholars/tools/paper_download/utils/biorxiv_downloader.py +14 -22
  241. aiagents4pharma/talk2scholars/tools/paper_download/utils/medrxiv_downloader.py +11 -13
  242. aiagents4pharma/talk2scholars/tools/paper_download/utils/pubmed_downloader.py +14 -28
  243. aiagents4pharma/talk2scholars/tools/pdf/question_and_answer.py +4 -8
  244. aiagents4pharma/talk2scholars/tools/pdf/utils/__init__.py +16 -14
  245. aiagents4pharma/talk2scholars/tools/pdf/utils/answer_formatter.py +4 -4
  246. aiagents4pharma/talk2scholars/tools/pdf/utils/batch_processor.py +15 -17
  247. aiagents4pharma/talk2scholars/tools/pdf/utils/collection_manager.py +2 -2
  248. aiagents4pharma/talk2scholars/tools/pdf/utils/document_processor.py +5 -5
  249. aiagents4pharma/talk2scholars/tools/pdf/utils/generate_answer.py +4 -4
  250. aiagents4pharma/talk2scholars/tools/pdf/utils/get_vectorstore.py +2 -6
  251. aiagents4pharma/talk2scholars/tools/pdf/utils/gpu_detection.py +5 -9
  252. aiagents4pharma/talk2scholars/tools/pdf/utils/nvidia_nim_reranker.py +4 -4
  253. aiagents4pharma/talk2scholars/tools/pdf/utils/paper_loader.py +2 -2
  254. aiagents4pharma/talk2scholars/tools/pdf/utils/rag_pipeline.py +6 -15
  255. aiagents4pharma/talk2scholars/tools/pdf/utils/retrieve_chunks.py +7 -15
  256. aiagents4pharma/talk2scholars/tools/pdf/utils/singleton_manager.py +2 -2
  257. aiagents4pharma/talk2scholars/tools/pdf/utils/tool_helper.py +3 -4
  258. aiagents4pharma/talk2scholars/tools/pdf/utils/vector_normalization.py +8 -17
  259. aiagents4pharma/talk2scholars/tools/pdf/utils/vector_store.py +17 -33
  260. aiagents4pharma/talk2scholars/tools/s2/__init__.py +8 -6
  261. aiagents4pharma/talk2scholars/tools/s2/display_dataframe.py +3 -7
  262. aiagents4pharma/talk2scholars/tools/s2/multi_paper_rec.py +7 -6
  263. aiagents4pharma/talk2scholars/tools/s2/query_dataframe.py +5 -12
  264. aiagents4pharma/talk2scholars/tools/s2/retrieve_semantic_scholar_paper_id.py +2 -4
  265. aiagents4pharma/talk2scholars/tools/s2/search.py +6 -6
  266. aiagents4pharma/talk2scholars/tools/s2/single_paper_rec.py +5 -3
  267. aiagents4pharma/talk2scholars/tools/s2/utils/__init__.py +1 -3
  268. aiagents4pharma/talk2scholars/tools/s2/utils/multi_helper.py +12 -18
  269. aiagents4pharma/talk2scholars/tools/s2/utils/search_helper.py +11 -18
  270. aiagents4pharma/talk2scholars/tools/s2/utils/single_helper.py +11 -16
  271. aiagents4pharma/talk2scholars/tools/zotero/__init__.py +1 -4
  272. aiagents4pharma/talk2scholars/tools/zotero/utils/__init__.py +1 -4
  273. aiagents4pharma/talk2scholars/tools/zotero/utils/read_helper.py +21 -39
  274. aiagents4pharma/talk2scholars/tools/zotero/utils/review_helper.py +2 -6
  275. aiagents4pharma/talk2scholars/tools/zotero/utils/write_helper.py +8 -11
  276. aiagents4pharma/talk2scholars/tools/zotero/utils/zotero_path.py +4 -12
  277. aiagents4pharma/talk2scholars/tools/zotero/utils/zotero_pdf_downloader.py +13 -27
  278. aiagents4pharma/talk2scholars/tools/zotero/zotero_read.py +4 -7
  279. aiagents4pharma/talk2scholars/tools/zotero/zotero_review.py +8 -10
  280. aiagents4pharma/talk2scholars/tools/zotero/zotero_write.py +3 -2
  281. {aiagents4pharma-1.43.0.dist-info → aiagents4pharma-1.45.0.dist-info}/METADATA +115 -50
  282. aiagents4pharma-1.45.0.dist-info/RECORD +324 -0
  283. {aiagents4pharma-1.43.0.dist-info → aiagents4pharma-1.45.0.dist-info}/WHEEL +1 -2
  284. aiagents4pharma-1.43.0.dist-info/RECORD +0 -293
  285. aiagents4pharma-1.43.0.dist-info/top_level.txt +0 -1
  286. /aiagents4pharma/talk2scholars/tests/{test_state.py → test_states_state.py} +0 -0
  287. /aiagents4pharma/talk2scholars/tests/{test_pdf_paper_loader.py → test_utils_pdf_paper_loader.py} +0 -0
  288. /aiagents4pharma/talk2scholars/tests/{test_tool_helper_utils.py → test_utils_tool_helper_utils.py} +0 -0
  289. /aiagents4pharma/talk2scholars/tests/{test_zotero_pdf_downloader_utils.py → test_utils_zotero_pdf_downloader_utils.py} +0 -0
  290. {aiagents4pharma-1.43.0.dist-info → aiagents4pharma-1.45.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,413 +1,736 @@
1
- """
2
- Test cases for tools/milvus_multimodal_subgraph_extraction.py
3
- """
4
-
5
- import importlib
6
- import unittest
7
- from unittest.mock import patch, MagicMock
8
- import numpy as np
9
- import pandas as pd
10
- from ..tools.milvus_multimodal_subgraph_extraction import MultimodalSubgraphExtractionTool
11
-
12
- class TestMultimodalSubgraphExtractionTool(unittest.TestCase):
13
- """
14
- Test cases for MultimodalSubgraphExtractionTool (Milvus)
15
- """
16
- def setUp(self):
17
- self.tool = MultimodalSubgraphExtractionTool()
18
- self.state = {
19
- "uploaded_files": [],
20
- "embedding_model": MagicMock(),
21
- "topk_nodes": 5,
22
- "topk_edges": 5,
23
- "dic_source_graph": [{"name": "TestGraph"}],
24
- }
25
- self.prompt = "Find subgraph for test"
26
- self.arg_data = {"extraction_name": "subkg_12345"}
27
- self.cfg_db = MagicMock()
28
- self.cfg_db.milvus_db.database_name = "testdb"
29
- self.cfg_db.milvus_db.alias = "default"
30
- self.cfg = MagicMock()
31
- self.cfg.cost_e = 1.0
32
- self.cfg.c_const = 1.0
33
- self.cfg.root = 0
34
- self.cfg.num_clusters = 1
35
- self.cfg.pruning = True
36
- self.cfg.verbosity_level = 0
37
- self.cfg.search_metric_type = "L2"
38
- self.cfg.node_colors_dict = {"gene/protein": "red"}
39
-
40
- @patch("aiagents4pharma.talk2knowledgegraphs.tools."
41
- "milvus_multimodal_subgraph_extraction.Collection")
42
- @patch("aiagents4pharma.talk2knowledgegraphs.tools."
43
- "milvus_multimodal_subgraph_extraction.MultimodalPCSTPruning")
44
- @patch("pymilvus.connections")
45
- def test_extract_multimodal_subgraph_wo_doc(self,
46
- mock_connections,
47
- mock_pcst,
48
- mock_collection):
49
- """
50
- Test the multimodal subgraph extraction tool for only text as modality.
51
- """
52
-
53
- # Mock Milvus connection utilities
54
- mock_connections.has_connection.return_value = True
55
-
56
- # No uploaded_files (no doc)
57
- self.state["uploaded_files"] = []
58
- self.state["embedding_model"].embed_query.return_value = [0.1, 0.2, 0.3]
59
- self.state["selections"] = {}
60
-
61
- # Mock Collection for nodes and edges
62
- colls = {}
63
- colls["nodes"] = MagicMock()
64
- colls["nodes"] = MagicMock()
65
- colls["nodes"].query.return_value = [
66
- {"node_index": 0,
67
- "node_id": "id1",
68
- "node_name": "JAK1",
69
- "node_type": "gene/protein",
70
- "feat": "featA",
71
- "feat_emb": [0.1, 0.2, 0.3],
72
- "desc": "descA",
73
- "desc_emb": [0.1, 0.2, 0.3]},
74
- {"node_index": 1,
75
- "node_id": "id2",
76
- "node_name": "JAK2",
77
- "node_type": "gene/protein",
78
- "feat": "featB",
79
- "feat_emb": [0.4, 0.5, 0.6],
80
- "desc": "descB",
81
- "desc_emb": [0.4, 0.5, 0.6]}
82
- ]
83
- colls["nodes"].load.return_value = None
84
-
85
- colls["edges"] = MagicMock()
86
- colls["edges"].query.return_value = [
87
- {"triplet_index": 0,
88
- "head_id": "id1",
89
- "head_index": 0,
90
- "tail_id": "id2",
91
- "tail_index": 1,
92
- "edge_type": "gene/protein,ppi,gene/protein",
93
- "display_relation": "ppi",
94
- "feat": "featC",
95
- "feat_emb": [0.7, 0.8, 0.9]}
96
- ]
97
- colls["edges"].load.return_value = None
98
-
99
- def collection_side_effect(name):
100
- """
101
- Mock side effect for Collection to return nodes or edges based on name.
102
- """
103
- if "nodes" in name:
104
- return colls["nodes"]
105
- if "edges" in name:
106
- return colls["edges"]
107
- return None
108
- mock_collection.side_effect = collection_side_effect
109
-
110
- # Mock MultimodalPCSTPruning
111
- mock_pcst_instance = MagicMock()
112
- mock_pcst_instance.extract_subgraph.return_value = {
113
- "nodes": pd.Series([1, 2]),
114
- "edges": pd.Series([0])
115
- }
116
- mock_pcst.return_value = mock_pcst_instance
117
-
118
- # Patch hydra.compose to return config objects
119
- with patch("aiagents4pharma.talk2knowledgegraphs.tools."
120
- "milvus_multimodal_subgraph_extraction.hydra.initialize"), \
121
- patch("aiagents4pharma.talk2knowledgegraphs.tools."
122
- "milvus_multimodal_subgraph_extraction.hydra.compose") as mock_compose:
123
- mock_compose.return_value = MagicMock()
124
- mock_compose.return_value.app.frontend = self.cfg_db
125
- mock_compose.return_value.tools.multimodal_subgraph_extraction = self.cfg
126
-
127
- response = self.tool.invoke(
128
- input={"prompt": self.prompt,
129
- "tool_call_id": "subgraph_extraction_tool",
130
- "state": self.state,
131
- "arg_data": self.arg_data}
132
- )
133
-
134
- # Check tool message
135
- self.assertEqual(response.update["messages"][-1].tool_call_id, "subgraph_extraction_tool")
136
-
137
- # Check extracted subgraph dictionary
138
- dic_extracted_graph = response.update["dic_extracted_graph"][0]
139
- self.assertIsInstance(dic_extracted_graph, dict)
140
- self.assertEqual(dic_extracted_graph["name"], self.arg_data["extraction_name"])
141
- self.assertEqual(dic_extracted_graph["graph_source"], "TestGraph")
142
- self.assertEqual(dic_extracted_graph["topk_nodes"], 5)
143
- self.assertEqual(dic_extracted_graph["topk_edges"], 5)
144
- self.assertIsInstance(dic_extracted_graph["graph_dict"], dict)
145
- self.assertGreater(len(dic_extracted_graph["graph_dict"]["nodes"]), 0)
146
- self.assertGreater(len(dic_extracted_graph["graph_dict"]["edges"]), 0)
147
- self.assertIsInstance(dic_extracted_graph["graph_text"], str)
148
- # Check if the nodes are in the graph_text
149
- self.assertTrue(all(
150
- n[0] in dic_extracted_graph["graph_text"].replace('"', '')
151
- for subgraph_nodes in dic_extracted_graph["graph_dict"]["nodes"]
152
- for n in subgraph_nodes
153
- ))
154
- # Check if the edges are in the graph_text
155
- self.assertTrue(all(
156
- ",".join([str(e[0])] + str(e[2]['label'][0]).split(",") + [str(e[1])])
157
- in dic_extracted_graph["graph_text"].replace('"', '').\
158
- replace("[", "").replace("]", "").replace("'", "")
159
- for subgraph_edges in dic_extracted_graph["graph_dict"]["edges"]
160
- for e in subgraph_edges
161
- ))
162
-
163
- # Another test for unknown collection
164
- result = collection_side_effect("unknown")
165
- self.assertIsNone(result)
166
-
167
- @patch("aiagents4pharma.talk2knowledgegraphs.tools."
168
- "milvus_multimodal_subgraph_extraction.Collection")
169
- @patch("aiagents4pharma.talk2knowledgegraphs.tools."
170
- "milvus_multimodal_subgraph_extraction.pd.read_excel")
171
- @patch("aiagents4pharma.talk2knowledgegraphs.tools."
172
- "milvus_multimodal_subgraph_extraction.MultimodalPCSTPruning")
173
- @patch("pymilvus.connections")
174
- def test_extract_multimodal_subgraph_w_doc(self,
175
- mock_connections,
176
- mock_pcst,
177
- mock_read_excel,
178
- mock_collection):
179
- """
180
- Test the multimodal subgraph extraction tool for text as modality, plus genes.
181
- """
182
- # Mock Milvus connection utilities
183
- mock_connections.has_connection.return_value = True
184
-
185
- # With uploaded_files (with doc)
186
- self.state["uploaded_files"] = [
187
- {"file_type": "multimodal", "file_path": "dummy.xlsx"}
188
- ]
189
- self.state["embedding_model"].embed_query.return_value = [0.1, 0.2, 0.3]
190
- self.state["selections"] = {"gene/protein": ["JAK1", "JAK2"]}
191
-
192
- # Mock pd.read_excel to return a dict of DataFrames
193
- df = pd.DataFrame({
194
- "name": ["JAK1", "JAK2"],
195
- "node_type": ["gene/protein", "gene/protein"]
196
- })
197
- mock_read_excel.return_value = {"gene/protein": df}
198
-
199
- # Mock Collection for nodes and edges
200
- colls = {}
201
- colls["nodes"] = MagicMock()
202
- colls["nodes"] = MagicMock()
203
- colls["nodes"].query.return_value = [
204
- {"node_index": 0,
205
- "node_id": "id1",
206
- "node_name": "JAK1",
207
- "node_type": "gene/protein",
208
- "feat": "featA",
209
- "feat_emb": [0.1, 0.2, 0.3],
210
- "desc": "descA",
211
- "desc_emb": [0.1, 0.2, 0.3]},
212
- {"node_index": 1,
213
- "node_id": "id2",
214
- "node_name": "JAK2",
215
- "node_type": "gene/protein",
216
- "feat": "featB",
217
- "feat_emb": [0.4, 0.5, 0.6],
218
- "desc": "descB",
219
- "desc_emb": [0.4, 0.5, 0.6]}
220
- ]
221
- colls["nodes"].load.return_value = None
222
-
223
- colls["edges"] = MagicMock()
224
- colls["edges"].query.return_value = [
225
- {"triplet_index": 0,
226
- "head_id": "id1",
227
- "head_index": 0,
228
- "tail_id": "id2",
229
- "tail_index": 1,
230
- "edge_type": "gene/protein,ppi,gene/protein",
231
- "display_relation": "ppi",
232
- "feat": "featC",
233
- "feat_emb": [0.7, 0.8, 0.9]}
234
- ]
235
- colls["edges"].load.return_value = None
236
-
237
- def collection_side_effect(name):
238
- """
239
- Mock side effect for Collection to return nodes or edges based on name.
240
- """
241
- if "nodes" in name:
242
- return colls["nodes"]
243
- if "edges" in name:
244
- return colls["edges"]
245
- return None
246
- mock_collection.side_effect = collection_side_effect
247
-
248
- # Mock MultimodalPCSTPruning
249
- mock_pcst_instance = MagicMock()
250
- mock_pcst_instance.extract_subgraph.return_value = {
251
- "nodes": pd.Series([1, 2]),
252
- "edges": pd.Series([0])
253
- }
254
- mock_pcst.return_value = mock_pcst_instance
255
-
256
- # Patch hydra.compose to return config objects
257
- with patch("aiagents4pharma.talk2knowledgegraphs.tools."
258
- "milvus_multimodal_subgraph_extraction.hydra.initialize"), \
259
- patch("aiagents4pharma.talk2knowledgegraphs.tools."
260
- "milvus_multimodal_subgraph_extraction.hydra.compose") as mock_compose:
261
- mock_compose.return_value = MagicMock()
262
- mock_compose.return_value.app.frontend = self.cfg_db
263
- mock_compose.return_value.tools.multimodal_subgraph_extraction = self.cfg
264
-
265
- response = self.tool.invoke(
266
- input={"prompt": self.prompt,
267
- "tool_call_id": "subgraph_extraction_tool",
268
- "state": self.state,
269
- "arg_data": self.arg_data}
270
- )
271
-
272
- # Check tool message
273
- self.assertEqual(response.update["messages"][-1].tool_call_id, "subgraph_extraction_tool")
274
-
275
- # Check extracted subgraph dictionary
276
- dic_extracted_graph = response.update["dic_extracted_graph"][0]
277
- self.assertIsInstance(dic_extracted_graph, dict)
278
- self.assertEqual(dic_extracted_graph["name"], self.arg_data["extraction_name"])
279
- self.assertEqual(dic_extracted_graph["graph_source"], "TestGraph")
280
- self.assertEqual(dic_extracted_graph["topk_nodes"], 5)
281
- self.assertEqual(dic_extracted_graph["topk_edges"], 5)
282
- self.assertIsInstance(dic_extracted_graph["graph_dict"], dict)
283
- self.assertGreater(len(dic_extracted_graph["graph_dict"]["nodes"]), 0)
284
- self.assertGreater(len(dic_extracted_graph["graph_dict"]["edges"]), 0)
285
- self.assertIsInstance(dic_extracted_graph["graph_text"], str)
286
- # Check if the nodes are in the graph_text
287
- self.assertTrue(all(
288
- n[0] in dic_extracted_graph["graph_text"].replace('"', '')
289
- for subgraph_nodes in dic_extracted_graph["graph_dict"]["nodes"]
290
- for n in subgraph_nodes
291
- ))
292
- # Check if the edges are in the graph_text
293
- self.assertTrue(all(
294
- ",".join([str(e[0])] + str(e[2]['label'][0]).split(",") + [str(e[1])])
295
- in dic_extracted_graph["graph_text"].replace('"', '').\
296
- replace("[", "").replace("]", "").replace("'", "")
297
- for subgraph_edges in dic_extracted_graph["graph_dict"]["edges"]
298
- for e in subgraph_edges
299
- ))
300
-
301
- # Another test for unknown collection
302
- result = collection_side_effect("unknown")
303
- self.assertIsNone(result)
304
-
305
- def test_extract_multimodal_subgraph_wo_doc_gpu(self):
306
- """
307
- Test the multimodal subgraph extraction tool for only text as modality,
308
- simulating GPU (cudf/cupy) environment.
309
- """
310
- module_name = "aiagents4pharma.talk2knowledgegraphs.tools."+\
311
- "milvus_multimodal_subgraph_extraction"
312
- with patch.dict("sys.modules", {"cupy": np, "cudf": pd}):
313
- mod = importlib.reload(importlib.import_module(module_name))
314
- # Patch Collection and MultimodalPCSTPruning after reload
315
- with patch(f"{module_name}.Collection") as mock_collection, \
316
- patch(f"{module_name}.MultimodalPCSTPruning") as mock_pcst, \
317
- patch("pymilvus.connections") as mock_connections:
318
- # Setup mocks as in the original test
319
- mock_connections.has_connection.return_value = True
320
- colls = {}
321
- colls["nodes"] = MagicMock()
322
- colls["nodes"].query.return_value = [
323
- {"node_index": 0,
324
- "node_id": "id1",
325
- "node_name": "JAK1",
326
- "node_type": "gene/protein",
327
- "feat": "featA",
328
- "feat_emb": [0.1, 0.2, 0.3],
329
- "desc": "descA",
330
- "desc_emb": [0.1, 0.2, 0.3]},
331
- {"node_index": 1,
332
- "node_id": "id2",
333
- "node_name": "JAK2",
334
- "node_type": "gene/protein",
335
- "feat": "featB",
336
- "feat_emb": [0.4, 0.5, 0.6],
337
- "desc": "descB",
338
- "desc_emb": [0.4, 0.5, 0.6]}
339
- ]
340
- colls["nodes"].load.return_value = None
341
- colls["edges"] = MagicMock()
342
- colls["edges"].query.return_value = [
343
- {"triplet_index": 0,
344
- "head_id": "id1",
345
- "head_index": 0,
346
- "tail_id": "id2",
347
- "tail_index": 1,
348
- "edge_type": "gene/protein,ppi,gene/protein",
349
- "display_relation": "ppi",
350
- "feat": "featC",
351
- "feat_emb": [0.7, 0.8, 0.9]}
352
- ]
353
- colls["edges"].load.return_value = None
354
- def collection_side_effect(name):
355
- if "nodes" in name:
356
- return colls["nodes"]
357
- if "edges" in name:
358
- return colls["edges"]
359
- return None
360
- mock_collection.side_effect = collection_side_effect
361
- mock_pcst_instance = MagicMock()
362
- mock_pcst_instance.extract_subgraph.return_value = {
363
- "nodes": pd.Series([1, 2]),
364
- "edges": pd.Series([0])
365
- }
366
- mock_pcst.return_value = mock_pcst_instance
367
- # Setup config mocks
368
- tool_cls = getattr(mod, "MultimodalSubgraphExtractionTool")
369
- tool = tool_cls()
370
-
371
- # Patch hydra.compose
372
- with patch(f"{module_name}.hydra.initialize"), \
373
- patch(f"{module_name}.hydra.compose") as mock_compose:
374
- mock_compose.return_value = MagicMock()
375
- mock_compose.return_value.app.frontend = self.cfg_db
376
- mock_compose.return_value.tools.multimodal_subgraph_extraction = self.cfg
377
- self.state["embedding_model"].embed_query.return_value = [0.1, 0.2, 0.3]
378
- self.state["selections"] = {}
379
- response = tool.invoke(
380
- input={"prompt": self.prompt,
381
- "tool_call_id": "subgraph_extraction_tool",
382
- "state": self.state,
383
- "arg_data": self.arg_data}
384
- )
385
- # Check tool message
386
- self.assertEqual(response.update["messages"][-1].tool_call_id,
387
- "subgraph_extraction_tool")
388
- dic_extracted_graph = response.update["dic_extracted_graph"][0]
389
- self.assertIsInstance(dic_extracted_graph, dict)
390
- self.assertEqual(dic_extracted_graph["name"], self.arg_data["extraction_name"])
391
- self.assertEqual(dic_extracted_graph["graph_source"], "TestGraph")
392
- self.assertEqual(dic_extracted_graph["topk_nodes"], 5)
393
- self.assertEqual(dic_extracted_graph["topk_edges"], 5)
394
- self.assertIsInstance(dic_extracted_graph["graph_dict"], dict)
395
- self.assertGreater(len(dic_extracted_graph["graph_dict"]["nodes"]), 0)
396
- self.assertGreater(len(dic_extracted_graph["graph_dict"]["edges"]), 0)
397
- self.assertIsInstance(dic_extracted_graph["graph_text"], str)
398
- self.assertTrue(all(
399
- n[0] in dic_extracted_graph["graph_text"].replace('"', '')
400
- for subgraph_nodes in dic_extracted_graph["graph_dict"]["nodes"]
401
- for n in subgraph_nodes
402
- ))
403
- self.assertTrue(all(
404
- ",".join([str(e[0])] + str(e[2]['label'][0]).split(",") + [str(e[1])])
405
- in dic_extracted_graph["graph_text"].replace('"', '').\
406
- replace("[", "").replace("]", "").replace("'", "")
407
- for subgraph_edges in dic_extracted_graph["graph_dict"]["edges"]
408
- for e in subgraph_edges
409
- ))
410
-
411
- # Another test for unknown collection
412
- result = collection_side_effect("unknown")
413
- self.assertIsNone(result)
1
+ """
2
+ Test cases for tools/milvus_multimodal_subgraph_extraction.py
3
+ """
4
+
5
+ import importlib
6
+ import unittest
7
+ from unittest.mock import MagicMock, patch
8
+
9
+ import numpy as np
10
+ import pandas as pd
11
+
12
+ from ..tools.milvus_multimodal_subgraph_extraction import MultimodalSubgraphExtractionTool
13
+
14
+
15
+ class TestMultimodalSubgraphExtractionTool(unittest.TestCase):
16
+ """
17
+ Test cases for MultimodalSubgraphExtractionTool (Milvus)
18
+ """
19
+
20
+ def setUp(self):
21
+ self.tool = MultimodalSubgraphExtractionTool()
22
+ self.state = {
23
+ "uploaded_files": [],
24
+ "embedding_model": MagicMock(),
25
+ "topk_nodes": 5,
26
+ "topk_edges": 5,
27
+ "dic_source_graph": [{"name": "TestGraph"}],
28
+ }
29
+ self.prompt = "Find subgraph for test"
30
+ self.arg_data = {"extraction_name": "subkg_12345"}
31
+ self.cfg_db = MagicMock()
32
+ self.cfg_db.milvus_db.database_name = "testdb"
33
+ self.cfg_db.milvus_db.alias = "default"
34
+ self.cfg = MagicMock()
35
+ self.cfg.cost_e = 1.0
36
+ self.cfg.c_const = 1.0
37
+ self.cfg.root = 0
38
+ self.cfg.num_clusters = 1
39
+ self.cfg.pruning = True
40
+ self.cfg.verbosity_level = 0
41
+ self.cfg.search_metric_type = "L2"
42
+ self.cfg.node_colors_dict = {"gene/protein": "red"}
43
+
44
+ @patch(
45
+ "aiagents4pharma.talk2knowledgegraphs.tools."
46
+ "milvus_multimodal_subgraph_extraction.Collection"
47
+ )
48
+ @patch(
49
+ "aiagents4pharma.talk2knowledgegraphs.tools."
50
+ "milvus_multimodal_subgraph_extraction.MultimodalPCSTPruning"
51
+ )
52
+ @patch("pymilvus.connections")
53
+ def test_extract_multimodal_subgraph_wo_doc(self, mock_connections, mock_pcst, mock_collection):
54
+ """
55
+ Test the multimodal subgraph extraction tool for only text as modality.
56
+ """
57
+
58
+ # Mock Milvus connection utilities
59
+ mock_connections.has_connection.return_value = True
60
+
61
+ # No uploaded_files (no doc)
62
+ self.state["uploaded_files"] = []
63
+ self.state["embedding_model"].embed_query.return_value = [0.1, 0.2, 0.3]
64
+ self.state["selections"] = {}
65
+
66
+ # Mock Collection for nodes and edges
67
+ colls = {}
68
+ colls["nodes"] = MagicMock()
69
+ colls["nodes"] = MagicMock()
70
+ colls["nodes"].query.return_value = [
71
+ {
72
+ "node_index": 0,
73
+ "node_id": "id1",
74
+ "node_name": "JAK1",
75
+ "node_type": "gene/protein",
76
+ "feat": "featA",
77
+ "feat_emb": [0.1, 0.2, 0.3],
78
+ "desc": "descA",
79
+ "desc_emb": [0.1, 0.2, 0.3],
80
+ },
81
+ {
82
+ "node_index": 1,
83
+ "node_id": "id2",
84
+ "node_name": "JAK2",
85
+ "node_type": "gene/protein",
86
+ "feat": "featB",
87
+ "feat_emb": [0.4, 0.5, 0.6],
88
+ "desc": "descB",
89
+ "desc_emb": [0.4, 0.5, 0.6],
90
+ },
91
+ ]
92
+ colls["nodes"].load.return_value = None
93
+
94
+ colls["edges"] = MagicMock()
95
+ colls["edges"].query.return_value = [
96
+ {
97
+ "triplet_index": 0,
98
+ "head_id": "id1",
99
+ "head_index": 0,
100
+ "tail_id": "id2",
101
+ "tail_index": 1,
102
+ "edge_type": "gene/protein,ppi,gene/protein",
103
+ "display_relation": "ppi",
104
+ "feat": "featC",
105
+ "feat_emb": [0.7, 0.8, 0.9],
106
+ }
107
+ ]
108
+ colls["edges"].load.return_value = None
109
+
110
+ def collection_side_effect(name):
111
+ """
112
+ Mock side effect for Collection to return nodes or edges based on name.
113
+ """
114
+ if "nodes" in name:
115
+ return colls["nodes"]
116
+ if "edges" in name:
117
+ return colls["edges"]
118
+ return None
119
+
120
+ mock_collection.side_effect = collection_side_effect
121
+
122
+ # Mock MultimodalPCSTPruning
123
+ mock_pcst_instance = MagicMock()
124
+ mock_pcst_instance.extract_subgraph.return_value = {
125
+ "nodes": pd.Series([1, 2]),
126
+ "edges": pd.Series([0]),
127
+ }
128
+ mock_pcst.return_value = mock_pcst_instance
129
+
130
+ # Patch hydra.compose to return config objects
131
+ with (
132
+ patch(
133
+ "aiagents4pharma.talk2knowledgegraphs.tools."
134
+ "milvus_multimodal_subgraph_extraction.hydra.initialize"
135
+ ),
136
+ patch(
137
+ "aiagents4pharma.talk2knowledgegraphs.tools."
138
+ "milvus_multimodal_subgraph_extraction.hydra.compose"
139
+ ) as mock_compose,
140
+ ):
141
+ mock_compose.return_value = MagicMock()
142
+ mock_compose.return_value.app.frontend = self.cfg_db
143
+ mock_compose.return_value.tools.multimodal_subgraph_extraction = self.cfg
144
+
145
+ response = self.tool.invoke(
146
+ input={
147
+ "prompt": self.prompt,
148
+ "tool_call_id": "subgraph_extraction_tool",
149
+ "state": self.state,
150
+ "arg_data": self.arg_data,
151
+ }
152
+ )
153
+
154
+ # Check tool message
155
+ self.assertEqual(response.update["messages"][-1].tool_call_id, "subgraph_extraction_tool")
156
+
157
+ # Check extracted subgraph dictionary
158
+ dic_extracted_graph = response.update["dic_extracted_graph"][0]
159
+ self.assertIsInstance(dic_extracted_graph, dict)
160
+ self.assertEqual(dic_extracted_graph["name"], self.arg_data["extraction_name"])
161
+ self.assertEqual(dic_extracted_graph["graph_source"], "TestGraph")
162
+ self.assertEqual(dic_extracted_graph["topk_nodes"], 5)
163
+ self.assertEqual(dic_extracted_graph["topk_edges"], 5)
164
+ self.assertIsInstance(dic_extracted_graph["graph_dict"], dict)
165
+ self.assertGreater(len(dic_extracted_graph["graph_dict"]["nodes"]), 0)
166
+ self.assertGreater(len(dic_extracted_graph["graph_dict"]["edges"]), 0)
167
+ self.assertIsInstance(dic_extracted_graph["graph_text"], str)
168
+ # Check if the nodes are in the graph_text
169
+ self.assertTrue(
170
+ all(
171
+ n[0] in dic_extracted_graph["graph_text"].replace('"', "")
172
+ for subgraph_nodes in dic_extracted_graph["graph_dict"]["nodes"]
173
+ for n in subgraph_nodes
174
+ )
175
+ )
176
+ # Check if the edges are in the graph_text
177
+ self.assertTrue(
178
+ all(
179
+ ",".join([str(e[0])] + str(e[2]["label"][0]).split(",") + [str(e[1])])
180
+ in dic_extracted_graph["graph_text"]
181
+ .replace('"', "")
182
+ .replace("[", "")
183
+ .replace("]", "")
184
+ .replace("'", "")
185
+ for subgraph_edges in dic_extracted_graph["graph_dict"]["edges"]
186
+ for e in subgraph_edges
187
+ )
188
+ )
189
+
190
+ # Another test for unknown collection
191
+ result = collection_side_effect("unknown")
192
+ self.assertIsNone(result)
193
+
194
+ @patch(
195
+ "aiagents4pharma.talk2knowledgegraphs.tools."
196
+ "milvus_multimodal_subgraph_extraction.Collection"
197
+ )
198
+ @patch(
199
+ "aiagents4pharma.talk2knowledgegraphs.tools."
200
+ "milvus_multimodal_subgraph_extraction.pd.read_excel"
201
+ )
202
+ @patch(
203
+ "aiagents4pharma.talk2knowledgegraphs.tools."
204
+ "milvus_multimodal_subgraph_extraction.MultimodalPCSTPruning"
205
+ )
206
+ @patch("pymilvus.connections")
207
+ def test_extract_multimodal_subgraph_w_doc(
208
+ self, mock_connections, mock_pcst, mock_read_excel, mock_collection
209
+ ):
210
+ """
211
+ Test the multimodal subgraph extraction tool for text as modality, plus genes.
212
+ """
213
+ # Mock Milvus connection utilities
214
+ mock_connections.has_connection.return_value = True
215
+
216
+ # With uploaded_files (with doc)
217
+ self.state["uploaded_files"] = [{"file_type": "multimodal", "file_path": "dummy.xlsx"}]
218
+ self.state["embedding_model"].embed_query.return_value = [0.1, 0.2, 0.3]
219
+ self.state["selections"] = {"gene/protein": ["JAK1", "JAK2"]}
220
+
221
+ # Mock pd.read_excel to return a dict of DataFrames
222
+ df = pd.DataFrame({"name": ["JAK1", "JAK2"], "node_type": ["gene/protein", "gene/protein"]})
223
+ mock_read_excel.return_value = {"gene/protein": df}
224
+
225
+ # Mock Collection for nodes and edges
226
+ colls = {}
227
+ colls["nodes"] = MagicMock()
228
+ colls["nodes"] = MagicMock()
229
+ colls["nodes"].query.return_value = [
230
+ {
231
+ "node_index": 0,
232
+ "node_id": "id1",
233
+ "node_name": "JAK1",
234
+ "node_type": "gene/protein",
235
+ "feat": "featA",
236
+ "feat_emb": [0.1, 0.2, 0.3],
237
+ "desc": "descA",
238
+ "desc_emb": [0.1, 0.2, 0.3],
239
+ },
240
+ {
241
+ "node_index": 1,
242
+ "node_id": "id2",
243
+ "node_name": "JAK2",
244
+ "node_type": "gene/protein",
245
+ "feat": "featB",
246
+ "feat_emb": [0.4, 0.5, 0.6],
247
+ "desc": "descB",
248
+ "desc_emb": [0.4, 0.5, 0.6],
249
+ },
250
+ ]
251
+ colls["nodes"].load.return_value = None
252
+
253
+ colls["edges"] = MagicMock()
254
+ colls["edges"].query.return_value = [
255
+ {
256
+ "triplet_index": 0,
257
+ "head_id": "id1",
258
+ "head_index": 0,
259
+ "tail_id": "id2",
260
+ "tail_index": 1,
261
+ "edge_type": "gene/protein,ppi,gene/protein",
262
+ "display_relation": "ppi",
263
+ "feat": "featC",
264
+ "feat_emb": [0.7, 0.8, 0.9],
265
+ }
266
+ ]
267
+ colls["edges"].load.return_value = None
268
+
269
+ def collection_side_effect(name):
270
+ """
271
+ Mock side effect for Collection to return nodes or edges based on name.
272
+ """
273
+ if "nodes" in name:
274
+ return colls["nodes"]
275
+ if "edges" in name:
276
+ return colls["edges"]
277
+ return None
278
+
279
+ mock_collection.side_effect = collection_side_effect
280
+
281
+ # Mock MultimodalPCSTPruning
282
+ mock_pcst_instance = MagicMock()
283
+ mock_pcst_instance.extract_subgraph.return_value = {
284
+ "nodes": pd.Series([1, 2]),
285
+ "edges": pd.Series([0]),
286
+ }
287
+ mock_pcst.return_value = mock_pcst_instance
288
+
289
+ # Patch hydra.compose to return config objects
290
+ with (
291
+ patch(
292
+ "aiagents4pharma.talk2knowledgegraphs.tools."
293
+ "milvus_multimodal_subgraph_extraction.hydra.initialize"
294
+ ),
295
+ patch(
296
+ "aiagents4pharma.talk2knowledgegraphs.tools."
297
+ "milvus_multimodal_subgraph_extraction.hydra.compose"
298
+ ) as mock_compose,
299
+ ):
300
+ mock_compose.return_value = MagicMock()
301
+ mock_compose.return_value.app.frontend = self.cfg_db
302
+ mock_compose.return_value.tools.multimodal_subgraph_extraction = self.cfg
303
+
304
+ response = self.tool.invoke(
305
+ input={
306
+ "prompt": self.prompt,
307
+ "tool_call_id": "subgraph_extraction_tool",
308
+ "state": self.state,
309
+ "arg_data": self.arg_data,
310
+ }
311
+ )
312
+
313
+ # Check tool message
314
+ self.assertEqual(response.update["messages"][-1].tool_call_id, "subgraph_extraction_tool")
315
+
316
+ # Check extracted subgraph dictionary
317
+ dic_extracted_graph = response.update["dic_extracted_graph"][0]
318
+ self.assertIsInstance(dic_extracted_graph, dict)
319
+ self.assertEqual(dic_extracted_graph["name"], self.arg_data["extraction_name"])
320
+ self.assertEqual(dic_extracted_graph["graph_source"], "TestGraph")
321
+ self.assertEqual(dic_extracted_graph["topk_nodes"], 5)
322
+ self.assertEqual(dic_extracted_graph["topk_edges"], 5)
323
+ self.assertIsInstance(dic_extracted_graph["graph_dict"], dict)
324
+ self.assertGreater(len(dic_extracted_graph["graph_dict"]["nodes"]), 0)
325
+ self.assertGreater(len(dic_extracted_graph["graph_dict"]["edges"]), 0)
326
+ self.assertIsInstance(dic_extracted_graph["graph_text"], str)
327
+ # Check if the nodes are in the graph_text
328
+ self.assertTrue(
329
+ all(
330
+ n[0] in dic_extracted_graph["graph_text"].replace('"', "")
331
+ for subgraph_nodes in dic_extracted_graph["graph_dict"]["nodes"]
332
+ for n in subgraph_nodes
333
+ )
334
+ )
335
+ # Check if the edges are in the graph_text
336
+ self.assertTrue(
337
+ all(
338
+ ",".join([str(e[0])] + str(e[2]["label"][0]).split(",") + [str(e[1])])
339
+ in dic_extracted_graph["graph_text"]
340
+ .replace('"', "")
341
+ .replace("[", "")
342
+ .replace("]", "")
343
+ .replace("'", "")
344
+ for subgraph_edges in dic_extracted_graph["graph_dict"]["edges"]
345
+ for e in subgraph_edges
346
+ )
347
+ )
348
+
349
+ # Another test for unknown collection
350
+ result = collection_side_effect("unknown")
351
+ self.assertIsNone(result)
352
+
353
+ def test_extract_multimodal_subgraph_wo_doc_gpu(self):
354
+ """
355
+ Test the multimodal subgraph extraction tool for only text as modality,
356
+ simulating GPU (cudf/cupy) environment.
357
+ """
358
+ module_name = (
359
+ "aiagents4pharma.talk2knowledgegraphs.tools." + "milvus_multimodal_subgraph_extraction"
360
+ )
361
+ with patch.dict("sys.modules", {"cupy": np, "cudf": pd}):
362
+ mod = importlib.reload(importlib.import_module(module_name))
363
+ # Patch Collection and MultimodalPCSTPruning after reload
364
+ with (
365
+ patch(f"{module_name}.Collection") as mock_collection,
366
+ patch(f"{module_name}.MultimodalPCSTPruning") as mock_pcst,
367
+ patch("pymilvus.connections") as mock_connections,
368
+ ):
369
+ # Setup mocks as in the original test
370
+ mock_connections.has_connection.return_value = True
371
+ colls = {}
372
+ colls["nodes"] = MagicMock()
373
+ colls["nodes"].query.return_value = [
374
+ {
375
+ "node_index": 0,
376
+ "node_id": "id1",
377
+ "node_name": "JAK1",
378
+ "node_type": "gene/protein",
379
+ "feat": "featA",
380
+ "feat_emb": [0.1, 0.2, 0.3],
381
+ "desc": "descA",
382
+ "desc_emb": [0.1, 0.2, 0.3],
383
+ },
384
+ {
385
+ "node_index": 1,
386
+ "node_id": "id2",
387
+ "node_name": "JAK2",
388
+ "node_type": "gene/protein",
389
+ "feat": "featB",
390
+ "feat_emb": [0.4, 0.5, 0.6],
391
+ "desc": "descB",
392
+ "desc_emb": [0.4, 0.5, 0.6],
393
+ },
394
+ ]
395
+ colls["nodes"].load.return_value = None
396
+ colls["edges"] = MagicMock()
397
+ colls["edges"].query.return_value = [
398
+ {
399
+ "triplet_index": 0,
400
+ "head_id": "id1",
401
+ "head_index": 0,
402
+ "tail_id": "id2",
403
+ "tail_index": 1,
404
+ "edge_type": "gene/protein,ppi,gene/protein",
405
+ "display_relation": "ppi",
406
+ "feat": "featC",
407
+ "feat_emb": [0.7, 0.8, 0.9],
408
+ }
409
+ ]
410
+ colls["edges"].load.return_value = None
411
+
412
+ def collection_side_effect(name):
413
+ if "nodes" in name:
414
+ return colls["nodes"]
415
+ if "edges" in name:
416
+ return colls["edges"]
417
+ return None
418
+
419
+ mock_collection.side_effect = collection_side_effect
420
+ mock_pcst_instance = MagicMock()
421
+ mock_pcst_instance.extract_subgraph.return_value = {
422
+ "nodes": pd.Series([1, 2]),
423
+ "edges": pd.Series([0]),
424
+ }
425
+ mock_pcst.return_value = mock_pcst_instance
426
+ # Setup config mocks
427
+ tool_cls = mod.MultimodalSubgraphExtractionTool
428
+ tool = tool_cls()
429
+
430
+ # Patch hydra.compose
431
+ with (
432
+ patch(f"{module_name}.hydra.initialize"),
433
+ patch(f"{module_name}.hydra.compose") as mock_compose,
434
+ ):
435
+ mock_compose.return_value = MagicMock()
436
+ mock_compose.return_value.app.frontend = self.cfg_db
437
+ mock_compose.return_value.tools.multimodal_subgraph_extraction = self.cfg
438
+ self.state["embedding_model"].embed_query.return_value = [0.1, 0.2, 0.3]
439
+ self.state["selections"] = {}
440
+ response = tool.invoke(
441
+ input={
442
+ "prompt": self.prompt,
443
+ "tool_call_id": "subgraph_extraction_tool",
444
+ "state": self.state,
445
+ "arg_data": self.arg_data,
446
+ }
447
+ )
448
+ # Check tool message
449
+ self.assertEqual(
450
+ response.update["messages"][-1].tool_call_id, "subgraph_extraction_tool"
451
+ )
452
+ dic_extracted_graph = response.update["dic_extracted_graph"][0]
453
+ self.assertIsInstance(dic_extracted_graph, dict)
454
+ self.assertEqual(dic_extracted_graph["name"], self.arg_data["extraction_name"])
455
+ self.assertEqual(dic_extracted_graph["graph_source"], "TestGraph")
456
+ self.assertEqual(dic_extracted_graph["topk_nodes"], 5)
457
+ self.assertEqual(dic_extracted_graph["topk_edges"], 5)
458
+ self.assertIsInstance(dic_extracted_graph["graph_dict"], dict)
459
+ self.assertGreater(len(dic_extracted_graph["graph_dict"]["nodes"]), 0)
460
+ self.assertGreater(len(dic_extracted_graph["graph_dict"]["edges"]), 0)
461
+ self.assertIsInstance(dic_extracted_graph["graph_text"], str)
462
+ self.assertTrue(
463
+ all(
464
+ n[0] in dic_extracted_graph["graph_text"].replace('"', "")
465
+ for subgraph_nodes in dic_extracted_graph["graph_dict"]["nodes"]
466
+ for n in subgraph_nodes
467
+ )
468
+ )
469
+ self.assertTrue(
470
+ all(
471
+ ",".join([str(e[0])] + str(e[2]["label"][0]).split(",") + [str(e[1])])
472
+ in dic_extracted_graph["graph_text"]
473
+ .replace('"', "")
474
+ .replace("[", "")
475
+ .replace("]", "")
476
+ .replace("'", "")
477
+ for subgraph_edges in dic_extracted_graph["graph_dict"]["edges"]
478
+ for e in subgraph_edges
479
+ )
480
+ )
481
+
482
+ # Another test for unknown collection
483
+ result = collection_side_effect("unknown")
484
+ self.assertIsNone(result)
485
+
486
+ def test_normalize_vector_gpu_mode(self):
487
+ """Test normalize_vector method in GPU mode."""
488
+ # Mock the loader to simulate GPU mode
489
+ self.tool.loader.normalize_vectors = True
490
+ self.tool.loader.py = MagicMock()
491
+ # Mock the GPU array operations
492
+ mock_array = MagicMock()
493
+ mock_norm = MagicMock()
494
+ mock_norm.return_value = 2.0
495
+ mock_array.__truediv__ = MagicMock(return_value=mock_array)
496
+ mock_array.tolist.return_value = [0.5, 1.0, 1.5]
497
+ self.tool.loader.py.asarray.return_value = mock_array
498
+ self.tool.loader.py.linalg.norm.return_value = mock_norm
499
+ result = self.tool.normalize_vector([1.0, 2.0, 3.0])
500
+ # Verify the result
501
+ self.assertEqual(result, [0.5, 1.0, 1.5])
502
+ self.tool.loader.py.asarray.assert_called_once_with([1.0, 2.0, 3.0])
503
+ self.tool.loader.py.linalg.norm.assert_called_once_with(mock_array)
504
+
505
+ def test_normalize_vector_cpu_mode(self):
506
+ """Test normalize_vector method in CPU mode."""
507
+ # Mock the loader to simulate CPU mode
508
+ self.tool.loader.normalize_vectors = False
509
+ result = self.tool.normalize_vector([1.0, 2.0, 3.0])
510
+ # In CPU mode, should return the input as-is
511
+ self.assertEqual(result, [1.0, 2.0, 3.0])
512
+
513
+ @patch(
514
+ "aiagents4pharma.talk2knowledgegraphs.tools."
515
+ "milvus_multimodal_subgraph_extraction.Collection"
516
+ )
517
+ @patch(
518
+ "aiagents4pharma.talk2knowledgegraphs.tools."
519
+ "milvus_multimodal_subgraph_extraction.MultimodalPCSTPruning"
520
+ )
521
+ @patch("pymilvus.connections")
522
+ def test_extract_multimodal_subgraph_no_vector_processing(
523
+ self, mock_connections, mock_pcst, mock_collection
524
+ ):
525
+ """Test when vector_processing config is not present."""
526
+ # Mock Milvus connection utilities
527
+ mock_connections.has_connection.return_value = True
528
+
529
+ self.state["uploaded_files"] = []
530
+ self.state["embedding_model"].embed_query.return_value = [0.1, 0.2, 0.3]
531
+ self.state["selections"] = {}
532
+
533
+ # Mock Collection for nodes and edges
534
+ colls = {}
535
+ colls["nodes"] = MagicMock()
536
+ colls["nodes"].query.return_value = [
537
+ {
538
+ "node_index": 0,
539
+ "node_id": "id1",
540
+ "node_name": "JAK1",
541
+ "node_type": "gene/protein",
542
+ "feat": "featA",
543
+ "feat_emb": [0.1, 0.2, 0.3],
544
+ "desc": "descA",
545
+ "desc_emb": [0.1, 0.2, 0.3],
546
+ }
547
+ ]
548
+ colls["nodes"].load.return_value = None
549
+
550
+ colls["edges"] = MagicMock()
551
+ colls["edges"].query.return_value = [
552
+ {
553
+ "triplet_index": 0,
554
+ "head_id": "id1",
555
+ "tail_id": "id2",
556
+ "edge_type": "gene/protein,ppi,gene/protein",
557
+ }
558
+ ]
559
+ colls["edges"].load.return_value = None
560
+
561
+ def collection_side_effect(name):
562
+ if "nodes" in name:
563
+ return colls["nodes"]
564
+ if "edges" in name:
565
+ return colls["edges"]
566
+ return None
567
+
568
+ mock_collection.side_effect = collection_side_effect
569
+
570
+ # Mock MultimodalPCSTPruning
571
+ mock_pcst_instance = MagicMock()
572
+ mock_pcst_instance.extract_subgraph.return_value = {
573
+ "nodes": pd.Series([1]),
574
+ "edges": pd.Series([0]),
575
+ }
576
+ mock_pcst.return_value = mock_pcst_instance
577
+
578
+ # Create config without vector_processing attribute
579
+ cfg_no_vector_processing = MagicMock()
580
+ cfg_no_vector_processing.cost_e = 1.0
581
+ cfg_no_vector_processing.c_const = 1.0
582
+ cfg_no_vector_processing.root = 0
583
+ cfg_no_vector_processing.num_clusters = 1
584
+ cfg_no_vector_processing.pruning = True
585
+ cfg_no_vector_processing.verbosity_level = 0
586
+ cfg_no_vector_processing.search_metric_type = "L2"
587
+ cfg_no_vector_processing.node_colors_dict = {"gene/protein": "red"}
588
+ # Remove vector_processing attribute to test the missing branch
589
+ del cfg_no_vector_processing.vector_processing
590
+
591
+ # Patch hydra.compose to return config without vector_processing
592
+ with (
593
+ patch(
594
+ "aiagents4pharma.talk2knowledgegraphs.tools."
595
+ "milvus_multimodal_subgraph_extraction.hydra.initialize"
596
+ ),
597
+ patch(
598
+ "aiagents4pharma.talk2knowledgegraphs.tools."
599
+ "milvus_multimodal_subgraph_extraction.hydra.compose"
600
+ ) as mock_compose,
601
+ ):
602
+ mock_compose.return_value = MagicMock()
603
+ mock_compose.return_value.app.frontend = self.cfg_db
604
+ mock_compose.return_value.tools.multimodal_subgraph_extraction = (
605
+ cfg_no_vector_processing
606
+ )
607
+
608
+ response = self.tool.invoke(
609
+ input={
610
+ "prompt": self.prompt,
611
+ "tool_call_id": "subgraph_extraction_tool",
612
+ "state": self.state,
613
+ "arg_data": self.arg_data,
614
+ }
615
+ )
616
+
617
+ # Verify the test completed successfully
618
+ self.assertEqual(response.update["messages"][-1].tool_call_id, "subgraph_extraction_tool")
619
+
620
+ # Test the collection_side_effect with unknown name for final test
621
+ result = collection_side_effect("final_unknown_collection")
622
+ self.assertIsNone(result)
623
+
624
+ # Test the collection_side_effect with unknown name
625
+ result = collection_side_effect("unknown_collection")
626
+ self.assertIsNone(result)
627
+
628
+ @patch(
629
+ "aiagents4pharma.talk2knowledgegraphs.tools."
630
+ "milvus_multimodal_subgraph_extraction.Collection"
631
+ )
632
+ @patch(
633
+ "aiagents4pharma.talk2knowledgegraphs.tools."
634
+ "milvus_multimodal_subgraph_extraction.MultimodalPCSTPruning"
635
+ )
636
+ @patch("pymilvus.connections")
637
+ def test_extract_multimodal_subgraph_dynamic_metrics_disabled(
638
+ self, mock_connections, mock_pcst, mock_collection
639
+ ):
640
+ """Test when dynamic_metrics is disabled."""
641
+ # Mock Milvus connection utilities
642
+ mock_connections.has_connection.return_value = True
643
+
644
+ self.state["uploaded_files"] = []
645
+ self.state["embedding_model"].embed_query.return_value = [0.1, 0.2, 0.3]
646
+ self.state["selections"] = {}
647
+
648
+ # Mock Collection for nodes and edges
649
+ colls = {}
650
+ colls["nodes"] = MagicMock()
651
+ colls["nodes"].query.return_value = [
652
+ {
653
+ "node_index": 0,
654
+ "node_id": "id1",
655
+ "node_name": "JAK1",
656
+ "node_type": "gene/protein",
657
+ "feat": "featA",
658
+ "feat_emb": [0.1, 0.2, 0.3],
659
+ "desc": "descA",
660
+ "desc_emb": [0.1, 0.2, 0.3],
661
+ }
662
+ ]
663
+ colls["nodes"].load.return_value = None
664
+
665
+ colls["edges"] = MagicMock()
666
+ colls["edges"].query.return_value = [
667
+ {
668
+ "triplet_index": 0,
669
+ "head_id": "id1",
670
+ "tail_id": "id2",
671
+ "edge_type": "gene/protein,ppi,gene/protein",
672
+ }
673
+ ]
674
+ colls["edges"].load.return_value = None
675
+
676
+ def collection_side_effect(name):
677
+ if "nodes" in name:
678
+ return colls["nodes"]
679
+ if "edges" in name:
680
+ return colls["edges"]
681
+ return None
682
+
683
+ mock_collection.side_effect = collection_side_effect
684
+
685
+ # Mock MultimodalPCSTPruning
686
+ mock_pcst_instance = MagicMock()
687
+ mock_pcst_instance.extract_subgraph.return_value = {
688
+ "nodes": pd.Series([1]),
689
+ "edges": pd.Series([0]),
690
+ }
691
+ mock_pcst.return_value = mock_pcst_instance
692
+
693
+ # Create config with dynamic_metrics disabled
694
+ cfg_dynamic_disabled = MagicMock()
695
+ cfg_dynamic_disabled.cost_e = 1.0
696
+ cfg_dynamic_disabled.c_const = 1.0
697
+ cfg_dynamic_disabled.root = 0
698
+ cfg_dynamic_disabled.num_clusters = 1
699
+ cfg_dynamic_disabled.pruning = True
700
+ cfg_dynamic_disabled.verbosity_level = 0
701
+ cfg_dynamic_disabled.search_metric_type = "L2"
702
+ cfg_dynamic_disabled.node_colors_dict = {"gene/protein": "red"}
703
+ # Set dynamic_metrics to False
704
+ cfg_dynamic_disabled.vector_processing = MagicMock()
705
+ cfg_dynamic_disabled.vector_processing.dynamic_metrics = False
706
+
707
+ # Patch hydra.compose to return config with dynamic_metrics disabled
708
+ with (
709
+ patch(
710
+ "aiagents4pharma.talk2knowledgegraphs.tools."
711
+ "milvus_multimodal_subgraph_extraction.hydra.initialize"
712
+ ),
713
+ patch(
714
+ "aiagents4pharma.talk2knowledgegraphs.tools."
715
+ "milvus_multimodal_subgraph_extraction.hydra.compose"
716
+ ) as mock_compose,
717
+ ):
718
+ mock_compose.return_value = MagicMock()
719
+ mock_compose.return_value.app.frontend = self.cfg_db
720
+ mock_compose.return_value.tools.multimodal_subgraph_extraction = cfg_dynamic_disabled
721
+
722
+ response = self.tool.invoke(
723
+ input={
724
+ "prompt": self.prompt,
725
+ "tool_call_id": "subgraph_extraction_tool",
726
+ "state": self.state,
727
+ "arg_data": self.arg_data,
728
+ }
729
+ )
730
+
731
+ # Verify the test completed successfully
732
+ self.assertEqual(response.update["messages"][-1].tool_call_id, "subgraph_extraction_tool")
733
+
734
+ # Test the collection_side_effect with unknown name for final test
735
+ result = collection_side_effect("final_unknown_collection")
736
+ self.assertIsNone(result)