aiagents4pharma 0.0.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 (336) hide show
  1. aiagents4pharma/__init__.py +11 -0
  2. aiagents4pharma/talk2aiagents4pharma/.dockerignore +13 -0
  3. aiagents4pharma/talk2aiagents4pharma/Dockerfile +133 -0
  4. aiagents4pharma/talk2aiagents4pharma/README.md +1 -0
  5. aiagents4pharma/talk2aiagents4pharma/__init__.py +5 -0
  6. aiagents4pharma/talk2aiagents4pharma/agents/__init__.py +6 -0
  7. aiagents4pharma/talk2aiagents4pharma/agents/main_agent.py +70 -0
  8. aiagents4pharma/talk2aiagents4pharma/configs/__init__.py +5 -0
  9. aiagents4pharma/talk2aiagents4pharma/configs/agents/__init__.py +5 -0
  10. aiagents4pharma/talk2aiagents4pharma/configs/agents/main_agent/default.yaml +29 -0
  11. aiagents4pharma/talk2aiagents4pharma/configs/app/__init__.py +0 -0
  12. aiagents4pharma/talk2aiagents4pharma/configs/app/frontend/__init__.py +0 -0
  13. aiagents4pharma/talk2aiagents4pharma/configs/app/frontend/default.yaml +102 -0
  14. aiagents4pharma/talk2aiagents4pharma/configs/config.yaml +4 -0
  15. aiagents4pharma/talk2aiagents4pharma/docker-compose/cpu/.env.example +23 -0
  16. aiagents4pharma/talk2aiagents4pharma/docker-compose/cpu/docker-compose.yml +93 -0
  17. aiagents4pharma/talk2aiagents4pharma/docker-compose/gpu/.env.example +23 -0
  18. aiagents4pharma/talk2aiagents4pharma/docker-compose/gpu/docker-compose.yml +108 -0
  19. aiagents4pharma/talk2aiagents4pharma/install.md +154 -0
  20. aiagents4pharma/talk2aiagents4pharma/states/__init__.py +5 -0
  21. aiagents4pharma/talk2aiagents4pharma/states/state_talk2aiagents4pharma.py +18 -0
  22. aiagents4pharma/talk2aiagents4pharma/tests/__init__.py +3 -0
  23. aiagents4pharma/talk2aiagents4pharma/tests/test_main_agent.py +312 -0
  24. aiagents4pharma/talk2biomodels/.dockerignore +13 -0
  25. aiagents4pharma/talk2biomodels/Dockerfile +104 -0
  26. aiagents4pharma/talk2biomodels/README.md +1 -0
  27. aiagents4pharma/talk2biomodels/__init__.py +5 -0
  28. aiagents4pharma/talk2biomodels/agents/__init__.py +6 -0
  29. aiagents4pharma/talk2biomodels/agents/t2b_agent.py +104 -0
  30. aiagents4pharma/talk2biomodels/api/__init__.py +5 -0
  31. aiagents4pharma/talk2biomodels/api/ols.py +75 -0
  32. aiagents4pharma/talk2biomodels/api/uniprot.py +36 -0
  33. aiagents4pharma/talk2biomodels/configs/__init__.py +5 -0
  34. aiagents4pharma/talk2biomodels/configs/agents/__init__.py +5 -0
  35. aiagents4pharma/talk2biomodels/configs/agents/t2b_agent/__init__.py +3 -0
  36. aiagents4pharma/talk2biomodels/configs/agents/t2b_agent/default.yaml +14 -0
  37. aiagents4pharma/talk2biomodels/configs/app/__init__.py +0 -0
  38. aiagents4pharma/talk2biomodels/configs/app/frontend/__init__.py +0 -0
  39. aiagents4pharma/talk2biomodels/configs/app/frontend/default.yaml +72 -0
  40. aiagents4pharma/talk2biomodels/configs/config.yaml +7 -0
  41. aiagents4pharma/talk2biomodels/configs/tools/__init__.py +5 -0
  42. aiagents4pharma/talk2biomodels/configs/tools/ask_question/__init__.py +3 -0
  43. aiagents4pharma/talk2biomodels/configs/tools/ask_question/default.yaml +30 -0
  44. aiagents4pharma/talk2biomodels/configs/tools/custom_plotter/__init__.py +3 -0
  45. aiagents4pharma/talk2biomodels/configs/tools/custom_plotter/default.yaml +8 -0
  46. aiagents4pharma/talk2biomodels/configs/tools/get_annotation/__init__.py +3 -0
  47. aiagents4pharma/talk2biomodels/configs/tools/get_annotation/default.yaml +8 -0
  48. aiagents4pharma/talk2biomodels/install.md +63 -0
  49. aiagents4pharma/talk2biomodels/models/__init__.py +5 -0
  50. aiagents4pharma/talk2biomodels/models/basico_model.py +125 -0
  51. aiagents4pharma/talk2biomodels/models/sys_bio_model.py +60 -0
  52. aiagents4pharma/talk2biomodels/states/__init__.py +6 -0
  53. aiagents4pharma/talk2biomodels/states/state_talk2biomodels.py +49 -0
  54. aiagents4pharma/talk2biomodels/tests/BIOMD0000000449_url.xml +1585 -0
  55. aiagents4pharma/talk2biomodels/tests/__init__.py +3 -0
  56. aiagents4pharma/talk2biomodels/tests/article_on_model_537.pdf +0 -0
  57. aiagents4pharma/talk2biomodels/tests/test_api.py +31 -0
  58. aiagents4pharma/talk2biomodels/tests/test_ask_question.py +42 -0
  59. aiagents4pharma/talk2biomodels/tests/test_basico_model.py +67 -0
  60. aiagents4pharma/talk2biomodels/tests/test_get_annotation.py +190 -0
  61. aiagents4pharma/talk2biomodels/tests/test_getmodelinfo.py +92 -0
  62. aiagents4pharma/talk2biomodels/tests/test_integration.py +116 -0
  63. aiagents4pharma/talk2biomodels/tests/test_load_biomodel.py +35 -0
  64. aiagents4pharma/talk2biomodels/tests/test_param_scan.py +71 -0
  65. aiagents4pharma/talk2biomodels/tests/test_query_article.py +184 -0
  66. aiagents4pharma/talk2biomodels/tests/test_save_model.py +47 -0
  67. aiagents4pharma/talk2biomodels/tests/test_search_models.py +35 -0
  68. aiagents4pharma/talk2biomodels/tests/test_simulate_model.py +44 -0
  69. aiagents4pharma/talk2biomodels/tests/test_steady_state.py +86 -0
  70. aiagents4pharma/talk2biomodels/tests/test_sys_bio_model.py +67 -0
  71. aiagents4pharma/talk2biomodels/tools/__init__.py +17 -0
  72. aiagents4pharma/talk2biomodels/tools/ask_question.py +125 -0
  73. aiagents4pharma/talk2biomodels/tools/custom_plotter.py +165 -0
  74. aiagents4pharma/talk2biomodels/tools/get_annotation.py +342 -0
  75. aiagents4pharma/talk2biomodels/tools/get_modelinfo.py +159 -0
  76. aiagents4pharma/talk2biomodels/tools/load_arguments.py +134 -0
  77. aiagents4pharma/talk2biomodels/tools/load_biomodel.py +44 -0
  78. aiagents4pharma/talk2biomodels/tools/parameter_scan.py +310 -0
  79. aiagents4pharma/talk2biomodels/tools/query_article.py +64 -0
  80. aiagents4pharma/talk2biomodels/tools/save_model.py +98 -0
  81. aiagents4pharma/talk2biomodels/tools/search_models.py +96 -0
  82. aiagents4pharma/talk2biomodels/tools/simulate_model.py +137 -0
  83. aiagents4pharma/talk2biomodels/tools/steady_state.py +187 -0
  84. aiagents4pharma/talk2biomodels/tools/utils.py +23 -0
  85. aiagents4pharma/talk2cells/README.md +1 -0
  86. aiagents4pharma/talk2cells/__init__.py +5 -0
  87. aiagents4pharma/talk2cells/agents/__init__.py +6 -0
  88. aiagents4pharma/talk2cells/agents/scp_agent.py +87 -0
  89. aiagents4pharma/talk2cells/states/__init__.py +6 -0
  90. aiagents4pharma/talk2cells/states/state_talk2cells.py +15 -0
  91. aiagents4pharma/talk2cells/tests/scp_agent/test_scp_agent.py +22 -0
  92. aiagents4pharma/talk2cells/tools/__init__.py +6 -0
  93. aiagents4pharma/talk2cells/tools/scp_agent/__init__.py +6 -0
  94. aiagents4pharma/talk2cells/tools/scp_agent/display_studies.py +27 -0
  95. aiagents4pharma/talk2cells/tools/scp_agent/search_studies.py +78 -0
  96. aiagents4pharma/talk2knowledgegraphs/.dockerignore +13 -0
  97. aiagents4pharma/talk2knowledgegraphs/Dockerfile +131 -0
  98. aiagents4pharma/talk2knowledgegraphs/README.md +1 -0
  99. aiagents4pharma/talk2knowledgegraphs/__init__.py +5 -0
  100. aiagents4pharma/talk2knowledgegraphs/agents/__init__.py +5 -0
  101. aiagents4pharma/talk2knowledgegraphs/agents/t2kg_agent.py +99 -0
  102. aiagents4pharma/talk2knowledgegraphs/configs/__init__.py +5 -0
  103. aiagents4pharma/talk2knowledgegraphs/configs/agents/t2kg_agent/__init__.py +3 -0
  104. aiagents4pharma/talk2knowledgegraphs/configs/agents/t2kg_agent/default.yaml +62 -0
  105. aiagents4pharma/talk2knowledgegraphs/configs/app/__init__.py +5 -0
  106. aiagents4pharma/talk2knowledgegraphs/configs/app/frontend/__init__.py +3 -0
  107. aiagents4pharma/talk2knowledgegraphs/configs/app/frontend/default.yaml +79 -0
  108. aiagents4pharma/talk2knowledgegraphs/configs/config.yaml +13 -0
  109. aiagents4pharma/talk2knowledgegraphs/configs/tools/__init__.py +5 -0
  110. aiagents4pharma/talk2knowledgegraphs/configs/tools/graphrag_reasoning/__init__.py +3 -0
  111. aiagents4pharma/talk2knowledgegraphs/configs/tools/graphrag_reasoning/default.yaml +24 -0
  112. aiagents4pharma/talk2knowledgegraphs/configs/tools/multimodal_subgraph_extraction/__init__.py +0 -0
  113. aiagents4pharma/talk2knowledgegraphs/configs/tools/multimodal_subgraph_extraction/default.yaml +33 -0
  114. aiagents4pharma/talk2knowledgegraphs/configs/tools/subgraph_extraction/__init__.py +3 -0
  115. aiagents4pharma/talk2knowledgegraphs/configs/tools/subgraph_extraction/default.yaml +43 -0
  116. aiagents4pharma/talk2knowledgegraphs/configs/tools/subgraph_summarization/__init__.py +3 -0
  117. aiagents4pharma/talk2knowledgegraphs/configs/tools/subgraph_summarization/default.yaml +9 -0
  118. aiagents4pharma/talk2knowledgegraphs/configs/utils/database/milvus/__init__.py +3 -0
  119. aiagents4pharma/talk2knowledgegraphs/configs/utils/database/milvus/default.yaml +61 -0
  120. aiagents4pharma/talk2knowledgegraphs/configs/utils/enrichments/ols_terms/default.yaml +3 -0
  121. aiagents4pharma/talk2knowledgegraphs/configs/utils/enrichments/reactome_pathways/default.yaml +3 -0
  122. aiagents4pharma/talk2knowledgegraphs/configs/utils/enrichments/uniprot_proteins/default.yaml +6 -0
  123. aiagents4pharma/talk2knowledgegraphs/configs/utils/pubchem_utils/default.yaml +5 -0
  124. aiagents4pharma/talk2knowledgegraphs/datasets/__init__.py +5 -0
  125. aiagents4pharma/talk2knowledgegraphs/datasets/biobridge_primekg.py +607 -0
  126. aiagents4pharma/talk2knowledgegraphs/datasets/dataset.py +25 -0
  127. aiagents4pharma/talk2knowledgegraphs/datasets/primekg.py +212 -0
  128. aiagents4pharma/talk2knowledgegraphs/datasets/starkqa_primekg.py +210 -0
  129. aiagents4pharma/talk2knowledgegraphs/docker-compose/cpu/.env.example +23 -0
  130. aiagents4pharma/talk2knowledgegraphs/docker-compose/cpu/docker-compose.yml +93 -0
  131. aiagents4pharma/talk2knowledgegraphs/docker-compose/gpu/.env.example +23 -0
  132. aiagents4pharma/talk2knowledgegraphs/docker-compose/gpu/docker-compose.yml +108 -0
  133. aiagents4pharma/talk2knowledgegraphs/entrypoint.sh +180 -0
  134. aiagents4pharma/talk2knowledgegraphs/install.md +165 -0
  135. aiagents4pharma/talk2knowledgegraphs/milvus_data_dump.py +886 -0
  136. aiagents4pharma/talk2knowledgegraphs/states/__init__.py +5 -0
  137. aiagents4pharma/talk2knowledgegraphs/states/state_talk2knowledgegraphs.py +40 -0
  138. aiagents4pharma/talk2knowledgegraphs/tests/__init__.py +0 -0
  139. aiagents4pharma/talk2knowledgegraphs/tests/test_agents_t2kg_agent.py +318 -0
  140. aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_biobridge_primekg.py +248 -0
  141. aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_dataset.py +33 -0
  142. aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_primekg.py +86 -0
  143. aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_starkqa_primekg.py +125 -0
  144. aiagents4pharma/talk2knowledgegraphs/tests/test_tools_graphrag_reasoning.py +257 -0
  145. aiagents4pharma/talk2knowledgegraphs/tests/test_tools_milvus_multimodal_subgraph_extraction.py +1444 -0
  146. aiagents4pharma/talk2knowledgegraphs/tests/test_tools_multimodal_subgraph_extraction.py +159 -0
  147. aiagents4pharma/talk2knowledgegraphs/tests/test_tools_subgraph_extraction.py +152 -0
  148. aiagents4pharma/talk2knowledgegraphs/tests/test_tools_subgraph_summarization.py +201 -0
  149. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_database_milvus_connection_manager.py +812 -0
  150. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_embeddings.py +51 -0
  151. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_huggingface.py +49 -0
  152. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_nim_molmim.py +59 -0
  153. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_ollama.py +63 -0
  154. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_sentencetransformer.py +47 -0
  155. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_enrichments.py +40 -0
  156. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_ollama.py +94 -0
  157. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_ols.py +70 -0
  158. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_pubchem.py +45 -0
  159. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_reactome.py +44 -0
  160. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_uniprot.py +48 -0
  161. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_extractions_milvus_multimodal_pcst.py +759 -0
  162. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_kg_utils.py +78 -0
  163. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_pubchem_utils.py +123 -0
  164. aiagents4pharma/talk2knowledgegraphs/tools/__init__.py +11 -0
  165. aiagents4pharma/talk2knowledgegraphs/tools/graphrag_reasoning.py +138 -0
  166. aiagents4pharma/talk2knowledgegraphs/tools/load_arguments.py +22 -0
  167. aiagents4pharma/talk2knowledgegraphs/tools/milvus_multimodal_subgraph_extraction.py +965 -0
  168. aiagents4pharma/talk2knowledgegraphs/tools/multimodal_subgraph_extraction.py +374 -0
  169. aiagents4pharma/talk2knowledgegraphs/tools/subgraph_extraction.py +291 -0
  170. aiagents4pharma/talk2knowledgegraphs/tools/subgraph_summarization.py +123 -0
  171. aiagents4pharma/talk2knowledgegraphs/utils/__init__.py +5 -0
  172. aiagents4pharma/talk2knowledgegraphs/utils/database/__init__.py +5 -0
  173. aiagents4pharma/talk2knowledgegraphs/utils/database/milvus_connection_manager.py +586 -0
  174. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/__init__.py +5 -0
  175. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/embeddings.py +81 -0
  176. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/huggingface.py +111 -0
  177. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/nim_molmim.py +54 -0
  178. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/ollama.py +87 -0
  179. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/sentence_transformer.py +73 -0
  180. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/__init__.py +12 -0
  181. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/enrichments.py +37 -0
  182. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/ollama.py +129 -0
  183. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/ols_terms.py +89 -0
  184. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/pubchem_strings.py +78 -0
  185. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/reactome_pathways.py +71 -0
  186. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/uniprot_proteins.py +98 -0
  187. aiagents4pharma/talk2knowledgegraphs/utils/extractions/__init__.py +5 -0
  188. aiagents4pharma/talk2knowledgegraphs/utils/extractions/milvus_multimodal_pcst.py +762 -0
  189. aiagents4pharma/talk2knowledgegraphs/utils/extractions/multimodal_pcst.py +298 -0
  190. aiagents4pharma/talk2knowledgegraphs/utils/extractions/pcst.py +229 -0
  191. aiagents4pharma/talk2knowledgegraphs/utils/kg_utils.py +67 -0
  192. aiagents4pharma/talk2knowledgegraphs/utils/pubchem_utils.py +104 -0
  193. aiagents4pharma/talk2scholars/.dockerignore +13 -0
  194. aiagents4pharma/talk2scholars/Dockerfile +104 -0
  195. aiagents4pharma/talk2scholars/README.md +1 -0
  196. aiagents4pharma/talk2scholars/__init__.py +7 -0
  197. aiagents4pharma/talk2scholars/agents/__init__.py +13 -0
  198. aiagents4pharma/talk2scholars/agents/main_agent.py +89 -0
  199. aiagents4pharma/talk2scholars/agents/paper_download_agent.py +96 -0
  200. aiagents4pharma/talk2scholars/agents/pdf_agent.py +101 -0
  201. aiagents4pharma/talk2scholars/agents/s2_agent.py +135 -0
  202. aiagents4pharma/talk2scholars/agents/zotero_agent.py +127 -0
  203. aiagents4pharma/talk2scholars/configs/__init__.py +7 -0
  204. aiagents4pharma/talk2scholars/configs/agents/__init__.py +7 -0
  205. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/__init__.py +7 -0
  206. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/main_agent/__init__.py +3 -0
  207. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/main_agent/default.yaml +52 -0
  208. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/paper_download_agent/__init__.py +3 -0
  209. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/paper_download_agent/default.yaml +19 -0
  210. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/pdf_agent/__init__.py +3 -0
  211. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/pdf_agent/default.yaml +19 -0
  212. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/s2_agent/__init__.py +3 -0
  213. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/s2_agent/default.yaml +44 -0
  214. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/zotero_agent/__init__.py +3 -0
  215. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/zotero_agent/default.yaml +19 -0
  216. aiagents4pharma/talk2scholars/configs/app/__init__.py +7 -0
  217. aiagents4pharma/talk2scholars/configs/app/frontend/__init__.py +3 -0
  218. aiagents4pharma/talk2scholars/configs/app/frontend/default.yaml +72 -0
  219. aiagents4pharma/talk2scholars/configs/config.yaml +16 -0
  220. aiagents4pharma/talk2scholars/configs/tools/__init__.py +21 -0
  221. aiagents4pharma/talk2scholars/configs/tools/multi_paper_recommendation/__init__.py +3 -0
  222. aiagents4pharma/talk2scholars/configs/tools/multi_paper_recommendation/default.yaml +26 -0
  223. aiagents4pharma/talk2scholars/configs/tools/paper_download/__init__.py +3 -0
  224. aiagents4pharma/talk2scholars/configs/tools/paper_download/default.yaml +124 -0
  225. aiagents4pharma/talk2scholars/configs/tools/question_and_answer/__init__.py +3 -0
  226. aiagents4pharma/talk2scholars/configs/tools/question_and_answer/default.yaml +62 -0
  227. aiagents4pharma/talk2scholars/configs/tools/retrieve_semantic_scholar_paper_id/__init__.py +3 -0
  228. aiagents4pharma/talk2scholars/configs/tools/retrieve_semantic_scholar_paper_id/default.yaml +12 -0
  229. aiagents4pharma/talk2scholars/configs/tools/search/__init__.py +3 -0
  230. aiagents4pharma/talk2scholars/configs/tools/search/default.yaml +26 -0
  231. aiagents4pharma/talk2scholars/configs/tools/single_paper_recommendation/__init__.py +3 -0
  232. aiagents4pharma/talk2scholars/configs/tools/single_paper_recommendation/default.yaml +26 -0
  233. aiagents4pharma/talk2scholars/configs/tools/zotero_read/__init__.py +3 -0
  234. aiagents4pharma/talk2scholars/configs/tools/zotero_read/default.yaml +57 -0
  235. aiagents4pharma/talk2scholars/configs/tools/zotero_write/__inti__.py +3 -0
  236. aiagents4pharma/talk2scholars/configs/tools/zotero_write/default.yaml +55 -0
  237. aiagents4pharma/talk2scholars/docker-compose/cpu/.env.example +21 -0
  238. aiagents4pharma/talk2scholars/docker-compose/cpu/docker-compose.yml +90 -0
  239. aiagents4pharma/talk2scholars/docker-compose/gpu/.env.example +21 -0
  240. aiagents4pharma/talk2scholars/docker-compose/gpu/docker-compose.yml +105 -0
  241. aiagents4pharma/talk2scholars/install.md +122 -0
  242. aiagents4pharma/talk2scholars/state/__init__.py +7 -0
  243. aiagents4pharma/talk2scholars/state/state_talk2scholars.py +98 -0
  244. aiagents4pharma/talk2scholars/tests/__init__.py +3 -0
  245. aiagents4pharma/talk2scholars/tests/test_agents_main_agent.py +256 -0
  246. aiagents4pharma/talk2scholars/tests/test_agents_paper_agents_download_agent.py +139 -0
  247. aiagents4pharma/talk2scholars/tests/test_agents_pdf_agent.py +114 -0
  248. aiagents4pharma/talk2scholars/tests/test_agents_s2_agent.py +198 -0
  249. aiagents4pharma/talk2scholars/tests/test_agents_zotero_agent.py +160 -0
  250. aiagents4pharma/talk2scholars/tests/test_s2_tools_display_dataframe.py +91 -0
  251. aiagents4pharma/talk2scholars/tests/test_s2_tools_query_dataframe.py +191 -0
  252. aiagents4pharma/talk2scholars/tests/test_states_state.py +38 -0
  253. aiagents4pharma/talk2scholars/tests/test_tools_paper_downloader.py +507 -0
  254. aiagents4pharma/talk2scholars/tests/test_tools_question_and_answer_tool.py +105 -0
  255. aiagents4pharma/talk2scholars/tests/test_tools_s2_multi.py +307 -0
  256. aiagents4pharma/talk2scholars/tests/test_tools_s2_retrieve.py +67 -0
  257. aiagents4pharma/talk2scholars/tests/test_tools_s2_search.py +286 -0
  258. aiagents4pharma/talk2scholars/tests/test_tools_s2_single.py +298 -0
  259. aiagents4pharma/talk2scholars/tests/test_utils_arxiv_downloader.py +469 -0
  260. aiagents4pharma/talk2scholars/tests/test_utils_base_paper_downloader.py +598 -0
  261. aiagents4pharma/talk2scholars/tests/test_utils_biorxiv_downloader.py +669 -0
  262. aiagents4pharma/talk2scholars/tests/test_utils_medrxiv_downloader.py +500 -0
  263. aiagents4pharma/talk2scholars/tests/test_utils_nvidia_nim_reranker.py +117 -0
  264. aiagents4pharma/talk2scholars/tests/test_utils_pdf_answer_formatter.py +67 -0
  265. aiagents4pharma/talk2scholars/tests/test_utils_pdf_batch_processor.py +92 -0
  266. aiagents4pharma/talk2scholars/tests/test_utils_pdf_collection_manager.py +173 -0
  267. aiagents4pharma/talk2scholars/tests/test_utils_pdf_document_processor.py +68 -0
  268. aiagents4pharma/talk2scholars/tests/test_utils_pdf_generate_answer.py +72 -0
  269. aiagents4pharma/talk2scholars/tests/test_utils_pdf_gpu_detection.py +129 -0
  270. aiagents4pharma/talk2scholars/tests/test_utils_pdf_paper_loader.py +116 -0
  271. aiagents4pharma/talk2scholars/tests/test_utils_pdf_rag_pipeline.py +88 -0
  272. aiagents4pharma/talk2scholars/tests/test_utils_pdf_retrieve_chunks.py +190 -0
  273. aiagents4pharma/talk2scholars/tests/test_utils_pdf_singleton_manager.py +159 -0
  274. aiagents4pharma/talk2scholars/tests/test_utils_pdf_vector_normalization.py +121 -0
  275. aiagents4pharma/talk2scholars/tests/test_utils_pdf_vector_store.py +406 -0
  276. aiagents4pharma/talk2scholars/tests/test_utils_pubmed_downloader.py +1007 -0
  277. aiagents4pharma/talk2scholars/tests/test_utils_read_helper_utils.py +106 -0
  278. aiagents4pharma/talk2scholars/tests/test_utils_s2_utils_ext_ids.py +403 -0
  279. aiagents4pharma/talk2scholars/tests/test_utils_tool_helper_utils.py +85 -0
  280. aiagents4pharma/talk2scholars/tests/test_utils_zotero_human_in_the_loop.py +266 -0
  281. aiagents4pharma/talk2scholars/tests/test_utils_zotero_path.py +496 -0
  282. aiagents4pharma/talk2scholars/tests/test_utils_zotero_pdf_downloader_utils.py +46 -0
  283. aiagents4pharma/talk2scholars/tests/test_utils_zotero_read.py +743 -0
  284. aiagents4pharma/talk2scholars/tests/test_utils_zotero_write.py +151 -0
  285. aiagents4pharma/talk2scholars/tools/__init__.py +9 -0
  286. aiagents4pharma/talk2scholars/tools/paper_download/__init__.py +12 -0
  287. aiagents4pharma/talk2scholars/tools/paper_download/paper_downloader.py +442 -0
  288. aiagents4pharma/talk2scholars/tools/paper_download/utils/__init__.py +22 -0
  289. aiagents4pharma/talk2scholars/tools/paper_download/utils/arxiv_downloader.py +207 -0
  290. aiagents4pharma/talk2scholars/tools/paper_download/utils/base_paper_downloader.py +336 -0
  291. aiagents4pharma/talk2scholars/tools/paper_download/utils/biorxiv_downloader.py +313 -0
  292. aiagents4pharma/talk2scholars/tools/paper_download/utils/medrxiv_downloader.py +196 -0
  293. aiagents4pharma/talk2scholars/tools/paper_download/utils/pubmed_downloader.py +323 -0
  294. aiagents4pharma/talk2scholars/tools/pdf/__init__.py +7 -0
  295. aiagents4pharma/talk2scholars/tools/pdf/question_and_answer.py +170 -0
  296. aiagents4pharma/talk2scholars/tools/pdf/utils/__init__.py +37 -0
  297. aiagents4pharma/talk2scholars/tools/pdf/utils/answer_formatter.py +62 -0
  298. aiagents4pharma/talk2scholars/tools/pdf/utils/batch_processor.py +198 -0
  299. aiagents4pharma/talk2scholars/tools/pdf/utils/collection_manager.py +172 -0
  300. aiagents4pharma/talk2scholars/tools/pdf/utils/document_processor.py +76 -0
  301. aiagents4pharma/talk2scholars/tools/pdf/utils/generate_answer.py +97 -0
  302. aiagents4pharma/talk2scholars/tools/pdf/utils/get_vectorstore.py +59 -0
  303. aiagents4pharma/talk2scholars/tools/pdf/utils/gpu_detection.py +150 -0
  304. aiagents4pharma/talk2scholars/tools/pdf/utils/nvidia_nim_reranker.py +97 -0
  305. aiagents4pharma/talk2scholars/tools/pdf/utils/paper_loader.py +123 -0
  306. aiagents4pharma/talk2scholars/tools/pdf/utils/rag_pipeline.py +113 -0
  307. aiagents4pharma/talk2scholars/tools/pdf/utils/retrieve_chunks.py +197 -0
  308. aiagents4pharma/talk2scholars/tools/pdf/utils/singleton_manager.py +140 -0
  309. aiagents4pharma/talk2scholars/tools/pdf/utils/tool_helper.py +86 -0
  310. aiagents4pharma/talk2scholars/tools/pdf/utils/vector_normalization.py +150 -0
  311. aiagents4pharma/talk2scholars/tools/pdf/utils/vector_store.py +327 -0
  312. aiagents4pharma/talk2scholars/tools/s2/__init__.py +21 -0
  313. aiagents4pharma/talk2scholars/tools/s2/display_dataframe.py +110 -0
  314. aiagents4pharma/talk2scholars/tools/s2/multi_paper_rec.py +111 -0
  315. aiagents4pharma/talk2scholars/tools/s2/query_dataframe.py +233 -0
  316. aiagents4pharma/talk2scholars/tools/s2/retrieve_semantic_scholar_paper_id.py +128 -0
  317. aiagents4pharma/talk2scholars/tools/s2/search.py +101 -0
  318. aiagents4pharma/talk2scholars/tools/s2/single_paper_rec.py +102 -0
  319. aiagents4pharma/talk2scholars/tools/s2/utils/__init__.py +5 -0
  320. aiagents4pharma/talk2scholars/tools/s2/utils/multi_helper.py +223 -0
  321. aiagents4pharma/talk2scholars/tools/s2/utils/search_helper.py +205 -0
  322. aiagents4pharma/talk2scholars/tools/s2/utils/single_helper.py +216 -0
  323. aiagents4pharma/talk2scholars/tools/zotero/__init__.py +7 -0
  324. aiagents4pharma/talk2scholars/tools/zotero/utils/__init__.py +7 -0
  325. aiagents4pharma/talk2scholars/tools/zotero/utils/read_helper.py +270 -0
  326. aiagents4pharma/talk2scholars/tools/zotero/utils/review_helper.py +74 -0
  327. aiagents4pharma/talk2scholars/tools/zotero/utils/write_helper.py +194 -0
  328. aiagents4pharma/talk2scholars/tools/zotero/utils/zotero_path.py +180 -0
  329. aiagents4pharma/talk2scholars/tools/zotero/utils/zotero_pdf_downloader.py +133 -0
  330. aiagents4pharma/talk2scholars/tools/zotero/zotero_read.py +105 -0
  331. aiagents4pharma/talk2scholars/tools/zotero/zotero_review.py +162 -0
  332. aiagents4pharma/talk2scholars/tools/zotero/zotero_write.py +91 -0
  333. aiagents4pharma-0.0.0.dist-info/METADATA +335 -0
  334. aiagents4pharma-0.0.0.dist-info/RECORD +336 -0
  335. aiagents4pharma-0.0.0.dist-info/WHEEL +4 -0
  336. aiagents4pharma-0.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,496 @@
