aiagents4pharma 1.43.0__py3-none-any.whl → 1.45.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. aiagents4pharma/__init__.py +2 -2
  2. aiagents4pharma/talk2aiagents4pharma/.dockerignore +13 -0
  3. aiagents4pharma/talk2aiagents4pharma/Dockerfile +105 -0
  4. aiagents4pharma/talk2aiagents4pharma/README.md +1 -0
  5. aiagents4pharma/talk2aiagents4pharma/__init__.py +4 -5
  6. aiagents4pharma/talk2aiagents4pharma/agents/__init__.py +3 -2
  7. aiagents4pharma/talk2aiagents4pharma/agents/main_agent.py +24 -23
  8. aiagents4pharma/talk2aiagents4pharma/configs/__init__.py +2 -2
  9. aiagents4pharma/talk2aiagents4pharma/configs/agents/__init__.py +2 -2
  10. aiagents4pharma/talk2aiagents4pharma/configs/agents/main_agent/default.yaml +2 -2
  11. aiagents4pharma/talk2aiagents4pharma/configs/config.yaml +1 -1
  12. aiagents4pharma/talk2aiagents4pharma/docker-compose/cpu/.env.example +23 -0
  13. aiagents4pharma/talk2aiagents4pharma/docker-compose/cpu/docker-compose.yml +93 -0
  14. aiagents4pharma/talk2aiagents4pharma/docker-compose/gpu/.env.example +23 -0
  15. aiagents4pharma/talk2aiagents4pharma/docker-compose/gpu/docker-compose.yml +108 -0
  16. aiagents4pharma/talk2aiagents4pharma/install.md +127 -0
  17. aiagents4pharma/talk2aiagents4pharma/states/__init__.py +3 -2
  18. aiagents4pharma/talk2aiagents4pharma/states/state_talk2aiagents4pharma.py +5 -3
  19. aiagents4pharma/talk2aiagents4pharma/tests/__init__.py +2 -2
  20. aiagents4pharma/talk2aiagents4pharma/tests/test_main_agent.py +72 -50
  21. aiagents4pharma/talk2biomodels/.dockerignore +13 -0
  22. aiagents4pharma/talk2biomodels/Dockerfile +104 -0
  23. aiagents4pharma/talk2biomodels/README.md +1 -0
  24. aiagents4pharma/talk2biomodels/__init__.py +4 -8
  25. aiagents4pharma/talk2biomodels/agents/__init__.py +3 -2
  26. aiagents4pharma/talk2biomodels/agents/t2b_agent.py +47 -42
  27. aiagents4pharma/talk2biomodels/api/__init__.py +4 -5
  28. aiagents4pharma/talk2biomodels/api/kegg.py +14 -10
  29. aiagents4pharma/talk2biomodels/api/ols.py +13 -10
  30. aiagents4pharma/talk2biomodels/api/uniprot.py +7 -6
  31. aiagents4pharma/talk2biomodels/configs/__init__.py +3 -4
  32. aiagents4pharma/talk2biomodels/configs/agents/__init__.py +2 -2
  33. aiagents4pharma/talk2biomodels/configs/agents/t2b_agent/__init__.py +2 -2
  34. aiagents4pharma/talk2biomodels/configs/agents/t2b_agent/default.yaml +1 -1
  35. aiagents4pharma/talk2biomodels/configs/config.yaml +1 -1
  36. aiagents4pharma/talk2biomodels/configs/tools/__init__.py +4 -5
  37. aiagents4pharma/talk2biomodels/configs/tools/ask_question/__init__.py +2 -2
  38. aiagents4pharma/talk2biomodels/configs/tools/ask_question/default.yaml +1 -2
  39. aiagents4pharma/talk2biomodels/configs/tools/custom_plotter/__init__.py +2 -2
  40. aiagents4pharma/talk2biomodels/configs/tools/custom_plotter/default.yaml +1 -1
  41. aiagents4pharma/talk2biomodels/configs/tools/get_annotation/__init__.py +2 -2
  42. aiagents4pharma/talk2biomodels/configs/tools/get_annotation/default.yaml +1 -1
  43. aiagents4pharma/talk2biomodels/install.md +63 -0
  44. aiagents4pharma/talk2biomodels/models/__init__.py +4 -4
  45. aiagents4pharma/talk2biomodels/models/basico_model.py +36 -28
  46. aiagents4pharma/talk2biomodels/models/sys_bio_model.py +13 -10
  47. aiagents4pharma/talk2biomodels/states/__init__.py +3 -2
  48. aiagents4pharma/talk2biomodels/states/state_talk2biomodels.py +12 -8
  49. aiagents4pharma/talk2biomodels/tests/BIOMD0000000449_url.xml +1585 -0
  50. aiagents4pharma/talk2biomodels/tests/__init__.py +2 -2
  51. aiagents4pharma/talk2biomodels/tests/article_on_model_537.pdf +0 -0
  52. aiagents4pharma/talk2biomodels/tests/test_api.py +18 -14
  53. aiagents4pharma/talk2biomodels/tests/test_ask_question.py +8 -9
  54. aiagents4pharma/talk2biomodels/tests/test_basico_model.py +15 -9
  55. aiagents4pharma/talk2biomodels/tests/test_get_annotation.py +54 -55
  56. aiagents4pharma/talk2biomodels/tests/test_getmodelinfo.py +28 -27
  57. aiagents4pharma/talk2biomodels/tests/test_integration.py +21 -33
  58. aiagents4pharma/talk2biomodels/tests/test_load_biomodel.py +14 -11
  59. aiagents4pharma/talk2biomodels/tests/test_param_scan.py +21 -20
  60. aiagents4pharma/talk2biomodels/tests/test_query_article.py +129 -29
  61. aiagents4pharma/talk2biomodels/tests/test_search_models.py +9 -13
  62. aiagents4pharma/talk2biomodels/tests/test_simulate_model.py +16 -15
  63. aiagents4pharma/talk2biomodels/tests/test_steady_state.py +12 -22
  64. aiagents4pharma/talk2biomodels/tests/test_sys_bio_model.py +33 -29
  65. aiagents4pharma/talk2biomodels/tools/__init__.py +15 -12
  66. aiagents4pharma/talk2biomodels/tools/ask_question.py +42 -32
  67. aiagents4pharma/talk2biomodels/tools/custom_plotter.py +51 -43
  68. aiagents4pharma/talk2biomodels/tools/get_annotation.py +99 -75
  69. aiagents4pharma/talk2biomodels/tools/get_modelinfo.py +57 -51
  70. aiagents4pharma/talk2biomodels/tools/load_arguments.py +52 -32
  71. aiagents4pharma/talk2biomodels/tools/load_biomodel.py +8 -2
  72. aiagents4pharma/talk2biomodels/tools/parameter_scan.py +107 -90
  73. aiagents4pharma/talk2biomodels/tools/query_article.py +14 -13
  74. aiagents4pharma/talk2biomodels/tools/search_models.py +37 -26
  75. aiagents4pharma/talk2biomodels/tools/simulate_model.py +47 -37
  76. aiagents4pharma/talk2biomodels/tools/steady_state.py +76 -58
  77. aiagents4pharma/talk2biomodels/tools/utils.py +4 -3
  78. aiagents4pharma/talk2cells/README.md +1 -0
  79. aiagents4pharma/talk2cells/__init__.py +4 -5
  80. aiagents4pharma/talk2cells/agents/__init__.py +3 -2
  81. aiagents4pharma/talk2cells/agents/scp_agent.py +21 -19
  82. aiagents4pharma/talk2cells/states/__init__.py +3 -2
  83. aiagents4pharma/talk2cells/states/state_talk2cells.py +4 -2
  84. aiagents4pharma/talk2cells/tests/scp_agent/test_scp_agent.py +8 -9
  85. aiagents4pharma/talk2cells/tools/__init__.py +3 -2
  86. aiagents4pharma/talk2cells/tools/scp_agent/__init__.py +4 -4
  87. aiagents4pharma/talk2cells/tools/scp_agent/display_studies.py +5 -3
  88. aiagents4pharma/talk2cells/tools/scp_agent/search_studies.py +21 -22
  89. aiagents4pharma/talk2knowledgegraphs/.dockerignore +13 -0
  90. aiagents4pharma/talk2knowledgegraphs/Dockerfile +103 -0
  91. aiagents4pharma/talk2knowledgegraphs/README.md +1 -0
  92. aiagents4pharma/talk2knowledgegraphs/__init__.py +4 -7
  93. aiagents4pharma/talk2knowledgegraphs/agents/__init__.py +3 -2
  94. aiagents4pharma/talk2knowledgegraphs/agents/t2kg_agent.py +40 -30
  95. aiagents4pharma/talk2knowledgegraphs/configs/__init__.py +3 -6
  96. aiagents4pharma/talk2knowledgegraphs/configs/agents/t2kg_agent/__init__.py +2 -2
  97. aiagents4pharma/talk2knowledgegraphs/configs/agents/t2kg_agent/default.yaml +8 -8
  98. aiagents4pharma/talk2knowledgegraphs/configs/app/__init__.py +3 -2
  99. aiagents4pharma/talk2knowledgegraphs/configs/app/frontend/__init__.py +2 -2
  100. aiagents4pharma/talk2knowledgegraphs/configs/app/frontend/default.yaml +1 -1
  101. aiagents4pharma/talk2knowledgegraphs/configs/config.yaml +1 -1
  102. aiagents4pharma/talk2knowledgegraphs/configs/tools/__init__.py +4 -5
  103. aiagents4pharma/talk2knowledgegraphs/configs/tools/graphrag_reasoning/__init__.py +2 -2
  104. aiagents4pharma/talk2knowledgegraphs/configs/tools/graphrag_reasoning/default.yaml +1 -1
  105. aiagents4pharma/talk2knowledgegraphs/configs/tools/multimodal_subgraph_extraction/default.yaml +17 -2
  106. aiagents4pharma/talk2knowledgegraphs/configs/tools/subgraph_extraction/__init__.py +2 -2
  107. aiagents4pharma/talk2knowledgegraphs/configs/tools/subgraph_extraction/default.yaml +1 -1
  108. aiagents4pharma/talk2knowledgegraphs/configs/tools/subgraph_summarization/__init__.py +2 -2
  109. aiagents4pharma/talk2knowledgegraphs/configs/tools/subgraph_summarization/default.yaml +1 -1
  110. aiagents4pharma/talk2knowledgegraphs/configs/utils/enrichments/ols_terms/default.yaml +1 -1
  111. aiagents4pharma/talk2knowledgegraphs/configs/utils/enrichments/reactome_pathways/default.yaml +1 -1
  112. aiagents4pharma/talk2knowledgegraphs/configs/utils/enrichments/uniprot_proteins/default.yaml +1 -1
  113. aiagents4pharma/talk2knowledgegraphs/configs/utils/pubchem_utils/default.yaml +1 -1
  114. aiagents4pharma/talk2knowledgegraphs/datasets/__init__.py +4 -6
  115. aiagents4pharma/talk2knowledgegraphs/datasets/biobridge_primekg.py +115 -67
  116. aiagents4pharma/talk2knowledgegraphs/datasets/dataset.py +2 -0
  117. aiagents4pharma/talk2knowledgegraphs/datasets/primekg.py +35 -24
  118. aiagents4pharma/talk2knowledgegraphs/datasets/starkqa_primekg.py +29 -21
  119. aiagents4pharma/talk2knowledgegraphs/docker-compose/cpu/.env.example +23 -0
  120. aiagents4pharma/talk2knowledgegraphs/docker-compose/cpu/docker-compose.yml +93 -0
  121. aiagents4pharma/talk2knowledgegraphs/docker-compose/gpu/.env.example +23 -0
  122. aiagents4pharma/talk2knowledgegraphs/docker-compose/gpu/docker-compose.yml +108 -0
  123. aiagents4pharma/talk2knowledgegraphs/entrypoint.sh +190 -0
  124. aiagents4pharma/talk2knowledgegraphs/install.md +140 -0
  125. aiagents4pharma/talk2knowledgegraphs/milvus_data_dump.py +31 -65
  126. aiagents4pharma/talk2knowledgegraphs/states/__init__.py +3 -2
  127. aiagents4pharma/talk2knowledgegraphs/states/state_talk2knowledgegraphs.py +1 -0
  128. aiagents4pharma/talk2knowledgegraphs/tests/test_agents_t2kg_agent.py +65 -40
  129. aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_biobridge_primekg.py +54 -48
  130. aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_dataset.py +4 -0
  131. aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_primekg.py +17 -4
  132. aiagents4pharma/talk2knowledgegraphs/tests/test_datasets_starkqa_primekg.py +33 -24
  133. aiagents4pharma/talk2knowledgegraphs/tests/test_tools_graphrag_reasoning.py +116 -69
  134. aiagents4pharma/talk2knowledgegraphs/tests/test_tools_milvus_multimodal_subgraph_extraction.py +736 -413
  135. aiagents4pharma/talk2knowledgegraphs/tests/test_tools_multimodal_subgraph_extraction.py +22 -15
  136. aiagents4pharma/talk2knowledgegraphs/tests/test_tools_subgraph_extraction.py +19 -12
  137. aiagents4pharma/talk2knowledgegraphs/tests/test_tools_subgraph_summarization.py +95 -48
  138. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_embeddings.py +4 -0
  139. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_huggingface.py +5 -0
  140. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_nim_molmim.py +13 -18
  141. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_embeddings_ollama.py +10 -3
  142. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_enrichments.py +4 -3
  143. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_ollama.py +3 -2
  144. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_ols.py +1 -0
  145. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_pubchem.py +9 -4
  146. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_reactome.py +6 -6
  147. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_enrichments_uniprot.py +4 -0
  148. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_extractions_milvus_multimodal_pcst.py +442 -42
  149. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_kg_utils.py +3 -4
  150. aiagents4pharma/talk2knowledgegraphs/tests/test_utils_pubchem_utils.py +10 -6
  151. aiagents4pharma/talk2knowledgegraphs/tools/__init__.py +10 -7
  152. aiagents4pharma/talk2knowledgegraphs/tools/graphrag_reasoning.py +15 -20
  153. aiagents4pharma/talk2knowledgegraphs/tools/milvus_multimodal_subgraph_extraction.py +245 -205
  154. aiagents4pharma/talk2knowledgegraphs/tools/multimodal_subgraph_extraction.py +92 -90
  155. aiagents4pharma/talk2knowledgegraphs/tools/subgraph_extraction.py +25 -37
  156. aiagents4pharma/talk2knowledgegraphs/tools/subgraph_summarization.py +10 -13
  157. aiagents4pharma/talk2knowledgegraphs/utils/__init__.py +4 -7
  158. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/__init__.py +4 -7
  159. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/embeddings.py +4 -0
  160. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/huggingface.py +11 -14
  161. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/nim_molmim.py +7 -7
  162. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/ollama.py +12 -6
  163. aiagents4pharma/talk2knowledgegraphs/utils/embeddings/sentence_transformer.py +8 -6
  164. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/__init__.py +9 -6
  165. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/enrichments.py +1 -0
  166. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/ollama.py +15 -9
  167. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/ols_terms.py +23 -20
  168. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/pubchem_strings.py +12 -10
  169. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/reactome_pathways.py +16 -10
  170. aiagents4pharma/talk2knowledgegraphs/utils/enrichments/uniprot_proteins.py +26 -18
  171. aiagents4pharma/talk2knowledgegraphs/utils/extractions/__init__.py +4 -5
  172. aiagents4pharma/talk2knowledgegraphs/utils/extractions/milvus_multimodal_pcst.py +218 -81
  173. aiagents4pharma/talk2knowledgegraphs/utils/extractions/multimodal_pcst.py +53 -47
  174. aiagents4pharma/talk2knowledgegraphs/utils/extractions/pcst.py +18 -14
  175. aiagents4pharma/talk2knowledgegraphs/utils/kg_utils.py +22 -23
  176. aiagents4pharma/talk2knowledgegraphs/utils/pubchem_utils.py +11 -10
  177. aiagents4pharma/talk2scholars/.dockerignore +13 -0
  178. aiagents4pharma/talk2scholars/Dockerfile +104 -0
  179. aiagents4pharma/talk2scholars/README.md +1 -0
  180. aiagents4pharma/talk2scholars/agents/__init__.py +1 -5
  181. aiagents4pharma/talk2scholars/agents/main_agent.py +6 -4
  182. aiagents4pharma/talk2scholars/agents/paper_download_agent.py +5 -4
  183. aiagents4pharma/talk2scholars/agents/pdf_agent.py +4 -2
  184. aiagents4pharma/talk2scholars/agents/s2_agent.py +2 -2
  185. aiagents4pharma/talk2scholars/agents/zotero_agent.py +10 -11
  186. aiagents4pharma/talk2scholars/configs/__init__.py +1 -3
  187. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/__init__.py +1 -4
  188. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/main_agent/default.yaml +1 -1
  189. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/pdf_agent/default.yaml +1 -1
  190. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/s2_agent/default.yaml +8 -8
  191. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/zotero_agent/default.yaml +7 -7
  192. aiagents4pharma/talk2scholars/configs/tools/__init__.py +8 -6
  193. aiagents4pharma/talk2scholars/docker-compose/cpu/.env.example +21 -0
  194. aiagents4pharma/talk2scholars/docker-compose/cpu/docker-compose.yml +90 -0
  195. aiagents4pharma/talk2scholars/docker-compose/gpu/.env.example +21 -0
  196. aiagents4pharma/talk2scholars/docker-compose/gpu/docker-compose.yml +105 -0
  197. aiagents4pharma/talk2scholars/install.md +122 -0
  198. aiagents4pharma/talk2scholars/state/state_talk2scholars.py +8 -8
  199. aiagents4pharma/talk2scholars/tests/{test_main_agent.py → test_agents_main_agent.py} +41 -23
  200. aiagents4pharma/talk2scholars/tests/{test_paper_download_agent.py → test_agents_paper_agents_download_agent.py} +10 -16
  201. aiagents4pharma/talk2scholars/tests/{test_pdf_agent.py → test_agents_pdf_agent.py} +6 -10
  202. aiagents4pharma/talk2scholars/tests/{test_s2_agent.py → test_agents_s2_agent.py} +8 -16
  203. aiagents4pharma/talk2scholars/tests/{test_zotero_agent.py → test_agents_zotero_agent.py} +5 -7
  204. aiagents4pharma/talk2scholars/tests/{test_s2_display_dataframe.py → test_s2_tools_display_dataframe.py} +6 -7
  205. aiagents4pharma/talk2scholars/tests/{test_s2_query_dataframe.py → test_s2_tools_query_dataframe.py} +5 -15
  206. aiagents4pharma/talk2scholars/tests/{test_paper_downloader.py → test_tools_paper_downloader.py} +25 -63
  207. aiagents4pharma/talk2scholars/tests/{test_question_and_answer_tool.py → test_tools_question_and_answer_tool.py} +2 -6
  208. aiagents4pharma/talk2scholars/tests/{test_s2_multi.py → test_tools_s2_multi.py} +5 -5
  209. aiagents4pharma/talk2scholars/tests/{test_s2_retrieve.py → test_tools_s2_retrieve.py} +2 -1
  210. aiagents4pharma/talk2scholars/tests/{test_s2_search.py → test_tools_s2_search.py} +5 -5
  211. aiagents4pharma/talk2scholars/tests/{test_s2_single.py → test_tools_s2_single.py} +5 -5
  212. aiagents4pharma/talk2scholars/tests/{test_arxiv_downloader.py → test_utils_arxiv_downloader.py} +16 -25
  213. aiagents4pharma/talk2scholars/tests/{test_base_paper_downloader.py → test_utils_base_paper_downloader.py} +25 -47
  214. aiagents4pharma/talk2scholars/tests/{test_biorxiv_downloader.py → test_utils_biorxiv_downloader.py} +14 -42
  215. aiagents4pharma/talk2scholars/tests/{test_medrxiv_downloader.py → test_utils_medrxiv_downloader.py} +15 -49
  216. aiagents4pharma/talk2scholars/tests/{test_nvidia_nim_reranker.py → test_utils_nvidia_nim_reranker.py} +6 -16
  217. aiagents4pharma/talk2scholars/tests/{test_pdf_answer_formatter.py → test_utils_pdf_answer_formatter.py} +1 -0
  218. aiagents4pharma/talk2scholars/tests/{test_pdf_batch_processor.py → test_utils_pdf_batch_processor.py} +6 -15
  219. aiagents4pharma/talk2scholars/tests/{test_pdf_collection_manager.py → test_utils_pdf_collection_manager.py} +34 -11
  220. aiagents4pharma/talk2scholars/tests/{test_pdf_document_processor.py → test_utils_pdf_document_processor.py} +2 -3
  221. aiagents4pharma/talk2scholars/tests/{test_pdf_generate_answer.py → test_utils_pdf_generate_answer.py} +3 -6
  222. aiagents4pharma/talk2scholars/tests/{test_pdf_gpu_detection.py → test_utils_pdf_gpu_detection.py} +5 -16
  223. aiagents4pharma/talk2scholars/tests/{test_pdf_rag_pipeline.py → test_utils_pdf_rag_pipeline.py} +7 -17
  224. aiagents4pharma/talk2scholars/tests/{test_pdf_retrieve_chunks.py → test_utils_pdf_retrieve_chunks.py} +4 -11
  225. aiagents4pharma/talk2scholars/tests/{test_pdf_singleton_manager.py → test_utils_pdf_singleton_manager.py} +26 -23
  226. aiagents4pharma/talk2scholars/tests/{test_pdf_vector_normalization.py → test_utils_pdf_vector_normalization.py} +1 -1
  227. aiagents4pharma/talk2scholars/tests/{test_pdf_vector_store.py → test_utils_pdf_vector_store.py} +27 -55
  228. aiagents4pharma/talk2scholars/tests/{test_pubmed_downloader.py → test_utils_pubmed_downloader.py} +31 -91
  229. aiagents4pharma/talk2scholars/tests/{test_read_helper_utils.py → test_utils_read_helper_utils.py} +2 -6
  230. aiagents4pharma/talk2scholars/tests/{test_s2_utils_ext_ids.py → test_utils_s2_utils_ext_ids.py} +5 -15
  231. aiagents4pharma/talk2scholars/tests/{test_zotero_human_in_the_loop.py → test_utils_zotero_human_in_the_loop.py} +6 -13
  232. aiagents4pharma/talk2scholars/tests/{test_zotero_path.py → test_utils_zotero_path.py} +53 -45
  233. aiagents4pharma/talk2scholars/tests/{test_zotero_read.py → test_utils_zotero_read.py} +30 -91
  234. aiagents4pharma/talk2scholars/tests/{test_zotero_write.py → test_utils_zotero_write.py} +6 -16
  235. aiagents4pharma/talk2scholars/tools/__init__.py +1 -4
  236. aiagents4pharma/talk2scholars/tools/paper_download/paper_downloader.py +20 -35
  237. aiagents4pharma/talk2scholars/tools/paper_download/utils/__init__.py +7 -5
  238. aiagents4pharma/talk2scholars/tools/paper_download/utils/arxiv_downloader.py +9 -11
  239. aiagents4pharma/talk2scholars/tools/paper_download/utils/base_paper_downloader.py +14 -21
  240. aiagents4pharma/talk2scholars/tools/paper_download/utils/biorxiv_downloader.py +14 -22
  241. aiagents4pharma/talk2scholars/tools/paper_download/utils/medrxiv_downloader.py +11 -13
  242. aiagents4pharma/talk2scholars/tools/paper_download/utils/pubmed_downloader.py +14 -28
  243. aiagents4pharma/talk2scholars/tools/pdf/question_and_answer.py +4 -8
  244. aiagents4pharma/talk2scholars/tools/pdf/utils/__init__.py +16 -14
  245. aiagents4pharma/talk2scholars/tools/pdf/utils/answer_formatter.py +4 -4
  246. aiagents4pharma/talk2scholars/tools/pdf/utils/batch_processor.py +15 -17
  247. aiagents4pharma/talk2scholars/tools/pdf/utils/collection_manager.py +2 -2
  248. aiagents4pharma/talk2scholars/tools/pdf/utils/document_processor.py +5 -5
  249. aiagents4pharma/talk2scholars/tools/pdf/utils/generate_answer.py +4 -4
  250. aiagents4pharma/talk2scholars/tools/pdf/utils/get_vectorstore.py +2 -6
  251. aiagents4pharma/talk2scholars/tools/pdf/utils/gpu_detection.py +5 -9
  252. aiagents4pharma/talk2scholars/tools/pdf/utils/nvidia_nim_reranker.py +4 -4
  253. aiagents4pharma/talk2scholars/tools/pdf/utils/paper_loader.py +2 -2
  254. aiagents4pharma/talk2scholars/tools/pdf/utils/rag_pipeline.py +6 -15
  255. aiagents4pharma/talk2scholars/tools/pdf/utils/retrieve_chunks.py +7 -15
  256. aiagents4pharma/talk2scholars/tools/pdf/utils/singleton_manager.py +2 -2
  257. aiagents4pharma/talk2scholars/tools/pdf/utils/tool_helper.py +3 -4
  258. aiagents4pharma/talk2scholars/tools/pdf/utils/vector_normalization.py +8 -17
  259. aiagents4pharma/talk2scholars/tools/pdf/utils/vector_store.py +17 -33
  260. aiagents4pharma/talk2scholars/tools/s2/__init__.py +8 -6
  261. aiagents4pharma/talk2scholars/tools/s2/display_dataframe.py +3 -7
  262. aiagents4pharma/talk2scholars/tools/s2/multi_paper_rec.py +7 -6
  263. aiagents4pharma/talk2scholars/tools/s2/query_dataframe.py +5 -12
  264. aiagents4pharma/talk2scholars/tools/s2/retrieve_semantic_scholar_paper_id.py +2 -4
  265. aiagents4pharma/talk2scholars/tools/s2/search.py +6 -6
  266. aiagents4pharma/talk2scholars/tools/s2/single_paper_rec.py +5 -3
  267. aiagents4pharma/talk2scholars/tools/s2/utils/__init__.py +1 -3
  268. aiagents4pharma/talk2scholars/tools/s2/utils/multi_helper.py +12 -18
  269. aiagents4pharma/talk2scholars/tools/s2/utils/search_helper.py +11 -18
  270. aiagents4pharma/talk2scholars/tools/s2/utils/single_helper.py +11 -16
  271. aiagents4pharma/talk2scholars/tools/zotero/__init__.py +1 -4
  272. aiagents4pharma/talk2scholars/tools/zotero/utils/__init__.py +1 -4
  273. aiagents4pharma/talk2scholars/tools/zotero/utils/read_helper.py +21 -39
  274. aiagents4pharma/talk2scholars/tools/zotero/utils/review_helper.py +2 -6
  275. aiagents4pharma/talk2scholars/tools/zotero/utils/write_helper.py +8 -11
  276. aiagents4pharma/talk2scholars/tools/zotero/utils/zotero_path.py +4 -12
  277. aiagents4pharma/talk2scholars/tools/zotero/utils/zotero_pdf_downloader.py +13 -27
  278. aiagents4pharma/talk2scholars/tools/zotero/zotero_read.py +4 -7
  279. aiagents4pharma/talk2scholars/tools/zotero/zotero_review.py +8 -10
  280. aiagents4pharma/talk2scholars/tools/zotero/zotero_write.py +3 -2
  281. {aiagents4pharma-1.43.0.dist-info → aiagents4pharma-1.45.0.dist-info}/METADATA +115 -50
  282. aiagents4pharma-1.45.0.dist-info/RECORD +324 -0
  283. {aiagents4pharma-1.43.0.dist-info → aiagents4pharma-1.45.0.dist-info}/WHEEL +1 -2
  284. aiagents4pharma-1.43.0.dist-info/RECORD +0 -293
  285. aiagents4pharma-1.43.0.dist-info/top_level.txt +0 -1
  286. /aiagents4pharma/talk2scholars/tests/{test_state.py → test_states_state.py} +0 -0
  287. /aiagents4pharma/talk2scholars/tests/{test_pdf_paper_loader.py → test_utils_pdf_paper_loader.py} +0 -0
  288. /aiagents4pharma/talk2scholars/tests/{test_tool_helper_utils.py → test_utils_tool_helper_utils.py} +0 -0
  289. /aiagents4pharma/talk2scholars/tests/{test_zotero_pdf_downloader_utils.py → test_utils_zotero_pdf_downloader_utils.py} +0 -0
  290. {aiagents4pharma-1.43.0.dist-info → aiagents4pharma-1.45.0.dist-info}/licenses/LICENSE +0 -0
