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,151 @@
1
+ """
2
+ Unit tests for Zotero write tool in zotero_write.py.
3
+ """
4
+
5
+ import unittest
6
+ from types import SimpleNamespace
7
+ from unittest.mock import MagicMock, patch
8
+
9
+ from aiagents4pharma.talk2scholars.tools.zotero.zotero_write import zotero_write
10
+
11
+ dummy_zotero_write_config = SimpleNamespace(user_id="dummy", library_type="user", api_key="dummy")
12
+ dummy_cfg = SimpleNamespace(tools=SimpleNamespace(zotero_write=dummy_zotero_write_config))
13
+
14
+
15
+ class TestZoteroSaveTool(unittest.TestCase):
16
+ """Test class for Zotero save tool"""
17
+
18
+ def setUp(self):
19
+ """Patch Hydra and Zotero client globally"""
20
+ self.hydra_init = patch(
21
+ "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.hydra.initialize"
22
+ ).start()
23
+ self.hydra_compose = patch(
24
+ "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.hydra.compose",
25
+ return_value=dummy_cfg,
26
+ ).start()
27
+ self.zotero_class = patch(
28
+ "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.zotero.Zotero"
29
+ ).start()
30
+
31
+ self.fake_zot = MagicMock()
32
+ self.zotero_class.return_value = self.fake_zot
33
+
34
+ def tearDown(self):
35
+ """Stop all patches"""
36
+ patch.stopall()
37
+
38
+ def make_state(self, papers=None, approved=True, path="/Test Collection"):
39
+ """Create a state dictionary with optional papers and approval info"""
40
+ state = {}
41
+ if approved:
42
+ state["zotero_write_approval_status"] = {
43
+ "approved": True,
44
+ "collection_path": path,
45
+ }
46
+ if papers is not None:
47
+ # When papers is provided as dict, use it directly.
48
+ state["last_displayed_papers"] = papers if isinstance(papers, dict) else "papers"
49
+ if isinstance(papers, dict):
50
+ state["papers"] = papers
51
+ return state
52
+
53
+ @patch(
54
+ "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.fetch_papers_for_save",
55
+ return_value=None,
56
+ )
57
+ def test_no_papers_after_approval(self, mock_fetch):
58
+ """Test when no fetched papers are found after approval"""
59
+ with self.assertRaises(ValueError) as cm:
60
+ zotero_write.run(
61
+ {
62
+ "tool_call_id": "id",
63
+ "collection_path": "/Test Collection",
64
+ "state": self.make_state({}, True),
65
+ }
66
+ )
67
+ self.assertIn("No fetched papers were found to save", str(cm.exception))
68
+ mock_fetch.assert_called_once()
69
+
70
+ @patch(
71
+ "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.fetch_papers_for_save",
72
+ return_value={"p1": {"Title": "X"}},
73
+ )
74
+ @patch(
75
+ "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.find_or_create_collection",
76
+ return_value=None,
77
+ )
78
+ def test_invalid_collection(self, mock_find, mock_fetch):
79
+ """Test when collection path is invalid"""
80
+ self.fake_zot.collections.return_value = [{"key": "k1", "data": {"name": "Existing"}}]
81
+ # Provide a valid papers dict so we don't hit the no-papers error.
82
+ state = self.make_state({"p1": {"Title": "X"}}, True)
83
+ result = zotero_write.run(
84
+ {
85
+ "tool_call_id": "id",
86
+ "collection_path": "/DoesNotExist",
87
+ "state": state,
88
+ }
89
+ )
90
+ # Remove outdated assertions and check for updated message content.
91
+ content = result.update["messages"][0].content
92
+ self.assertIn("does not exist in Zotero", content)
93
+ self.assertIn("/DoesNotExist", content)
94
+ self.assertIn("Existing", content)
95
+ mock_fetch.return_value = {"p1": {"Title": "X"}}
96
+ mock_find.return_value = None
97
+
98
+ @patch(
99
+ "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.fetch_papers_for_save",
100
+ return_value={"p1": {"Title": "X"}},
101
+ )
102
+ @patch(
103
+ "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.find_or_create_collection",
104
+ return_value="colKey",
105
+ )
106
+ def test_save_failure(self, mock_find, mock_fetch):
107
+ """Test when Zotero save operation fails"""
108
+ self.fake_zot.collections.return_value = [
109
+ {"key": "colKey", "data": {"name": "Test Collection"}}
110
+ ]
111
+ self.fake_zot.create_items.side_effect = Exception("Creation error")
112
+ state = self.make_state({"p1": {"Title": "X"}}, True)
113
+ with self.assertRaises(RuntimeError) as cm:
114
+ zotero_write.run(
115
+ {
116
+ "tool_call_id": "id",
117
+ "collection_path": "/Test Collection",
118
+ "state": state,
119
+ }
120
+ )
121
+ self.assertIn("Error saving papers to Zotero", str(cm.exception))
122
+ mock_fetch.return_value = {"p1": {"Title": "X"}}
123
+ mock_find.return_value = "colKey"
124
+
125
+ @patch(
126
+ "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.fetch_papers_for_save",
127
+ return_value={"p1": {"Title": "X"}},
128
+ )
129
+ @patch(
130
+ "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.find_or_create_collection",
131
+ return_value="colKey",
132
+ )
133
+ def test_successful_save(self, mock_find, mock_fetch):
134
+ """Test when Zotero save operation is successful"""
135
+ self.fake_zot.collections.return_value = [
136
+ {"key": "colKey", "data": {"name": "Test Collection"}}
137
+ ]
138
+ self.fake_zot.create_items.return_value = {"successful": {"0": {"key": "item1"}}}
139
+ mock_fetch.return_value = {"p1": {"Title": "X"}}
140
+ mock_find.return_value = "colKey"
141
+
142
+ result = zotero_write.run(
143
+ {
144
+ "tool_call_id": "id",
145
+ "collection_path": "/Test Collection",
146
+ "state": self.make_state({"p1": {"Title": "X"}}, True),
147
+ }
148
+ )
149
+ content = result.update["messages"][0].content
150
+ self.assertIn("Save was successful", content)
151
+ self.assertIn("Test Collection", content)
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """
4
+ Import statements
5
+ """
6
+
7
+ from . import paper_download, pdf, s2, zotero
8
+
9
+ __all__ = ["s2", "pdf", "zotero", "paper_download"]
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ This package provides modules for fetching and downloading academic papers from arXiv,
4
+ biorxiv and medrxiv.
5
+ """
6
+
7
+ # Import modules
8
+ from . import paper_downloader
9
+
10
+ __all__ = [
11
+ "paper_downloader",
12
+ ]
@@ -0,0 +1,442 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Unified paper download tool for LangGraph.
4
+ Supports downloading papers from arXiv, medRxiv, bioRxiv, and PubMed through a single interface.
5
+ """
6
+
7
+ import logging
8
+ import threading
9
+ from typing import Annotated, Any, Literal
10
+
11
+ import hydra
12
+ from hydra.core.global_hydra import GlobalHydra
13
+ from langchain_core.messages import ToolMessage
14
+ from langchain_core.tools import tool
15
+ from langchain_core.tools.base import InjectedToolCallId
16
+ from langgraph.types import Command
17
+ from omegaconf import OmegaConf
18
+ from pydantic import BaseModel, Field
19
+
20
+ from .utils.arxiv_downloader import ArxivDownloader
21
+ from .utils.base_paper_downloader import BasePaperDownloader
22
+ from .utils.biorxiv_downloader import BiorxivDownloader
23
+ from .utils.medrxiv_downloader import MedrxivDownloader
24
+ from .utils.pubmed_downloader import PubmedDownloader
25
+
26
+ # Configure logging
27
+ logging.basicConfig(level=logging.INFO)
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ class UnifiedPaperDownloadInput(BaseModel):
32
+ """Input schema for the unified paper download tool."""
33
+
34
+ service: Literal["arxiv", "medrxiv", "biorxiv", "pubmed"] | None = Field(
35
+ default=None,
36
+ description=(
37
+ "Paper service to download from: 'arxiv', 'medrxiv', 'biorxiv', or 'pubmed'. "
38
+ "If not specified, uses the configured default service."
39
+ ),
40
+ )
41
+ identifiers: list[str] = Field(
42
+ description=(
43
+ "List of paper identifiers. Format depends on service:\n"
44
+ "- arxiv: arXiv IDs (e.g., ['1234.5678', '2301.12345'])\n"
45
+ "- medrxiv: DOIs (e.g., ['10.1101/2020.09.09.20191205'])\n"
46
+ "- biorxiv: DOIs (e.g., ['10.1101/2020.09.09.20191205'])\n"
47
+ "- pubmed: PMIDs (e.g., ['12345678', '87654321'])"
48
+ )
49
+ )
50
+ tool_call_id: Annotated[str, InjectedToolCallId]
51
+
52
+
53
+ class PaperDownloaderFactory:
54
+ """Factory class for creating paper downloader instances."""
55
+
56
+ # Class-level cache for configuration
57
+ _cached_config = None
58
+ _config_lock = None
59
+
60
+ @classmethod
61
+ def clear_cache(cls) -> None:
62
+ """Clear cached configuration."""
63
+ cls._cached_config = None
64
+
65
+ @staticmethod
66
+ def get_default_service() -> Literal["arxiv", "medrxiv", "biorxiv", "pubmed"]:
67
+ """
68
+ Get the default service from configuration.
69
+
70
+ Returns:
71
+ Default service name from config, fallback to 'pubmed'
72
+ """
73
+ config = PaperDownloaderFactory._get_unified_config()
74
+ default_service = getattr(config.tool, "default_service", "pubmed")
75
+ # Ensure the default service is valid and return with proper type
76
+ if default_service == "arxiv":
77
+ return "arxiv"
78
+ if default_service == "medrxiv":
79
+ return "medrxiv"
80
+ if default_service == "biorxiv":
81
+ return "biorxiv"
82
+ if default_service == "pubmed":
83
+ return "pubmed"
84
+ logger.warning(
85
+ "Invalid default service '%s' in config, falling back to 'pubmed'",
86
+ default_service,
87
+ )
88
+ return "pubmed"
89
+
90
+ @staticmethod
91
+ def create(
92
+ service: Literal["arxiv", "medrxiv", "biorxiv", "pubmed"],
93
+ ) -> BasePaperDownloader:
94
+ """
95
+ Create appropriate downloader instance for the specified service.
96
+
97
+ Args:
98
+ service: Service name ('arxiv', 'medrxiv', 'biorxiv', 'pubmed')
99
+
100
+ Returns:
101
+ Configured downloader instance
102
+
103
+ Raises:
104
+ ValueError: If service is not supported
105
+ """
106
+ config = PaperDownloaderFactory._get_unified_config()
107
+ service_config = PaperDownloaderFactory._build_service_config(config, service)
108
+
109
+ if service == "arxiv":
110
+ return ArxivDownloader(service_config)
111
+ if service == "medrxiv":
112
+ return MedrxivDownloader(service_config)
113
+ if service == "biorxiv":
114
+ return BiorxivDownloader(service_config)
115
+ # service == "pubmed"
116
+ return PubmedDownloader(service_config)
117
+
118
+ @staticmethod
119
+ def _get_unified_config() -> Any:
120
+ """
121
+ Load unified paper download configuration using Hydra with caching.
122
+ This avoids the GlobalHydra reinitialization issue by caching the config.
123
+
124
+ Returns:
125
+ Unified configuration object
126
+ """
127
+ # Return cached config if available
128
+ if PaperDownloaderFactory._cached_config is not None:
129
+ return PaperDownloaderFactory._cached_config
130
+
131
+ # Ensure lock exists and get a local reference
132
+ lock = PaperDownloaderFactory._config_lock
133
+ if lock is None:
134
+ lock = threading.Lock()
135
+ PaperDownloaderFactory._config_lock = lock
136
+
137
+ # Thread-safe config loading with guaranteed non-None lock
138
+ with lock:
139
+ # Double-check pattern - another thread might have loaded it
140
+ if PaperDownloaderFactory._cached_config is not None:
141
+ return PaperDownloaderFactory._cached_config
142
+
143
+ try:
144
+ # Clear if already initialized
145
+ if GlobalHydra().is_initialized():
146
+ logger.info("GlobalHydra already initialized, clearing for config load")
147
+ GlobalHydra.instance().clear()
148
+
149
+ # Load configuration
150
+ with hydra.initialize(version_base=None, config_path="../../configs"):
151
+ cfg = hydra.compose(
152
+ config_name="config", overrides=["tools/paper_download=default"]
153
+ )
154
+
155
+ # Cache the configuration
156
+ PaperDownloaderFactory._cached_config = cfg.tools.paper_download
157
+ logger.info("Successfully loaded and cached paper download configuration")
158
+
159
+ return PaperDownloaderFactory._cached_config
160
+
161
+ except Exception as e:
162
+ logger.error("Failed to load unified paper download configuration: %s", e)
163
+ raise RuntimeError(f"Configuration loading failed: {e}") from e
164
+
165
+ @staticmethod
166
+ def _build_service_config(unified_config: Any, service: str) -> Any:
167
+ """
168
+ Build service-specific configuration by merging common and service settings.
169
+ Handles Hydra's OmegaConf objects properly.
170
+
171
+ Args:
172
+ unified_config: The unified configuration object
173
+ service: Service name
174
+
175
+ Returns:
176
+ Service-specific configuration object
177
+ """
178
+ if not hasattr(unified_config, "services") or service not in unified_config.services:
179
+ raise ValueError(f"Service '{service}' not found in configuration")
180
+
181
+ # Create a simple config object that combines common and service-specific settings
182
+ class ServiceConfig:
183
+ """Service-specific configuration holder."""
184
+
185
+ def get_config_dict(self):
186
+ """Return configuration as dictionary."""
187
+ return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
188
+
189
+ def has_attribute(self, name: str) -> bool:
190
+ """Check if configuration has a specific attribute."""
191
+ return hasattr(self, name)
192
+
193
+ config_obj = ServiceConfig()
194
+
195
+ # Handle common config (using helper method to reduce branches)
196
+ PaperDownloaderFactory._apply_config(config_obj, unified_config.common, "common")
197
+
198
+ # Handle service-specific config (using helper method to reduce branches)
199
+ PaperDownloaderFactory._apply_config(config_obj, unified_config.services[service], service)
200
+
201
+ return config_obj
202
+
203
+ @staticmethod
204
+ def _apply_config(config_obj: Any, source_config: Any, config_type: str) -> None:
205
+ """
206
+ Apply configuration from source to target object using multiple fallback methods.
207
+ This preserves all the original logic but reduces branches in the main method.
208
+
209
+ Args:
210
+ config_obj: Target configuration object
211
+ source_config: Source configuration to extract from
212
+ config_type: Type description for logging
213
+ """
214
+ try:
215
+ PaperDownloaderFactory._try_config_extraction(config_obj, source_config)
216
+ except (AttributeError, TypeError, KeyError) as e:
217
+ logger.warning("Failed to process %s config: %s", config_type, e)
218
+
219
+ @staticmethod
220
+ def _try_config_extraction(config_obj: Any, source_config: Any) -> None:
221
+ """Try different methods to extract configuration data."""
222
+ # Method 1: Try OmegaConf conversion
223
+ if hasattr(source_config, "_content"):
224
+ PaperDownloaderFactory._extract_from_omegaconf(config_obj, source_config)
225
+ return
226
+
227
+ # Method 2: Try direct attribute access
228
+ if hasattr(source_config, "__dict__"):
229
+ PaperDownloaderFactory._extract_from_dict(config_obj, source_config.__dict__)
230
+ return
231
+
232
+ # Method 3: Try items() method
233
+ if hasattr(source_config, "items"):
234
+ PaperDownloaderFactory._extract_from_items(config_obj, source_config)
235
+ return
236
+
237
+ # Method 4: Try dir() approach as fallback
238
+ PaperDownloaderFactory._extract_from_dir(config_obj, source_config)
239
+
240
+ @staticmethod
241
+ def _extract_from_omegaconf(config_obj: Any, source_config: Any) -> None:
242
+ """Extract configuration from OmegaConf object."""
243
+ config_dict = OmegaConf.to_container(source_config, resolve=True)
244
+ if isinstance(config_dict, dict):
245
+ for key, value in config_dict.items():
246
+ if isinstance(key, str): # Type guard for key
247
+ setattr(config_obj, key, value)
248
+
249
+ @staticmethod
250
+ def _extract_from_dict(config_obj: Any, config_dict: dict) -> None:
251
+ """Extract configuration from dictionary."""
252
+ for key, value in config_dict.items():
253
+ if not key.startswith("_"):
254
+ setattr(config_obj, key, value)
255
+
256
+ @staticmethod
257
+ def _extract_from_items(config_obj: Any, source_config: Any) -> None:
258
+ """Extract configuration using items() method."""
259
+ for key, value in source_config.items():
260
+ if isinstance(key, str): # Type guard for key
261
+ setattr(config_obj, key, value)
262
+
263
+ @staticmethod
264
+ def _extract_from_dir(config_obj: Any, source_config: Any) -> None:
265
+ """Extract configuration using dir() approach as fallback."""
266
+ for key in dir(source_config):
267
+ if not key.startswith("_"):
268
+ value = getattr(source_config, key)
269
+ if not callable(value):
270
+ setattr(config_obj, key, value)
271
+
272
+
273
+ @tool(
274
+ args_schema=UnifiedPaperDownloadInput,
275
+ parse_docstring=True,
276
+ )
277
+ def download_papers(
278
+ service: Literal["arxiv", "medrxiv", "biorxiv", "pubmed"] | None,
279
+ identifiers: list[str],
280
+ tool_call_id: Annotated[str, InjectedToolCallId],
281
+ ) -> Command[Any]:
282
+ """
283
+ Universal paper download tool supporting multiple academic paper services.
284
+
285
+ Downloads paper metadata and PDFs from arXiv, medRxiv, bioRxiv, or PubMed and stores them
286
+ in temporary files for further processing. The downloaded PDFs can be accessed
287
+ using the temp_file_path in the returned metadata.
288
+
289
+ Args:
290
+ service: Paper service to download from (optional, uses configured default if not specified)
291
+ - 'arxiv': For arXiv preprints (requires arXiv IDs)
292
+ - 'medrxiv': For medRxiv preprints (requires DOIs)
293
+ - 'biorxiv': For bioRxiv preprints (requires DOIs)
294
+ - 'pubmed': For PubMed papers (requires PMIDs)
295
+ identifiers: List of paper identifiers in the format expected by the service
296
+
297
+ Returns:
298
+ Command with article_data containing paper metadata and local file paths
299
+
300
+ Examples:
301
+ # Download from arXiv
302
+ download_papers("arxiv", ["1234.5678", "2301.12345"])
303
+
304
+ # Download from medRxiv
305
+ download_papers("medrxiv", ["10.1101/2020.09.09.20191205"])
306
+
307
+ # Download from bioRxiv
308
+ download_papers("biorxiv", ["10.1101/2020.09.09.20191205"])
309
+
310
+ # Download from PubMed
311
+ download_papers("pubmed", ["12345678", "87654321"])
312
+
313
+ # Use default service (configured in default.yaml)
314
+ download_papers(None, ["12345678", "87654321"])
315
+ """
316
+ return _download_papers_impl(service, identifiers, tool_call_id)
317
+
318
+
319
+ # Convenience functions for backward compatibility (optional)
320
+ # These functions explicitly specify the service, bypassing the default service config
321
+ def download_arxiv_papers(
322
+ arxiv_ids: list[str], tool_call_id: Annotated[str, InjectedToolCallId]
323
+ ) -> Command[Any]:
324
+ """Convenience function for downloading arXiv papers (explicitly uses arXiv service)."""
325
+ return _download_papers_impl("arxiv", arxiv_ids, tool_call_id)
326
+
327
+
328
+ def download_medrxiv_papers(
329
+ dois: list[str], tool_call_id: Annotated[str, InjectedToolCallId]
330
+ ) -> Command[Any]:
331
+ """Convenience function for downloading medRxiv papers (explicitly uses medRxiv service)."""
332
+ return _download_papers_impl("medrxiv", dois, tool_call_id)
333
+
334
+
335
+ def download_biorxiv_papers(
336
+ dois: list[str], tool_call_id: Annotated[str, InjectedToolCallId]
337
+ ) -> Command[Any]:
338
+ """Convenience function for downloading bioRxiv papers (explicitly uses bioRxiv service)."""
339
+ return _download_papers_impl("biorxiv", dois, tool_call_id)
340
+
341
+
342
+ def download_pubmed_papers(
343
+ pmids: list[str], tool_call_id: Annotated[str, InjectedToolCallId]
344
+ ) -> Command[Any]:
345
+ """Convenience function for downloading PubMed papers (explicitly uses PubMed service)."""
346
+ return _download_papers_impl("pubmed", pmids, tool_call_id)
347
+
348
+
349
+ def _download_papers_impl(
350
+ service: Literal["arxiv", "medrxiv", "biorxiv", "pubmed"] | None,
351
+ identifiers: list[str],
352
+ tool_call_id: str,
353
+ ) -> Command[Any]:
354
+ """
355
+ Internal implementation function that contains the actual download logic.
356
+ This is called by both the decorated tool and the convenience functions.
357
+ """
358
+ # Resolve default service if not specified
359
+ if service is None:
360
+ service = PaperDownloaderFactory.get_default_service()
361
+ logger.info("No service specified, using configured default: %s", service)
362
+ logger.info(
363
+ "Starting unified paper download for service '%s' with %d identifiers: %s",
364
+ service,
365
+ len(identifiers),
366
+ identifiers,
367
+ )
368
+
369
+ try:
370
+ # Step 1: Create appropriate downloader using factory
371
+ downloader = PaperDownloaderFactory.create(service)
372
+ logger.info("Created %s downloader successfully", downloader.get_service_name())
373
+
374
+ # Step 2: Process all identifiers
375
+ article_data = downloader.process_identifiers(identifiers)
376
+
377
+ # Step 3: Build summary for user
378
+ content = downloader.build_summary(article_data)
379
+
380
+ # Step 4: Log results summary
381
+ total_papers = len(article_data)
382
+ successful_downloads = sum(
383
+ 1
384
+ for paper in article_data.values()
385
+ if paper.get("access_type") == "open_access_downloaded"
386
+ )
387
+ logger.info(
388
+ "Download complete for %s: %d papers processed, %d PDFs downloaded",
389
+ service,
390
+ total_papers,
391
+ successful_downloads,
392
+ )
393
+
394
+ # Step 5: Return command with results
395
+ return Command(
396
+ update={
397
+ "article_data": article_data,
398
+ "messages": [
399
+ ToolMessage(
400
+ content=content,
401
+ tool_call_id=tool_call_id,
402
+ artifact=article_data,
403
+ )
404
+ ],
405
+ }
406
+ )
407
+
408
+ except ValueError as e:
409
+ # Handle service/configuration errors
410
+ error_msg = f"Service error for '{service}': {str(e)}"
411
+ logger.error(error_msg)
412
+
413
+ return Command(
414
+ update={
415
+ "article_data": {},
416
+ "messages": [
417
+ ToolMessage(
418
+ content=f"Error: {error_msg}",
419
+ tool_call_id=tool_call_id,
420
+ artifact={},
421
+ )
422
+ ],
423
+ }
424
+ )
425
+
426
+ except Exception as e: # pylint: disable=broad-exception-caught
427
+ # Handle unexpected errors
428
+ error_msg = f"Unexpected error during paper download: {str(e)}"
429
+ logger.error(error_msg, exc_info=True)
430
+
431
+ return Command(
432
+ update={
433
+ "article_data": {},
434
+ "messages": [
435
+ ToolMessage(
436
+ content=f"Error: {error_msg}",
437
+ tool_call_id=tool_call_id,
438
+ artifact={},
439
+ )
440
+ ],
441
+ }
442
+ )
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ This package provides modules for fetching and downloading academic papers from arXiv,
4
+ biorxiv and medrxiv.
5
+ """
6
+
7
+ # Import modules
8
+ from . import (
9
+ arxiv_downloader,
10
+ base_paper_downloader,
11
+ biorxiv_downloader,
12
+ medrxiv_downloader,
13
+ pubmed_downloader,
14
+ )
15
+
16
+ __all__ = [
17
+ "arxiv_downloader",
18
+ "base_paper_downloader",
19
+ "biorxiv_downloader",
20
+ "medrxiv_downloader",
21
+ "pubmed_downloader",
22
+ ]