1
+ """
2
+ Unit tests for Zotero path utility in zotero_path.py.
3
+ """
4
+
5
+ import unittest
6
+ from unittest.mock import MagicMock, patch
7
+
8
+ import pytest
9
+
10
+ from aiagents4pharma.talk2scholars.tools.zotero.utils.zotero_path import (
11
+ fetch_papers_for_save,
12
+ find_or_create_collection,
13
+ get_all_collection_paths,
14
+ get_item_collections,
15
+ )
16
+ from aiagents4pharma.talk2scholars.tools.zotero.zotero_read import (
17
+ zotero_read,
18
+ )
19
+ from aiagents4pharma.talk2scholars.tools.zotero.zotero_write import (
20
+ zotero_write,
21
+ )
22
+
23
+
24
+ class TestGetItemCollections(unittest.TestCase):
25
+ """Unit tests for the get_item_collections function."""
26
+
27
+ def test_basic_collections_mapping(self):
28
+ """test_basic_collections_mapping"""
29
+ # Define fake collections with one parent-child relationship and one independent collection.
30
+ fake_collections = [
31
+ {"key": "A", "data": {"name": "Parent", "parentCollection": None}},
32
+ {"key": "B", "data": {"name": "Child", "parentCollection": "A"}},
33
+ {"key": "C", "data": {"name": "Independent", "parentCollection": None}},
34
+ ]
35
+ # Define fake collection items for each collection:
36
+ # - Collection A returns one item with key "item1"
37
+ # - Collection B returns one item with key "item2"
38
+ # - Collection C returns two items: one duplicate ("item1") and one new ("item3")
39
+ fake_collection_items = {
40
+ "A": [{"data": {"key": "item1"}}],
41
+ "B": [{"data": {"key": "item2"}}],
42
+ "C": [{"data": {"key": "item1"}}, {"data": {"key": "item3"}}],
43
+ }
44
+ fake_zot = MagicMock()
45
+ fake_zot.collections.return_value = fake_collections
46
+
47
+ # When collection_items is called, return the appropriate list based on collection key.
48
+ def fake_collection_items_func(collection_key):
49
+ return fake_collection_items.get(collection_key, [])
50
+
51
+ fake_zot.collection_items.side_effect = fake_collection_items_func
52
+
53
+ # Expected full collection paths:
54
+ # - Collection A: "/Parent"
55
+ # - Collection B: "/Parent/Child" (child of A)
56
+ # - Collection C: "/Independent"
57
+ #
58
+ # Expected mapping for items:
59
+ # - "item1" appears in collections A and C → ["/Parent", "/Independent"]
60
+ # - "item2" appears in B → ["/Parent/Child"]
61
+ # - "item3" appears in C → ["/Independent"]
62
+ expected_mapping = {
63
+ "item1": ["/Parent", "/Independent"],
64
+ "item2": ["/Parent/Child"],
65
+ "item3": ["/Independent"],
66
+ }
67
+
68
+ result = get_item_collections(fake_zot)
69
+ self.assertEqual(result, expected_mapping)
70
+
71
+
72
+ class TestFindOrCreateCollectionExtra(unittest.TestCase):
73
+ """Extra tests for the find_or_create_collection function."""
74
+
75
+ def setUp(self):
76
+ """Set up a fake Zotero client with some default collections."""
77
+ # Set up a fake Zotero client with some default collections.
78
+ self.fake_zot = MagicMock()
79
+ self.fake_zot.collections.return_value = [
80
+ {"key": "parent1", "data": {"name": "Parent", "parentCollection": None}},
81
+ {"key": "child1", "data": {"name": "Child", "parentCollection": "parent1"}},
82
+ ]
83
+
84
+ def test_empty_path(self):
85
+ """Test that an empty path returns None."""
86
+ result = find_or_create_collection(self.fake_zot, "", create_missing=False)
87
+ self.assertIsNone(result)
88
+
89
+ def test_create_collection_with_success_key(self):
90
+ """
91
+ Test that when create_missing is True and the response contains a "success" key,
92
+ the function returns the new collection key.
93
+ """
94
+ # Simulate no existing collections (so direct match fails)
95
+ self.fake_zot.collections.return_value = []
96
+ # Simulate create_collection returning a dict with a "success" key.
97
+ self.fake_zot.create_collection.return_value = {"success": {"0": "new_key_success"}}
98
+ result = find_or_create_collection(self.fake_zot, "/NewCollection", create_missing=True)
99
+ self.assertEqual(result, "new_key_success")
100
+ # Verify payload formatting: for a simple (non-nested) path, no parentCollection.
101
+ args, _ = self.fake_zot.create_collection.call_args
102
+ payload = args[0]
103
+ self.assertEqual(payload["name"], "newcollection")
104
+ self.assertNotIn("parentCollection", payload)
105
+
106
+ def test_create_collection_with_successful_key(self):
107
+ """
108
+ Test that when create_missing is True and the response contains a "successful" key,
109
+ the function returns the new collection key.
110
+ """
111
+ self.fake_zot.collections.return_value = []
112
+ self.fake_zot.create_collection.return_value = {
113
+ "successful": {"0": {"data": {"key": "new_key_successful"}}}
114
+ }
115
+ result = find_or_create_collection(self.fake_zot, "/NewCollection", create_missing=True)
116
+ self.assertEqual(result, "new_key_successful")
117
+
118
+ def test_create_collection_exception(self):
119
+ """
120
+ Test that if create_collection raises an exception,
121
+ the function logs the error and returns None.
122
+ """
123
+ self.fake_zot.collections.return_value = []
124
+ self.fake_zot.create_collection.side_effect = Exception("Creation error")
125
+ result = find_or_create_collection(self.fake_zot, "/NewCollection", create_missing=True)
126
+ self.assertIsNone(result)
127
+
128
+
129
+ class TestZoteroPath:
130
+ """Tests for the zotero_path utility functions."""
131
+
132
+ def test_fetch_papers_for_save_no_papers(self):
133
+ """Test that fetch_papers_for_save returns None when no papers are available."""
134
+ # Empty state
135
+ state = {}
136
+ assert fetch_papers_for_save(state) is None
137
+
138
+ # State with empty last_displayed_papers
139
+ state = {"last_displayed_papers": ""}
140
+ assert fetch_papers_for_save(state) is None
141
+
142
+ # State with last_displayed_papers pointing to non-existent key
143
+ state = {"last_displayed_papers": "nonexistent_key"}
144
+ assert fetch_papers_for_save(state) is None
145
+
146
+ def test_fetch_papers_for_save_with_papers(self):
147
+ """Test that fetch_papers_for_save correctly retrieves papers from state."""
148
+ # State with direct papers
149
+ sample_papers = {"paper1": {"Title": "Test Paper"}}
150
+ state = {"last_displayed_papers": sample_papers}
151
+ assert fetch_papers_for_save(state) == sample_papers
152
+
153
+ # State with papers referenced by key
154
+ state = {"last_displayed_papers": "zotero_read", "zotero_read": sample_papers}
155
+ assert fetch_papers_for_save(state) == sample_papers
156
+
157
+ @patch("pyzotero.zotero.Zotero")
158
+ def test_find_or_create_collection_exact_match(self, mock_zotero):
159
+ """Test that find_or_create_collection correctly finds an exact match."""
160
+ # Setup mock
161
+ mock_zot = MagicMock()
162
+ mock_zotero.return_value = mock_zot
163
+
164
+ # Setup collections
165
+ collections = [
166
+ {"key": "abc123", "data": {"name": "Curiosity", "parentCollection": None}},
167
+ {
168
+ "key": "def456",
169
+ "data": {"name": "Curiosity1", "parentCollection": "abc123"},
170
+ },
171
+ {"key": "ghi789", "data": {"name": "Random", "parentCollection": None}},
172
+ {"key": "rad123", "data": {"name": "radiation", "parentCollection": None}},
173
+ ]
174
+ mock_zot.collections.return_value = collections
175
+
176
+ # Test finding "Curiosity"
177
+ result = find_or_create_collection(mock_zot, "/Curiosity")
178
+ assert result == "abc123"
179
+
180
+ # Test finding with different case
181
+ result = find_or_create_collection(mock_zot, "/curiosity")
182
+ assert result == "abc123"
183
+
184
+ # Test finding "radiation" - direct match
185
+ result = find_or_create_collection(mock_zot, "/radiation")
186
+ assert result == "rad123"
187
+
188
+ # Test finding without leading slash
189
+ result = find_or_create_collection(mock_zot, "radiation")
190
+ assert result == "rad123"
191
+
192
+ @patch("pyzotero.zotero.Zotero")
193
+ def test_find_or_create_collection_no_match(self, mock_zotero):
194
+ """Test that find_or_create_collection returns None for non-existent collections."""
195
+ # Setup mock
196
+ mock_zot = MagicMock()
197
+ mock_zotero.return_value = mock_zot
198
+
199
+ # Setup collections
200
+ collections = [
201
+ {"key": "abc123", "data": {"name": "Curiosity", "parentCollection": None}},
202
+ {
203
+ "key": "def456",
204
+ "data": {"name": "Curiosity1", "parentCollection": "abc123"},
205
+ },
206
+ ]
207
+ mock_zot.collections.return_value = collections
208
+
209
+ # Test finding non-existent "Curiosity2"
210
+ result = find_or_create_collection(mock_zot, "/Curiosity2")
211
+ assert result is None
212
+
213
+ # Test finding non-existent nested path
214
+ result = find_or_create_collection(mock_zot, "/Curiosity/Curiosity2")
215
+ assert result is None
216
+
217
+ @patch("pyzotero.zotero.Zotero")
218
+ def test_find_or_create_collection_with_creation(self, mock_zotero):
219
+ """Test that find_or_create_collection creates collections when requested."""
220
+ # Setup mock
221
+ mock_zot = MagicMock()
222
+ mock_zotero.return_value = mock_zot
223
+
224
+ # Setup collections
225
+ collections = [{"key": "abc123", "data": {"name": "Curiosity", "parentCollection": None}}]
226
+ mock_zot.collections.return_value = collections
227
+
228
+ # Setup create_collection response
229
+ mock_zot.create_collection.return_value = {
230
+ "successful": {"0": {"data": {"key": "new_key"}}}
231
+ }
232
+
233
+ # Test creating "Curiosity2" - note we're expecting lowercase in the call
234
+ result = find_or_create_collection(mock_zot, "/Curiosity2", create_missing=True)
235
+ assert result == "new_key"
236
+ # Use case-insensitive check for the collection name
237
+ mock_zot.create_collection.assert_called_once()
238
+ call_args = mock_zot.create_collection.call_args[0][0]
239
+ assert "name" in call_args
240
+ assert call_args["name"].lower() == "curiosity2"
241
+
242
+ # Test creating nested "Curiosity/Curiosity2"
243
+ mock_zot.create_collection.reset_mock()
244
+ result = find_or_create_collection(mock_zot, "/Curiosity/Curiosity2", create_missing=True)
245
+ assert result == "new_key"
246
+ # Check that the call includes parentCollection
247
+ mock_zot.create_collection.assert_called_once()
248
+ call_args = mock_zot.create_collection.call_args[0][0]
249
+ assert "name" in call_args
250
+ assert "parentCollection" in call_args
251
+ assert call_args["name"].lower() == "curiosity2"
252
+ assert call_args["parentCollection"] == "abc123"
253
+
254
+ @patch("pyzotero.zotero.Zotero")
255
+ def test_get_all_collection_paths(self, mock_zotero):
256
+ """Test that get_all_collection_paths returns correct paths."""
257
+ # Setup mock
258
+ mock_zot = MagicMock()
259
+ mock_zotero.return_value = mock_zot
260
+
261
+ # Setup collections
262
+ collections = [
263
+ {"key": "abc123", "data": {"name": "Curiosity", "parentCollection": None}},
264
+ {
265
+ "key": "def456",
266
+ "data": {"name": "Curiosity1", "parentCollection": "abc123"},
267
+ },
268
+ {"key": "ghi789", "data": {"name": "Random", "parentCollection": None}},
269
+ ]
270
+ mock_zot.collections.return_value = collections
271
+
272
+ # Test getting all paths
273
+ result = get_all_collection_paths(mock_zot)
274
+ assert "/Curiosity" in result
275
+ assert "/Random" in result
276
+ assert "/Curiosity/Curiosity1" in result
277
+
278
+
279
+ class TestZoteroWrite:
280
+ """Integration tests for zotero_write.py."""
281
+
282
+ @pytest.fixture
283
+ def mock_hydra(self):
284
+ """Fixture to mock hydra configuration."""
285
+ with patch(
286
+ "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.hydra.compose"
287
+ ) as mock_compose:
288
+ cfg = MagicMock()
289
+ cfg.tools.zotero_write.user_id = "test_user"
290
+ cfg.tools.zotero_write.library_type = "user"
291
+ cfg.tools.zotero_write.api_key = "test_key"
292
+ cfg.tools.zotero_write.zotero = MagicMock()
293
+ cfg.tools.zotero_write.zotero.max_limit = 50
294
+ mock_compose.return_value = cfg
295
+ yield cfg
296
+
297
+ @pytest.fixture
298
+ def mock_zotero(self):
299
+ """Fixture to mock Zotero client."""
300
+ with patch(
301
+ "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.zotero.Zotero"
302
+ ) as mock_zot_class:
303
+ mock_zot = MagicMock()
304
+ mock_zot_class.return_value = mock_zot
305
+ yield mock_zot
306
+
307
+ @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.fetch_papers_for_save")
308
+ @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.hydra.initialize")
309
+ @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.hydra.compose")
310
+ @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.zotero.Zotero")
311
+ def test_zotero_write_no_papers(self, mock_zotero_class, mock_compose, _, mock_fetch):
312
+ """When no papers exist (even after approval), the function raises a ValueError."""
313
+ # Mock hydra configuration
314
+ cfg = MagicMock()
315
+ cfg.user_id = "test_user"
316
+ cfg.library_type = "user"
317
+ cfg.api_key = "test_key"
318
+ mock_compose.return_value = MagicMock()
319
+ mock_compose.return_value.tools.zotero_write = cfg
320
+
321
+ # Mock Zotero client
322
+ mock_zot = MagicMock()
323
+ mock_zotero_class.return_value = mock_zot
324
+
325
+ mock_fetch.return_value = None
326
+
327
+ state = {
328
+ "zotero_write_approval_status": {
329
+ "approved": True,
330
+ "collection_path": "/Curiosity",
331
+ }
332
+ }
333
+
334
+ with pytest.raises(ValueError) as excinfo:
335
+ zotero_write.run(
336
+ {
337
+ "tool_call_id": "test_id",
338
+ "collection_path": "/Curiosity",
339
+ "state": state,
340
+ }
341
+ )
342
+ assert "No fetched papers were found to save" in str(excinfo.value)
343
+
344
+ @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.fetch_papers_for_save")
345
+ @patch(
346
+ "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.find_or_create_collection"
347
+ )
348
+ @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.hydra.initialize")
349
+ @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.hydra.compose")
350
+ @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.zotero.Zotero")
351
+ def test_zotero_write_invalid_collection(
352
+ self, mock_zotero_class, mock_compose, _, mock_find, mock_fetch
353
+ ):
354
+ """Saving to a nonexistent Zotero collection returns an error Command."""
355
+ # Mock hydra configuration
356
+ cfg = MagicMock()
357
+ cfg.user_id = "test_user"
358
+ cfg.library_type = "user"
359
+ cfg.api_key = "test_key"
360
+ mock_compose.return_value = MagicMock()
361
+ mock_compose.return_value.tools.zotero_write = cfg
362
+
363
+ # Mock Zotero client
364
+ mock_zot = MagicMock()
365
+ mock_zotero_class.return_value = mock_zot
366
+ mock_zot.collections.return_value = [
367
+ {"key": "k1", "data": {"name": "Curiosity"}},
368
+ {"key": "k2", "data": {"name": "Random"}},
369
+ ]
370
+
371
+ sample = {"paper1": {"Title": "Test Paper"}}
372
+ mock_fetch.return_value = sample
373
+ mock_find.return_value = None
374
+
375
+ state = {
376
+ "zotero_write_approval_status": {
377
+ "approved": True,
378
+ "collection_path": "/NonExistent",
379
+ },
380
+ "last_displayed_papers": "papers",
381
+ "papers": sample,
382
+ }
383
+
384
+ result = zotero_write.run(
385
+ {
386
+ "tool_call_id": "test_id",
387
+ "collection_path": "/NonExistent",
388
+ "state": state,
389
+ }
390
+ )
391
+
392
+ msg = result.update["messages"][0].content
393
+ assert "does not exist in Zotero" in msg
394
+ assert "Curiosity, Random" in msg
395
+
396
+ @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.fetch_papers_for_save")
397
+ @patch(
398
+ "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.find_or_create_collection"
399
+ )
400
+ def test_zotero_write_success(self, mock_find, mock_fetch, mock_hydra, mock_zotero):
401
+ """A valid approved save returns a success Command with summary."""
402
+ sample = {"paper1": {"Title": "Test Paper", "Authors": ["Test Author"]}}
403
+ mock_fetch.return_value = sample
404
+ mock_find.return_value = "abc123"
405
+ mock_zotero.collections.return_value = [{"key": "abc123", "data": {"name": "radiation"}}]
406
+ mock_zotero.create_items.return_value = {"successful": {"0": {"key": "item123"}}}
407
+ mock_hydra.tools.zotero_write.zotero.max_limit = 50
408
+
409
+ state = {
410
+ "zotero_write_approval_status": {
411
+ "approved": True,
412
+ "collection_path": "/radiation",
413
+ },
414
+ "last_displayed_papers": "papers",
415
+ "papers": sample,
416
+ }
417
+
418
+ result = zotero_write.run(
419
+ {
420
+ "tool_call_id": "test_id",
421
+ "collection_path": "/radiation",
422
+ "state": state,
423
+ }
424
+ )
425
+
426
+ msg = result.update["messages"][0].content
427
+ assert "Save was successful" in msg
428
+ assert "radiation" in msg
429
+
430
+
431
+ class TestZoteroRead:
432
+ """Integration tests for zotero_read.py."""
433
+
434
+ @pytest.fixture
435
+ def mock_hydra(self):
436
+ """Fixture to mock hydra configuration."""
437
+ with (
438
+ patch("aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.hydra.initialize"),
439
+ patch(
440
+ "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.hydra.compose"
441
+ ) as mock_compose,
442
+ ):
443
+ cfg = MagicMock()
444
+ cfg.tools.zotero_read.user_id = "test_user"
445
+ cfg.tools.zotero_read.library_type = "user"
446
+ cfg.tools.zotero_read.api_key = "test_key"
447
+ cfg.tools.zotero_read.zotero = MagicMock()
448
+ cfg.tools.zotero_read.zotero.max_limit = 50
449
+ cfg.tools.zotero_read.zotero.filter_item_types = [
450
+ "journalArticle",
451
+ "conferencePaper",
452
+ ]
453
+ mock_compose.return_value = cfg
454
+ yield cfg
455
+
456
+ @pytest.fixture
457
+ def mock_zotero(self):
458
+ """Fixture to mock Zotero client."""
459
+ with patch(
460
+ "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.zotero.Zotero"
461
+ ) as mock_zot_class:
462
+ mock_zot = MagicMock()
463
+ mock_zot_class.return_value = mock_zot
464
+ yield mock_zot
465
+
466
+ @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.zotero_path.get_item_collections")
467
+ def test_zotero_read_item_collections_error(
468
+ self, mock_get_collections, mock_hydra, mock_zotero
469
+ ):
470
+ """Test that zotero_read handles errors in get_item_collections."""
471
+
472
+ mock_get_collections.side_effect = Exception("Test error")
473
+
474
+ mock_zotero.items.return_value = [
475
+ {
476
+ "data": {
477
+ "key": "paper1",
478
+ "title": "Test Paper",
479
+ "itemType": "journalArticle",
480
+ }
481
+ }
482
+ ]
483
+ mock_hydra.tools.zotero_read.zotero.max_limit = 50
484
+
485
+ result = zotero_read.run(
486
+ {
487
+ "query": "test",
488
+ "only_articles": True,
489
+ "tool_call_id": "test_id",
490
+ "limit": 2,
491
+ }
492
+ )
493
+
494
+ assert result is not None
495
+ assert isinstance(result.update, dict)
496
+ assert "article_data" in result.update
@@ -0,0 +1,46 @@
1
+ """
2
+ Unit tests for Zotero PDF downloader utilities.
3
+ """
4
+
5
+ import os
6
+ import unittest
7
+ from unittest.mock import MagicMock, patch
8
+
9
+ import requests
10
+
11
+ from aiagents4pharma.talk2scholars.tools.zotero.utils.zotero_pdf_downloader import (
12
+ download_pdfs_in_parallel,
13
+ download_zotero_pdf,
14
+ )
15
+
16
+
17
+ class TestZoteroPDFDownloaderUtils(unittest.TestCase):
18
+ """Tests for zotero_pdf_downloader module."""
19
+
20
+ @patch("requests.Session.get")
21
+ def test_download_zotero_pdf_default_filename(self, mock_get):
22
+ """Test download_zotero_pdf returns default filename when header has no filename."""
23
+ # Mock response without Content-Disposition filename
24
+ mock_response = MagicMock()
25
+ mock_response.raise_for_status = lambda: None
26
+ mock_response.iter_content = lambda chunk_size: [b"fakepdf"]
27
+ mock_response.headers = {}
28
+ mock_get.return_value = mock_response
29
+
30
+ session = requests.Session()
31
+ result = download_zotero_pdf(session, "user123", "apikey", "attach123")
32
+ # Should return a tuple (file_path, filename)
33
+ self.assertIsNotNone(result)
34
+ file_path, filename = result
35
+ # File should exist
36
+ self.assertTrue(os.path.isfile(file_path))
37
+ # Filename should default to 'downloaded.pdf'
38
+ self.assertEqual(filename, "downloaded.pdf")
39
+ # Clean up temp file
40
+ os.remove(file_path)
41
+
42
+ def test_download_pdfs_in_parallel_empty(self):
43
+ """Test that download_pdfs_in_parallel returns empty dict on empty input."""
44
+ session = requests.Session()
45
+ result = download_pdfs_in_parallel(session, "user123", "apikey", {})
46
+ self.assertEqual(result, {})