@@ -139,8 +139,7 @@ class TestPubmedDownloaderBasics(unittest.TestCase):
139
139
  """<error> node -> return empty string."""
140
140
  resp = Mock()
141
141
  resp.text = (
142
- '<?xml version="1.0"?><OA><error code="idDoesNotExist">'
143
- "Invalid PMC ID</error></OA>"
142
+ '<?xml version="1.0"?><OA><error code="idDoesNotExist">Invalid PMC ID</error></OA>'
144
143
  )
145
144
  resp.raise_for_status = Mock()
146
145
  mock_get.return_value = resp
@@ -299,10 +298,7 @@ class TestPubmedDownloaderEuropePMC(unittest.TestCase):
299
298
  resp.status_code = 200
300
299
  mock_head.return_value = resp
301
300
  result = self.downloader.try_europe_pmc_public("PMC123456")
302
- expected = (
303
- "https://www.ebi.ac.uk/europepmc/webservices/rest"
304
- "?accid=PMC123456&blobtype=pdf"
305
- )
301
+ expected = "https://www.ebi.ac.uk/europepmc/webservices/rest?accid=PMC123456&blobtype=pdf"
306
302
  mock_head.assert_called_once_with(expected, timeout=30)
307
303
  self.assertEqual(result, expected)
308
304
 
