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,270 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """
4
+ Utility for zotero read tool.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any
9
+
10
+ import hydra
11
+ import requests
12
+ from pyzotero import zotero
13
+
14
+ from .zotero_path import get_item_collections
15
+ from .zotero_pdf_downloader import download_pdfs_in_parallel
16
+
17
+ # Configure logging
18
+ logging.basicConfig(level=logging.INFO)
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # pylint: disable=broad-exception-caught
22
+
23
+
24
+ class ZoteroSearchData:
25
+ """Helper class to organize Zotero search-related data."""
26
+
27
+ def __init__(
28
+ self,
29
+ query: str,
30
+ only_articles: bool,
31
+ limit: int,
32
+ download_pdfs: bool = True,
33
+ **_kwargs,
34
+ ):
35
+ self.query = query
36
+ self.only_articles = only_articles
37
+ self.limit = limit
38
+ # Control whether to fetch PDF attachments now
39
+ self.download_pdfs = download_pdfs
40
+ self.cfg = self._load_config()
41
+ self.zot = self._init_zotero_client()
42
+ self.item_to_collections = get_item_collections(self.zot)
43
+ self.article_data = {}
44
+ self.content = ""
45
+ # Create a session for connection pooling
46
+ self.session = requests.Session()
47
+
48
+ def process_search(self) -> None:
49
+ """Process the search request and prepare results."""
50
+ items = self._fetch_items()
51
+ self._filter_and_format_papers(items)
52
+ self._create_content()
53
+
54
+ def get_search_results(self) -> dict[str, Any]:
55
+ """Get the search results and content."""
56
+ return {
57
+ "article_data": self.article_data,
58
+ "content": self.content,
59
+ }
60
+
61
+ def _load_config(self) -> Any:
62
+ """Load hydra configuration."""
63
+ with hydra.initialize(version_base=None, config_path="../../../configs"):
64
+ cfg = hydra.compose(config_name="config", overrides=["tools/zotero_read=default"])
65
+ logger.info("Loaded configuration for Zotero search tool")
66
+ return cfg.tools.zotero_read
67
+
68
+ def _init_zotero_client(self) -> zotero.Zotero:
69
+ """Initialize Zotero client."""
70
+ logger.info(
71
+ "Searching Zotero for query: '%s' (only_articles: %s, limit: %d)",
72
+ self.query,
73
+ self.only_articles,
74
+ self.limit,
75
+ )
76
+ return zotero.Zotero(self.cfg.user_id, self.cfg.library_type, self.cfg.api_key)
77
+
78
+ def _fetch_items(self) -> list[dict[str, Any]]:
79
+ """Fetch items from Zotero."""
80
+ try:
81
+ if self.query.strip() == "":
82
+ logger.info(
83
+ "Empty query provided, fetching all items up to max_limit: %d",
84
+ self.cfg.zotero.max_limit,
85
+ )
86
+ items = self.zot.items(limit=self.cfg.zotero.max_limit)
87
+ else:
88
+ items = self.zot.items(
89
+ q=self.query, limit=min(self.limit, self.cfg.zotero.max_limit)
90
+ )
91
+ except Exception as e:
92
+ logger.error("Failed to fetch items from Zotero: %s", e)
93
+ raise RuntimeError(
94
+ "Failed to fetch items from Zotero. Please retry the same query."
95
+ ) from e
96
+
97
+ logger.info("Received %d items from Zotero", len(items))
98
+
99
+ if not items:
100
+ logger.error("No items returned from Zotero for query: '%s'", self.query)
101
+ raise RuntimeError("No items returned from Zotero. Please retry the same query.")
102
+
103
+ return items
104
+
105
+ def _collect_item_attachments(self) -> dict[str, str]:
106
+ """Collect PDF attachment keys for non-orphan items."""
107
+ item_attachments: dict[str, str] = {}
108
+ for item_key, item_data in self.article_data.items():
109
+ if item_data.get("Type") == "orphan_attachment":
110
+ continue
111
+ try:
112
+ children = self.zot.children(item_key)
113
+ for child in children:
114
+ data = child.get("data", {})
115
+ if data.get("contentType") == "application/pdf":
116
+ attachment_key = data.get("key")
117
+ filename = data.get("filename", "unknown.pdf")
118
+ if attachment_key:
119
+ item_attachments[attachment_key] = item_key
120
+ self.article_data[item_key]["filename"] = filename
121
+ break
122
+ except Exception as e:
123
+ logger.error("Failed to get attachments for item %s: %s", item_key, e)
124
+ return item_attachments
125
+
126
+ def _process_orphaned_pdfs(self, orphaned_pdfs: dict[str, str]) -> None:
127
+ """Download or record orphaned PDF attachments."""
128
+ if self.download_pdfs:
129
+ logger.info("Downloading %d orphaned PDFs in parallel", len(orphaned_pdfs))
130
+ results = download_pdfs_in_parallel(
131
+ self.session,
132
+ self.cfg.user_id,
133
+ self.cfg.api_key,
134
+ orphaned_pdfs,
135
+ chunk_size=getattr(self.cfg, "chunk_size", None),
136
+ )
137
+ for item_key, (file_path, filename, attachment_key) in results.items():
138
+ self.article_data[item_key]["filename"] = filename
139
+ self.article_data[item_key]["pdf_url"] = file_path
140
+ self.article_data[item_key]["attachment_key"] = attachment_key
141
+ logger.info("Downloaded orphaned Zotero PDF to: %s", file_path)
142
+ else:
143
+ logger.info("Skipping orphaned PDF downloads (download_pdfs=False)")
144
+ for attachment_key in orphaned_pdfs:
145
+ self.article_data[attachment_key]["attachment_key"] = attachment_key
146
+ self.article_data[attachment_key]["filename"] = self.article_data[
147
+ attachment_key
148
+ ].get("Title", attachment_key)
149
+
150
+ def _process_item_pdfs(self, item_attachments: dict[str, str]) -> None:
151
+ """Download or record regular item PDF attachments."""
152
+ if self.download_pdfs:
153
+ logger.info("Downloading %d regular item PDFs in parallel", len(item_attachments))
154
+ results = download_pdfs_in_parallel(
155
+ self.session,
156
+ self.cfg.user_id,
157
+ self.cfg.api_key,
158
+ item_attachments,
159
+ chunk_size=getattr(self.cfg, "chunk_size", None),
160
+ )
161
+ else:
162
+ logger.info("Skipping regular PDF downloads (download_pdfs=False)")
163
+ results = {}
164
+ for attachment_key, item_key in item_attachments.items():
165
+ self.article_data[item_key]["attachment_key"] = attachment_key
166
+ for item_key, (file_path, filename, attachment_key) in results.items():
167
+ self.article_data[item_key]["filename"] = filename
168
+ self.article_data[item_key]["pdf_url"] = file_path
169
+ self.article_data[item_key]["attachment_key"] = attachment_key
170
+ logger.info("Downloaded Zotero PDF to: %s", file_path)
171
+
172
+ def _filter_and_format_papers(self, items: list[dict[str, Any]]) -> None:
173
+ """Filter and format papers from Zotero items, including standalone PDFs."""
174
+ filter_item_types = self.cfg.zotero.filter_item_types if self.only_articles else []
175
+ logger.debug("Filtering item types: %s", filter_item_types)
176
+
177
+ # Maps to track attachments for batch processing
178
+ orphaned_pdfs: dict[str, str] = {} # attachment_key -> item key (same for orphans)
179
+
180
+ # First pass: process all items without downloading PDFs
181
+ for item in items:
182
+ if not isinstance(item, dict):
183
+ continue
184
+
185
+ data = item.get("data", {})
186
+ item_type = data.get("itemType", "N/A")
187
+ key = data.get("key")
188
+ if not key:
189
+ continue
190
+
191
+ # CASE 1: Top-level item (e.g., journalArticle)
192
+ if item_type != "attachment":
193
+ collection_paths = self.item_to_collections.get(key, ["/Unknown"])
194
+
195
+ self.article_data[key] = {
196
+ "Title": data.get("title", "N/A"),
197
+ "Abstract": data.get("abstractNote", "N/A"),
198
+ "Publication Date": data.get("date", "N/A"),
199
+ "URL": data.get("url", "N/A"),
200
+ "Type": item_type,
201
+ "Collections": collection_paths,
202
+ "Citation Count": data.get("citationCount", "N/A"),
203
+ "Venue": data.get("venue", "N/A"),
204
+ "Publication Venue": data.get("publicationTitle", "N/A"),
205
+ "Journal Name": data.get("journalAbbreviation", "N/A"),
206
+ "Authors": [
207
+ f"{creator.get('firstName', '')} {creator.get('lastName', '')}".strip()
208
+ for creator in data.get("creators", [])
209
+ if isinstance(creator, dict) and creator.get("creatorType") == "author"
210
+ ],
211
+ "source": "zotero",
212
+ }
213
+ # We'll collect attachment info in second pass
214
+
215
+ # CASE 2: Standalone orphaned PDF attachment
216
+ elif data.get("contentType") == "application/pdf" and not data.get("parentItem"):
217
+ attachment_key = key
218
+ filename = data.get("filename", "unknown.pdf")
219
+
220
+ # Add to orphaned PDFs for batch processing
221
+ orphaned_pdfs[attachment_key] = (
222
+ attachment_key # Same key as both attachment and "item"
223
+ )
224
+
225
+ # Create the entry without PDF info yet
226
+ self.article_data[key] = {
227
+ "Title": filename,
228
+ "Abstract": "No abstract available",
229
+ "Publication Date": "N/A",
230
+ "URL": "N/A",
231
+ "Type": "orphan_attachment",
232
+ "Collections": ["/(No Collection)"],
233
+ "Citation Count": "N/A",
234
+ "Venue": "N/A",
235
+ "Publication Venue": "N/A",
236
+ "Journal Name": "N/A",
237
+ "Authors": ["(Unknown)"],
238
+ "source": "zotero",
239
+ }
240
+
241
+ # Collect and process attachments
242
+ item_attachments = self._collect_item_attachments()
243
+
244
+ # Process orphaned PDFs
245
+ self._process_orphaned_pdfs(orphaned_pdfs)
246
+
247
+ # Process regular item PDFs
248
+ self._process_item_pdfs(item_attachments)
249
+
250
+ # Ensure we have some results
251
+ if not self.article_data:
252
+ logger.error("No matching papers returned from Zotero for query: '%s'", self.query)
253
+ raise RuntimeError(
254
+ "No matching papers returned from Zotero. Please retry the same query."
255
+ )
256
+
257
+ logger.info("Filtered %d items (including orphaned attachments)", len(self.article_data))
258
+
259
+ def _create_content(self) -> None:
260
+ """Create the content message for the response."""
261
+ top_papers = list(self.article_data.values())[:2]
262
+ top_papers_info = "\n".join(
263
+ [f"{i + 1}. {paper['Title']} ({paper['Type']})" for i, paper in enumerate(top_papers)]
264
+ )
265
+
266
+ self.content = "Retrieval was successful. Papers are attached as an artifact."
267
+ self.content += " And here is a summary of the retrieval results:\n"
268
+ self.content += f"Number of papers found: {len(self.article_data)}\n"
269
+ self.content += f"Query: {self.query}\n"
270
+ self.content += "Here are a few of these papers:\n" + top_papers_info
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """
4
+ Utility for reviewing papers and saving them to Zotero.
5
+ """
6
+
7
+ import logging
8
+
9
+ # Configure logging
10
+ logging.basicConfig(level=logging.INFO)
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class ReviewData:
15
+ """Helper class to organize review-related data."""
16
+
17
+ def __init__(
18
+ self,
19
+ collection_path: str,
20
+ fetched_papers: dict,
21
+ tool_call_id: str,
22
+ state: dict,
23
+ ):
24
+ self.collection_path = collection_path
25
+ self.fetched_papers = fetched_papers
26
+ self.tool_call_id = tool_call_id
27
+ self.state = state
28
+ self.total_papers = len(fetched_papers)
29
+ self.papers_summary = self._create_papers_summary()
30
+ self.papers_preview = "\n".join(self.papers_summary)
31
+ self.review_info = self._create_review_info()
32
+
33
+ def get_approval_message(self) -> str:
34
+ """Get the formatted approval message for the review."""
35
+ return (
36
+ f"Human approved saving {self.total_papers} papers to Zotero "
37
+ f"collection '{self.collection_path}'."
38
+ )
39
+
40
+ def get_custom_path_approval_message(self, custom_path: str) -> str:
41
+ """Get the formatted approval message for a custom collection path."""
42
+ return f"Human approved saving papers to custom Zotero collection '{custom_path}'."
43
+
44
+ def _create_papers_summary(self) -> list[str]:
45
+ """Create a summary of papers for review."""
46
+ summary = []
47
+ for paper_id, paper in list(self.fetched_papers.items())[:5]:
48
+ logger.info("Paper ID: %s", paper_id)
49
+ title = paper.get("Title", "N/A")
50
+ authors = ", ".join(
51
+ [author.split(" (ID: ")[0] for author in paper.get("Authors", [])[:2]]
52
+ )
53
+ if len(paper.get("Authors", [])) > 2:
54
+ authors += " et al."
55
+ summary.append(f"- {title} by {authors}")
56
+
57
+ if self.total_papers > 5:
58
+ summary.append(f"... and {self.total_papers - 5} more papers")
59
+ return summary
60
+
61
+ def _create_review_info(self) -> dict:
62
+ """Create the review information dictionary."""
63
+ return {
64
+ "action": "save_to_zotero",
65
+ "collection_path": self.collection_path,
66
+ "total_papers": self.total_papers,
67
+ "papers_preview": self.papers_preview,
68
+ "message": (
69
+ f"Would you like to save {self.total_papers} papers to Zotero "
70
+ f"collection '{self.collection_path}'? Please respond with a "
71
+ f"structured decision using one of the following options: 'approve', "
72
+ f"'reject', or 'custom' (with a custom_path)."
73
+ ),
74
+ }
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """
4
+ Utility for zotero write tool.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any
9
+
10
+ import hydra
11
+ from pyzotero import zotero
12
+
13
+ from .zotero_path import (
14
+ fetch_papers_for_save,
15
+ find_or_create_collection,
16
+ )
17
+
18
+ # Configure logging
19
+ logging.basicConfig(level=logging.INFO)
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class ZoteroWriteData:
24
+ """Helper class to organize Zotero write-related data."""
25
+
26
+ def __init__(
27
+ self,
28
+ tool_call_id: str,
29
+ collection_path: str,
30
+ state: dict,
31
+ ):
32
+ self.tool_call_id = tool_call_id
33
+ self.collection_path = collection_path
34
+ self.state = state
35
+ self.cfg = self._load_config()
36
+ self.zot = self._init_zotero_client()
37
+ self.fetched_papers = fetch_papers_for_save(state)
38
+ self.normalized_path = collection_path.rstrip("/").lower()
39
+ self.zotero_items = []
40
+ self.content = ""
41
+
42
+ def _load_config(self) -> Any:
43
+ """Load hydra configuration."""
44
+ with hydra.initialize(version_base=None, config_path="../../../configs"):
45
+ cfg = hydra.compose(config_name="config", overrides=["tools/zotero_write=default"])
46
+ logger.info("Loaded configuration for Zotero write tool")
47
+ return cfg.tools.zotero_write
48
+
49
+ def _init_zotero_client(self) -> zotero.Zotero:
50
+ """Initialize Zotero client."""
51
+ logger.info(
52
+ "Saving fetched papers to Zotero under collection path: %s",
53
+ self.collection_path,
54
+ )
55
+ return zotero.Zotero(self.cfg.user_id, self.cfg.library_type, self.cfg.api_key)
56
+
57
+ def _validate_papers(self) -> None:
58
+ """Validate that papers exist to save."""
59
+ if not self.fetched_papers:
60
+ raise ValueError(
61
+ "No fetched papers were found to save. "
62
+ "Please retrieve papers using Zotero Read or Semantic Scholar first."
63
+ )
64
+
65
+ def _find_collection(self) -> str:
66
+ """Find or create the target collection."""
67
+ matched_collection_key = find_or_create_collection(
68
+ self.zot, self.normalized_path, create_missing=False
69
+ )
70
+
71
+ if not matched_collection_key:
72
+ available_collections = self.zot.collections()
73
+ collection_names = [col["data"]["name"] for col in available_collections]
74
+ names_display = ", ".join(collection_names)
75
+
76
+ raise ValueError(
77
+ f"Error: The collection path '{self.collection_path}' does "
78
+ f"not exist in Zotero. "
79
+ f"Available collections are: {names_display}. "
80
+ f"Please try saving to one of these existing collections."
81
+ )
82
+
83
+ return matched_collection_key
84
+
85
+ def _format_papers_for_zotero(self, matched_collection_key: str) -> None:
86
+ """Format papers for Zotero and assign to the specified collection."""
87
+ for paper_id, paper in self.fetched_papers.items():
88
+ title = paper.get("Title", "N/A")
89
+ abstract = paper.get("Abstract", "N/A")
90
+ publication_date = paper.get("Publication Date", "N/A")
91
+ url = paper.get("URL", "N/A")
92
+ citations = paper.get("Citation Count", "N/A")
93
+ venue = paper.get("Venue", "N/A")
94
+ publication_venue = paper.get("Publication Venue", "N/A")
95
+ journal_name = paper.get("Journal Name", "N/A")
96
+ journal_volume = paper.get("Journal Volume", "N/A")
97
+ journal_pages = paper.get("Journal Pages", "N/A")
98
+
99
+ authors = [
100
+ (
101
+ {
102
+ "creatorType": "author",
103
+ "firstName": name.split(" ")[0],
104
+ "lastName": " ".join(name.split(" ")[1:]),
105
+ }
106
+ if " " in name
107
+ else {"creatorType": "author", "lastName": name}
108
+ )
109
+ for name in [author.split(" (ID: ")[0] for author in paper.get("Authors", [])]
110
+ ]
111
+
112
+ self.zotero_items.append(
113
+ {
114
+ "itemType": "journalArticle",
115
+ "title": title,
116
+ "abstractNote": abstract,
117
+ "date": publication_date,
118
+ "url": url,
119
+ "extra": f"Paper ID: {paper_id}\nCitations: {citations}",
120
+ "collections": [matched_collection_key],
121
+ "publicationTitle": (
122
+ publication_venue if publication_venue != "N/A" else venue
123
+ ),
124
+ "journalAbbreviation": journal_name,
125
+ "volume": journal_volume if journal_volume != "N/A" else None,
126
+ "pages": journal_pages if journal_pages != "N/A" else None,
127
+ "creators": authors,
128
+ }
129
+ )
130
+
131
+ def _save_to_zotero(self) -> None:
132
+ """Save items to Zotero."""
133
+ try:
134
+ response = self.zot.create_items(self.zotero_items)
135
+ logger.info("Papers successfully saved to Zotero: %s", response)
136
+ except Exception as e:
137
+ logger.error("Error saving to Zotero: %s", str(e))
138
+ raise RuntimeError(f"Error saving papers to Zotero: {str(e)}") from e
139
+
140
+ def _create_content(self, collection_name: str) -> None:
141
+ """Create the content message for the response."""
142
+ self.content = (
143
+ f"Save was successful. Papers have been saved to Zotero collection "
144
+ f"'{collection_name}' with the requested path '{self.get_collection_path()}'.\n"
145
+ )
146
+ self.content += "Summary of saved papers:\n"
147
+ self.content += f"Number of articles saved: {self.get_paper_count()}\n"
148
+ self.content += f"Query: {self.state.get('query', 'N/A')}\n"
149
+ top_papers = list(self.fetched_papers.values())[:2]
150
+ top_papers_info = "\n".join(
151
+ [
152
+ f"{i + 1}. {paper.get('Title', 'N/A')} ({paper.get('URL', 'N/A')})"
153
+ for i, paper in enumerate(top_papers)
154
+ ]
155
+ )
156
+ self.content += "Here are a few of these articles:\n" + top_papers_info
157
+
158
+ def process_write(self) -> dict[str, Any]:
159
+ """Process the write operation and return results."""
160
+ self._validate_papers()
161
+ matched_collection_key = self._find_collection()
162
+ self._format_papers_for_zotero(matched_collection_key)
163
+ self._save_to_zotero()
164
+
165
+ # Get collection name for feedback
166
+ collections = self.zot.collections()
167
+ collection_name = ""
168
+ for col in collections:
169
+ if col["key"] == matched_collection_key:
170
+ collection_name = col["data"]["name"]
171
+ break
172
+
173
+ self._create_content(collection_name)
174
+
175
+ return {
176
+ "content": self.content,
177
+ "fetched_papers": self.fetched_papers,
178
+ }
179
+
180
+ def get_paper_count(self) -> int:
181
+ """Get the number of papers to be saved.
182
+
183
+ Returns:
184
+ int: The number of papers in the fetched papers dictionary.
185
+ """
186
+ return len(self.fetched_papers)
187
+
188
+ def get_collection_path(self) -> str:
189
+ """Get the normalized collection path.
190
+
191
+ Returns:
192
+ str: The normalized collection path where papers will be saved.
193
+ """
194
+ return self.collection_path