@@ -356,18 +352,14 @@ class TestPubmedDownloaderPMCScrape(unittest.TestCase):
356
352
 
357
353
  expected_url = "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC123456/"
358
354
  expected_headers = {"User-Agent": "Mozilla/5.0 (compatible; test-agent)"}
359
- mock_get.assert_called_once_with(
360
- expected_url, headers=expected_headers, timeout=30
361
- )
362
- self.assertEqual(
363
- result, "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC123456/pdf/test.pdf"
364
- )
355
+ mock_get.assert_called_once_with(expected_url, headers=expected_headers, timeout=30)
356
+ self.assertEqual(result, "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC123456/pdf/test.pdf")
365
357
 
366
358
  @patch("requests.get")
367
359
  def test_try_pmc_page_scraping_no_pdf(self, mock_get):
368
360
  """Test PMC page scraping with no PDF found."""
369
361
  resp = Mock()
370
- resp.content = "<html><head></head></html>".encode()
362
+ resp.content = b"<html><head></head></html>"
371
363
  resp.raise_for_status = Mock()
372
364
  mock_get.return_value = resp
373
365
  self.assertEqual(self.downloader.try_pmc_page_scraping_public("PMC123456"), "")
@@ -486,17 +478,13 @@ class TestPubmedDownloaderConstructAndFallbacks(unittest.TestCase):
486
478
  """Test fallback chain through multiple sources."""
487
479
  with (
488
480
  patch.object(self.downloader, "try_oa_api_public", return_value="") as m_oa,
489
- patch.object(
490
- self.downloader, "try_europe_pmc_public", return_value=""
491
- ) as m_eu,
481
+ patch.object(self.downloader, "try_europe_pmc_public", return_value="") as m_eu,
492
482
  patch.object(
493
483
  self.downloader,
494
484
  "try_pmc_page_scraping_public",
495
485
  return_value="http://test.pdf",
496
486
  ) as m_scr,
497
- patch.object(
498
- self.downloader, "try_direct_pmc_url_public", return_value=""
499
- ) as m_dir,
487
+ patch.object(self.downloader, "try_direct_pmc_url_public", return_value="") as m_dir,
500
488
  ):
501
489
  out = self.downloader.fetch_pdf_url_with_fallbacks_public("PMC123456")
502
490
  self.assertEqual(out, "http://test.pdf")
@@ -509,12 +497,8 @@ class TestPubmedDownloaderConstructAndFallbacks(unittest.TestCase):
509
497
  """Test fallback chain with direct PMC success."""
510
498
  with (
511
499
  patch.object(self.downloader, "try_oa_api_public", return_value="") as m_oa,
512
- patch.object(
513
- self.downloader, "try_europe_pmc_public", return_value=""
514
- ) as m_eu,
515
- patch.object(
516
- self.downloader, "try_pmc_page_scraping_public", return_value=""
517
- ) as m_scr,
500
+ patch.object(self.downloader, "try_europe_pmc_public", return_value="") as m_eu,
501
+ patch.object(self.downloader, "try_pmc_page_scraping_public", return_value="") as m_scr,
518
502
  patch.object(
519
503
  self.downloader,
520
504
  "try_direct_pmc_url_public",
@@ -532,15 +516,9 @@ class TestPubmedDownloaderConstructAndFallbacks(unittest.TestCase):
532
516
  """Test fallback chain when all sources fail."""
533
517
  with (
534
518
  patch.object(self.downloader, "try_oa_api_public", return_value="") as m_oa,
535
- patch.object(
536
- self.downloader, "try_europe_pmc_public", return_value=""
537
- ) as m_eu,
538
- patch.object(
539
- self.downloader, "try_pmc_page_scraping_public", return_value=""
540
- ) as m_scr,
541
- patch.object(
542
- self.downloader, "try_direct_pmc_url_public", return_value=""
543
- ) as m_dir,
519
+ patch.object(self.downloader, "try_europe_pmc_public", return_value="") as m_eu,
520
+ patch.object(self.downloader, "try_pmc_page_scraping_public", return_value="") as m_scr,
521
+ patch.object(self.downloader, "try_direct_pmc_url_public", return_value="") as m_dir,
544
522
  ):
545
523
  out = self.downloader.fetch_pdf_url_with_fallbacks_public("PMC123456")
546
524
  self.assertEqual(out, "")
@@ -587,9 +565,7 @@ class TestPubmedDownloaderIntegration(unittest.TestCase):
587
565
  """Test full workflow from PMID to PDF URL."""
588
566
  metadata_response = Mock()
589
567
  metadata_response.json.return_value = {
590
- "records": [
591
- {"pmid": "12345678", "pmcid": "PMC123456", "doi": "10.1234/test"}
592
- ]
568
+ "records": [{"pmid": "12345678", "pmcid": "PMC123456", "doi": "10.1234/test"}]
593
569
  }
594
570
  metadata_response.raise_for_status = Mock()
595
571
 
@@ -631,9 +607,7 @@ class TestPubmedDownloaderIntegration(unittest.TestCase):
631
607
  """Test workflow with fallback to alternative sources."""
632
608
  metadata_response = Mock()
633
609
  metadata_response.json.return_value = {
634
- "records": [
635
- {"pmid": "12345678", "pmcid": "PMC123456", "doi": "10.1234/test"}
636
- ]
610
+ "records": [{"pmid": "12345678", "pmcid": "PMC123456", "doi": "10.1234/test"}]
637
611
  }
638
612
  metadata_response.raise_for_status = Mock()
639
613
 
@@ -780,9 +754,7 @@ class TestPubmedDownloaderHelpers(unittest.TestCase):
780
754
  """Test that placeholder abstracts return empty snippets."""
781
755
  self.assertEqual(self.downloader.get_snippet(""), "")
782
756
  self.assertEqual(self.downloader.get_snippet("N/A"), "")
783
- self.assertEqual(
784
- self.downloader.get_snippet("Abstract available in PubMed"), ""
785
- )
757
+ self.assertEqual(self.downloader.get_snippet("Abstract available in PubMed"), "")
786
758
 
787
759
  def test_get_snippet_non_placeholder_delegates_to_base(self):
788
760
  """Test that non-placeholder abstracts delegate to base class."""
@@ -793,9 +765,7 @@ class TestPubmedDownloaderHelpers(unittest.TestCase):
793
765
 
794
766
  def test_get_paper_identifier_info_without_pmcid_line(self):
795
767
  """Test paper identifier info formatting without PMCID."""
796
- info = self.downloader.get_paper_identifier_info_public(
797
- {"PMID": "999", "PMCID": "N/A"}
798
- )
768
+ info = self.downloader.get_paper_identifier_info_public({"PMID": "999", "PMCID": "N/A"})
799
769
  self.assertIn("(PMID: 999)", info)
800
770
  self.assertNotIn("PMCID:", info)
801
771
 
@@ -861,14 +831,10 @@ class TestPubmedDownloaderMissingLineCoverage(unittest.TestCase):
861
831
  patch.object(self.downloader, "_try_pmc_page_scraping", return_value=""),
862
832
  patch.object(self.downloader, "_try_direct_pmc_url", return_value=""),
863
833
  patch(
864
- "aiagents4pharma.talk2scholars.tools.paper_download.utils."
865
- "pubmed_downloader.logger"
834
+ "aiagents4pharma.talk2scholars.tools.paper_download.utils.pubmed_downloader.logger"
866
835
  ) as mock_logger,
867
836
  ):
868
-
869
- result = self.downloader.fetch_pdf_url_with_fallbacks_production(
870
- "PMC123456"
871
- )
837
+ result = self.downloader.fetch_pdf_url_with_fallbacks_production("PMC123456")
872
838
 
873
839
  self.assertEqual(result, "")
874
840
  # Verify the warning log is called
@@ -879,21 +845,15 @@ class TestPubmedDownloaderMissingLineCoverage(unittest.TestCase):
879
845
  def test_fetch_pdf_url_with_fallbacks_oa_api_success_early_return(self):
880
846
  """Test _fetch_pdf_url_with_fallbacks when OA API succeeds on first try."""
881
847
  with (
882
- patch.object(
883
- self.downloader, "_try_oa_api", return_value="http://oa.pdf"
884
- ) as mock_oa,
848
+ patch.object(self.downloader, "_try_oa_api", return_value="http://oa.pdf") as mock_oa,
885
849
  patch.object(self.downloader, "_try_europe_pmc") as mock_eu,
886
850
  patch.object(self.downloader, "_try_pmc_page_scraping") as mock_scr,
887
851
  patch.object(self.downloader, "_try_direct_pmc_url") as mock_dir,
888
852
  patch(
889
- "aiagents4pharma.talk2scholars.tools.paper_download.utils."
890
- "pubmed_downloader.logger"
853
+ "aiagents4pharma.talk2scholars.tools.paper_download.utils.pubmed_downloader.logger"
891
854
  ) as mock_logger,
892
855
  ):
893
-
894
- result = self.downloader.fetch_pdf_url_with_fallbacks_production(
895
- "PMC123456"
896
- )
856
+ result = self.downloader.fetch_pdf_url_with_fallbacks_production("PMC123456")
897
857
 
898
858
  self.assertEqual(result, "http://oa.pdf")
899
859
  mock_oa.assert_called_once_with("PMC123456")
@@ -901,9 +861,7 @@ class TestPubmedDownloaderMissingLineCoverage(unittest.TestCase):
901
861
  mock_scr.assert_not_called()
902
862
  mock_dir.assert_not_called()
903
863
  # Verify the initial info log is called
904
- mock_logger.info.assert_called_with(
905
- "Fetching PDF URL for PMCID: %s", "PMC123456"
906
- )
864
+ mock_logger.info.assert_called_with("Fetching PDF URL for PMCID: %s", "PMC123456")
907
865
 
908
866
  def test_fetch_pdf_url_with_fallbacks_europe_pmc_success_after_oa_fail(self):
909
867
  """Test _fetch_pdf_url_with_fallbacks when Europe PMC succeeds after OA API fails."""
@@ -915,10 +873,7 @@ class TestPubmedDownloaderMissingLineCoverage(unittest.TestCase):
915
873
  patch.object(self.downloader, "_try_pmc_page_scraping") as mock_scr,
916
874
  patch.object(self.downloader, "_try_direct_pmc_url") as mock_dir,
917
875
  ):
918
-
919
- result = self.downloader.fetch_pdf_url_with_fallbacks_production(
920
- "PMC123456"
921
- )
876
+ result = self.downloader.fetch_pdf_url_with_fallbacks_production("PMC123456")
922
877
 
923
878
  self.assertEqual(result, "http://eu.pdf")
924
879
  mock_oa.assert_called_once_with("PMC123456")
@@ -932,18 +887,13 @@ class TestPubmedDownloaderMissingLineCoverage(unittest.TestCase):
932
887
  """Test _fetch_pdf_url_with_fallbacks when PMC scraping succeeds."""
933
888
  with (
934
889
  patch.object(self.downloader, "_try_oa_api", return_value="") as mock_oa,
935
- patch.object(
936
- self.downloader, "_try_europe_pmc", return_value=""
937
- ) as mock_eu,
890
+ patch.object(self.downloader, "_try_europe_pmc", return_value="") as mock_eu,
938
891
  patch.object(
939
892
  self.downloader, "_try_pmc_page_scraping", return_value="http://scr.pdf"
940
893
  ) as mock_scr,
941
894
  patch.object(self.downloader, "_try_direct_pmc_url") as mock_dir,
942
895
  ):
943
-
944
- result = self.downloader.fetch_pdf_url_with_fallbacks_production(
945
- "PMC123456"
946
- )
896
+ result = self.downloader.fetch_pdf_url_with_fallbacks_production("PMC123456")
947
897
 
948
898
  self.assertEqual(result, "http://scr.pdf")
949
899
  mock_oa.assert_called_once_with("PMC123456")
@@ -955,20 +905,13 @@ class TestPubmedDownloaderMissingLineCoverage(unittest.TestCase):
955
905
  """Test _fetch_pdf_url_with_fallbacks when direct PMC succeeds as last resort."""
956
906
  with (
957
907
  patch.object(self.downloader, "_try_oa_api", return_value="") as mock_oa,
958
- patch.object(
959
- self.downloader, "_try_europe_pmc", return_value=""
960
- ) as mock_eu,
961
- patch.object(
962
- self.downloader, "_try_pmc_page_scraping", return_value=""
963
- ) as mock_scr,
908
+ patch.object(self.downloader, "_try_europe_pmc", return_value="") as mock_eu,
909
+ patch.object(self.downloader, "_try_pmc_page_scraping", return_value="") as mock_scr,
964
910
  patch.object(
965
911
  self.downloader, "_try_direct_pmc_url", return_value="http://dir.pdf"
966
912
  ) as mock_dir,
967
913
  ):
968
-
969
- result = self.downloader.fetch_pdf_url_with_fallbacks_production(
970
- "PMC123456"
971
- )
914
+ result = self.downloader.fetch_pdf_url_with_fallbacks_production("PMC123456")
972
915
 
973
916
  self.assertEqual(result, "http://dir.pdf")
974
917
  mock_oa.assert_called_once_with("PMC123456")
@@ -1012,8 +955,7 @@ class TestPubmedDownloaderProductionConstructPdfUrl(unittest.TestCase):
1012
955
  """Test production construct_pdf_url with missing pmcid key."""
1013
956
  metadata = {"records": [{"doi": "10.1/x"}]}
1014
957
  with patch(
1015
- "aiagents4pharma.talk2scholars.tools.paper_download.utils."
1016
- "pubmed_downloader.logger"
958
+ "aiagents4pharma.talk2scholars.tools.paper_download.utils.pubmed_downloader.logger"
1017
959
  ) as mock_logger:
1018
960
  result = self.downloader.construct_pdf_url(metadata, "12345678")
1019
961
 
@@ -1027,8 +969,7 @@ class TestPubmedDownloaderProductionConstructPdfUrl(unittest.TestCase):
1027
969
  """Test production construct_pdf_url with empty pmcid."""
1028
970
  metadata = {"records": [{"pmcid": "", "doi": "10.1/x"}]}
1029
971
  with patch(
1030
- "aiagents4pharma.talk2scholars.tools.paper_download.utils."
1031
- "pubmed_downloader.logger"
972
+ "aiagents4pharma.talk2scholars.tools.paper_download.utils.pubmed_downloader.logger"
1032
973
  ) as mock_logger:
1033
974
  result = self.downloader.construct_pdf_url(metadata, "12345678")
1034
975
 
@@ -1042,8 +983,7 @@ class TestPubmedDownloaderProductionConstructPdfUrl(unittest.TestCase):
1042
983
  """Test production construct_pdf_url with N/A pmcid."""
1043
984
  metadata = {"records": [{"pmcid": "N/A", "doi": "10.1/x"}]}
1044
985
  with patch(
1045
- "aiagents4pharma.talk2scholars.tools.paper_download.utils."
1046
- "pubmed_downloader.logger"
986
+ "aiagents4pharma.talk2scholars.tools.paper_download.utils.pubmed_downloader.logger"
1047
987
  ) as mock_logger:
1048
988
  result = self.downloader.construct_pdf_url(metadata, "12345678")
1049
989
 
@@ -27,14 +27,10 @@ dummy_cfg = SimpleNamespace(tools=SimpleNamespace(zotero_read=dummy_zotero_read_
27
27
  class TestReadHelperDownloadsFalse(unittest.TestCase):
28
28
  """Tests for read_helper download_pdfs=False branches."""
29
29
 
30
- @patch(
31
- "aiagents4pharma.talk2scholars.tools.zotero.utils.zotero_path.get_item_collections"
32
- )
30
+ @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.zotero_path.get_item_collections")
33
31
  @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.read_helper.zotero.Zotero")
34
32
  @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.read_helper.hydra.compose")
35
- @patch(
36
- "aiagents4pharma.talk2scholars.tools.zotero.utils.read_helper.hydra.initialize"
37
- )
33
+ @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.read_helper.hydra.initialize")
38
34
  def test_download_pdfs_false_branches(
39
35
  self,
40
36
  mock_hydra_init,
@@ -49,9 +49,7 @@ def patch_hydra(monkeypatch):
49
49
  ),
50
50
  )
51
51
  )
52
- monkeypatch.setattr(
53
- hydra, "initialize", lambda version_base, config_path: DummyHydraContext()
54
- )
52
+ monkeypatch.setattr(hydra, "initialize", lambda version_base, config_path: DummyHydraContext())
55
53
  monkeypatch.setattr(hydra, "compose", lambda config_name, overrides: dummy_cfg)
56
54
 
57
55
 
@@ -69,9 +67,7 @@ def test_multi_helper_pmc_and_doi_ids(monkeypatch):
69
67
  }
70
68
  ]
71
69
  }
72
- response = SimpleNamespace(
73
- status_code=200, json=lambda: data, raise_for_status=lambda: None
74
- )
70
+ response = SimpleNamespace(status_code=200, json=lambda: data, raise_for_status=lambda: None)
75
71
  monkeypatch.setattr(requests, "post", lambda *args, **kwargs: response)
76
72
  results = rec.process_recommendations()
77
73
  ids_list = results["papers"]["p1"]["paper_ids"]
@@ -91,9 +87,7 @@ def test_search_helper_pmc_and_doi_ids(monkeypatch):
91
87
  }
92
88
  ]
93
89
  }
94
- response = SimpleNamespace(
95
- status_code=200, json=lambda: data, raise_for_status=lambda: None
96
- )
90
+ response = SimpleNamespace(status_code=200, json=lambda: data, raise_for_status=lambda: None)
97
91
  monkeypatch.setattr(requests, "get", lambda *args, **kwargs: response)
98
92
  results = sd.process_search()
99
93
  ids_list = results["papers"]["s1"]["paper_ids"]
@@ -113,9 +107,7 @@ def test_single_helper_pmc_and_doi_ids(monkeypatch):
113
107
  }
114
108
  ]
115
109
  }
116
- response = SimpleNamespace(
117
- status_code=200, json=lambda: data, raise_for_status=lambda: None
118
- )
110
+ response = SimpleNamespace(status_code=200, json=lambda: data, raise_for_status=lambda: None)
119
111
  monkeypatch.setattr(requests, "get", lambda *args, **kwargs: response)
120
112
  results = sp.process_recommendations()
121
113
  ids_list = results["papers"]["x1"]["paper_ids"]
@@ -395,9 +387,7 @@ def test_single_helper_create_content_snippet(monkeypatch):
395
387
 
396
388
  def test_multi_helper_create_content_snippet(monkeypatch):
397
389
  """Test that MultiPaperRecData._create_content includes snippets appropriately."""
398
- mr = MultiPaperRecData(
399
- paper_ids=["a", "b"], limit=2, year="2021", tool_call_id="tid"
400
- )
390
+ mr = MultiPaperRecData(paper_ids=["a", "b"], limit=2, year="2021", tool_call_id="tid")
401
391
  mr.filtered_papers = {
402
392
  "m1": {"Title": "MTitle1", "Year": "2017", "Abstract": "MOne. MTwo. MThree."},
403
393
  "m2": {"Title": "MTitle2", "Year": "2016", "Abstract": ""},
@@ -3,7 +3,8 @@ Unit tests for Zotero human in the loop in zotero_review.py with structured outp
3
3
  """
4
4
 
5
5
  import unittest
6
- from unittest.mock import patch, MagicMock
6
+ from unittest.mock import MagicMock, patch
7
+
7
8
  from aiagents4pharma.talk2scholars.tools.zotero.zotero_review import zotero_review
8
9
 
9
10
 
@@ -185,22 +186,16 @@ class TestZoteroReviewTool(unittest.TestCase):
185
186
 
186
187
  upd = result.update
187
188
  self.assertEqual(upd["zotero_write_approval_status"], {"approved": False})
188
- self.assertIn(
189
- "Human rejected saving papers to Zotero", upd["messages"][0].content
190
- )
189
+ self.assertIn("Human rejected saving papers to Zotero", upd["messages"][0].content)
191
190
  mock_fetch.assert_called_once()
192
191
  mock_interrupt.assert_called_once()
193
192
 
194
- @patch(
195
- "aiagents4pharma.talk2scholars.tools.zotero.zotero_review.fetch_papers_for_save"
196
- )
193
+ @patch("aiagents4pharma.talk2scholars.tools.zotero.zotero_review.fetch_papers_for_save")
197
194
  @patch("aiagents4pharma.talk2scholars.tools.zotero.zotero_review.interrupt")
198
195
  def test_structured_processing_failure(self, mock_interrupt, mock_fetch):
199
196
  """Test fallback when structured review processing fails."""
200
197
  # Simulate valid fetched papers with multiple entries.
201
- papers = {
202
- f"p{i}": {"Title": f"Title{i}", "Authors": [f"A{i}"]} for i in range(1, 8)
203
- }
198
+ papers = {f"p{i}": {"Title": f"Title{i}", "Authors": [f"A{i}"]} for i in range(1, 8)}
204
199
  mock_fetch.return_value = papers
205
200
  mock_interrupt.return_value = "dummy_response"
206
201
  # Provide a fake llm_model whose invoke() raises an exception.
@@ -234,9 +229,7 @@ class TestZoteroReviewTool(unittest.TestCase):
234
229
 
235
230
  @patch(
236
231
  "aiagents4pharma.talk2scholars.tools.zotero.zotero_review.fetch_papers_for_save",
237
- return_value={
238
- "p1": {"Title": "Test Paper", "Authors": ["Alice", "Bob", "Charlie"]}
239
- },
232
+ return_value={"p1": {"Title": "Test Paper", "Authors": ["Alice", "Bob", "Charlie"]}},
240
233
  )
241
234
  @patch(
242
235
  "aiagents4pharma.talk2scholars.tools.zotero.zotero_review.interrupt",
@@ -4,7 +4,9 @@ Unit tests for Zotero path utility in zotero_path.py.
4
4
 
5
5
  import unittest
6
6
  from unittest.mock import MagicMock, patch
7
+
7
8
  import pytest
9
+
8
10
  from aiagents4pharma.talk2scholars.tools.zotero.utils.zotero_path import (
9
11
  fetch_papers_for_save,
10
12
  find_or_create_collection,
@@ -92,12 +94,8 @@ class TestFindOrCreateCollectionExtra(unittest.TestCase):
92
94
  # Simulate no existing collections (so direct match fails)
93
95
  self.fake_zot.collections.return_value = []
94
96
  # Simulate create_collection returning a dict with a "success" key.
95
- self.fake_zot.create_collection.return_value = {
96
- "success": {"0": "new_key_success"}
97
- }
98
- result = find_or_create_collection(
99
- self.fake_zot, "/NewCollection", create_missing=True
100
- )
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)
101
99
  self.assertEqual(result, "new_key_success")
102
100
  # Verify payload formatting: for a simple (non-nested) path, no parentCollection.
103
101
  args, _ = self.fake_zot.create_collection.call_args
@@ -114,9 +112,7 @@ class TestFindOrCreateCollectionExtra(unittest.TestCase):
114
112
  self.fake_zot.create_collection.return_value = {
115
113
  "successful": {"0": {"data": {"key": "new_key_successful"}}}
116
114
  }
117
- result = find_or_create_collection(
118
- self.fake_zot, "/NewCollection", create_missing=True
119
- )
115
+ result = find_or_create_collection(self.fake_zot, "/NewCollection", create_missing=True)
120
116
  self.assertEqual(result, "new_key_successful")
121
117
 
122
118
  def test_create_collection_exception(self):
@@ -126,9 +122,7 @@ class TestFindOrCreateCollectionExtra(unittest.TestCase):
126
122
  """
127
123
  self.fake_zot.collections.return_value = []
128
124
  self.fake_zot.create_collection.side_effect = Exception("Creation error")
129
- result = find_or_create_collection(
130
- self.fake_zot, "/NewCollection", create_missing=True
131
- )
125
+ result = find_or_create_collection(self.fake_zot, "/NewCollection", create_missing=True)
132
126
  self.assertIsNone(result)
133
127
 
134
128
 
@@ -228,9 +222,7 @@ class TestZoteroPath:
228
222
  mock_zotero.return_value = mock_zot
229
223
 
230
224
  # Setup collections
231
- collections = [
232
- {"key": "abc123", "data": {"name": "Curiosity", "parentCollection": None}}
233
- ]
225
+ collections = [{"key": "abc123", "data": {"name": "Curiosity", "parentCollection": None}}]
234
226
  mock_zot.collections.return_value = collections
235
227
 
236
228
  # Setup create_collection response
@@ -249,9 +241,7 @@ class TestZoteroPath:
249
241
 
250
242
  # Test creating nested "Curiosity/Curiosity2"
251
243
  mock_zot.create_collection.reset_mock()
252
- result = find_or_create_collection(
253
- mock_zot, "/Curiosity/Curiosity2", create_missing=True
254
- )
244
+ result = find_or_create_collection(mock_zot, "/Curiosity/Curiosity2", create_missing=True)
255
245
  assert result == "new_key"
256
246
  # Check that the call includes parentCollection
257
247
  mock_zot.create_collection.assert_called_once()
@@ -314,11 +304,24 @@ class TestZoteroWrite:
314
304
  mock_zot_class.return_value = mock_zot
315
305
  yield mock_zot
316
306
 
317
- @patch(
318
- "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.fetch_papers_for_save"
319
- )
320
- def test_zotero_write_no_papers(self, mock_fetch):
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):
321
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
+
322
325
  mock_fetch.return_value = None
323
326
 
324
327
  state = {
@@ -338,22 +341,37 @@ class TestZoteroWrite:
338
341
  )
339
342
  assert "No fetched papers were found to save" in str(excinfo.value)
340
343
 
341
- @patch(
342
- "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.fetch_papers_for_save"
343
- )
344
+ @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.fetch_papers_for_save")
344
345
  @patch(
345
346
  "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.find_or_create_collection"
346
347
  )
347
- def test_zotero_write_invalid_collection(self, mock_find, mock_fetch, mock_zotero):
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
+ ):
348
354
  """Saving to a nonexistent Zotero collection returns an error Command."""
349
- sample = {"paper1": {"Title": "Test Paper"}}
350
- mock_fetch.return_value = sample
351
- mock_find.return_value = None
352
- mock_zotero.collections.return_value = [
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 = [
353
367
  {"key": "k1", "data": {"name": "Curiosity"}},
354
368
  {"key": "k2", "data": {"name": "Random"}},
355
369
  ]
356
370
 
371
+ sample = {"paper1": {"Title": "Test Paper"}}
372
+ mock_fetch.return_value = sample
373
+ mock_find.return_value = None
374
+
357
375
  state = {
358
376
  "zotero_write_approval_status": {
359
377
  "approved": True,
@@ -375,9 +393,7 @@ class TestZoteroWrite:
375
393
  assert "does not exist in Zotero" in msg
376
394
  assert "Curiosity, Random" in msg
377
395
 
378
- @patch(
379
- "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.fetch_papers_for_save"
380
- )
396
+ @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.fetch_papers_for_save")
381
397
  @patch(
382
398
  "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.find_or_create_collection"
383
399
  )
@@ -386,12 +402,8 @@ class TestZoteroWrite:
386
402
  sample = {"paper1": {"Title": "Test Paper", "Authors": ["Test Author"]}}
387
403
  mock_fetch.return_value = sample
388
404
  mock_find.return_value = "abc123"
389
- mock_zotero.collections.return_value = [
390
- {"key": "abc123", "data": {"name": "radiation"}}
391
- ]
392
- mock_zotero.create_items.return_value = {
393
- "successful": {"0": {"key": "item123"}}
394
- }
405
+ mock_zotero.collections.return_value = [{"key": "abc123", "data": {"name": "radiation"}}]
406
+ mock_zotero.create_items.return_value = {"successful": {"0": {"key": "item123"}}}
395
407
  mock_hydra.tools.zotero_write.zotero.max_limit = 50
396
408
 
397
409
  state = {
@@ -423,9 +435,7 @@ class TestZoteroRead:
423
435
  def mock_hydra(self):
424
436
  """Fixture to mock hydra configuration."""
425
437
  with (
426
- patch(
427
- "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.hydra.initialize"
428
- ),
438
+ patch("aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.hydra.initialize"),
429
439
  patch(
430
440
  "aiagents4pharma.talk2scholars.tools.zotero.utils.write_helper.hydra.compose"
431
441
  ) as mock_compose,
@@ -453,9 +463,7 @@ class TestZoteroRead:
453
463
  mock_zot_class.return_value = mock_zot
454
464
  yield mock_zot
455
465
 
456
- @patch(
457
- "aiagents4pharma.talk2scholars.tools.zotero.utils.zotero_path.get_item_collections"
458
- )
466
+ @patch("aiagents4pharma.talk2scholars.tools.zotero.utils.zotero_path.get_item_collections")
459
467
  def test_zotero_read_item_collections_error(
460
468
  self, mock_get_collections, mock_hydra, mock_zotero
461
469
  ):