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
@@ -26,9 +26,8 @@ Notes
26
26
  first identifier is returned; otherwise a list is returned from all rows that have values.
27
27
  """
28
28
 
29
-
30
29
  import logging
31
- from typing import Annotated, Optional, Any
30
+ from typing import Annotated, Any
32
31
 
33
32
  import pandas as pd
34
33
  from langchain_core.messages import ToolMessage
@@ -99,7 +98,7 @@ class QueryDataFrameInput(BaseModel):
99
98
  "extract when extract_ids=True."
100
99
  ),
101
100
  )
102
- row_number: Optional[int] = Field(
101
+ row_number: int | None = Field(
103
102
  default=None,
104
103
  description=(
105
104
  "1-based index of the ID to extract from the list; if provided, returns only"
@@ -180,9 +179,7 @@ def query_dataframe(
180
179
  context_val = state.get("last_displayed_papers")
181
180
  if not context_val:
182
181
  logger.info("No papers displayed so far, raising NoPapersFoundError")
183
- raise NoPapersFoundError(
184
- "No papers found. A search needs to be performed first."
185
- )
182
+ raise NoPapersFoundError("No papers found. A search needs to be performed first.")
186
183
 
187
184
  # Resolve the paper dictionary
188
185
  if isinstance(context_val, dict):
@@ -205,14 +202,10 @@ def query_dataframe(
205
202
  if not id_column:
206
203
  raise ValueError("Must specify 'id_column' when extract_ids=True.")
207
204
  if row_number is not None:
208
- question_to_agent = (
209
- f"df['{id_column}'].dropna().str[0].tolist()[{row_number-1}]"
210
- )
205
+ question_to_agent = f"df['{id_column}'].dropna().str[0].tolist()[{row_number - 1}]"
211
206
  else:
212
207
  question_to_agent = f"df['{id_column}'].dropna().str[0].tolist()"
213
- logger.info(
214
- "extract_ids enabled: asking agent to run expression: %s", question_to_agent
215
- )
208
+ logger.info("extract_ids enabled: asking agent to run expression: %s", question_to_agent)
216
209
 
217
210
  df_agent = create_pandas_dataframe_agent(
218
211
  llm_model,
@@ -10,6 +10,7 @@ Configuration is loaded via Hydra and the top ranked result is returned.
10
10
 
11
11
  import logging
12
12
  from typing import Annotated, Any
13
+
13
14
  import hydra
14
15
  import requests
15
16
  from langchain_core.messages import ToolMessage
@@ -18,7 +19,6 @@ from langchain_core.tools.base import InjectedToolCallId
18
19
  from langgraph.types import Command
19
20
  from pydantic import BaseModel, Field
20
21
 
21
-
22
22
  # Configure logging
23
23
  logging.basicConfig(level=logging.INFO)
24
24
  logger = logging.getLogger(__name__)
@@ -36,9 +36,7 @@ class RetrieveSemanticScholarPaperIdInput(BaseModel):
36
36
  Runtime-injected identifier for tracing the tool invocation.
37
37
  """
38
38
 
39
- paper_title: str = Field(
40
- ..., description="The paper title to search for on Semantic Scholar."
41
- )
39
+ paper_title: str = Field(..., description="The paper title to search for on Semantic Scholar.")
42
40
  tool_call_id: Annotated[str, InjectedToolCallId]
43
41
 
44
42
 
@@ -8,12 +8,14 @@ optionally filtered by publication year.
8
8
  """
9
9
 
10
10
  import logging
11
- from typing import Annotated, Any, Optional
11
+ from typing import Annotated, Any
12
+
12
13
  from langchain_core.messages import ToolMessage
13
14
  from langchain_core.tools import tool
14
15
  from langchain_core.tools.base import InjectedToolCallId
15
16
  from langgraph.types import Command
16
17
  from pydantic import BaseModel, Field
18
+
17
19
  from .utils.search_helper import SearchData
18
20
 
19
21
  # Configure logging
@@ -32,16 +34,14 @@ class SearchInput(BaseModel):
32
34
  tool_call_id: Internal tool call identifier injected by the system.
33
35
  """
34
36
 
35
- query: str = Field(
36
- description="Full or partial paper title or keywords to search for"
37
- )
37
+ query: str = Field(description="Full or partial paper title or keywords to search for")
38
38
  limit: int = Field(
39
39
  default=10,
40
40
  description="Maximum number of search results to return (1-100)",
41
41
  ge=1,
42
42
  le=100,
43
43
  )
44
- year: Optional[str] = Field(
44
+ year: str | None = Field(
45
45
  default=None,
46
46
  description="Publication year filter; supports formats:"
47
47
  "'YYYY', 'YYYY-', '-YYYY', 'YYYY:YYYY'",
@@ -58,7 +58,7 @@ def search_tool(
58
58
  query: str,
59
59
  tool_call_id: Annotated[str, InjectedToolCallId],
60
60
  limit: int = 10,
61
- year: Optional[str] = None,
61
+ year: str | None = None,
62
62
  ) -> Command[Any]:
63
63
  """
64
64
  Return academic papers from Semantic Scholar matching a title or keyword query.
@@ -8,12 +8,14 @@ Given a Semantic Scholar paper ID, this tool retrieves related works
8
8
  """
9
9
 
10
10
  import logging
11
- from typing import Annotated, Any, Optional
11
+ from typing import Annotated, Any
12
+
12
13
  from langchain_core.messages import ToolMessage
13
14
  from langchain_core.tools import tool
14
15
  from langchain_core.tools.base import InjectedToolCallId
15
16
  from langgraph.types import Command
16
17
  from pydantic import BaseModel, Field
18
+
17
19
  from .utils.single_helper import SinglePaperRecData
18
20
 
19
21
  # Configure logging
@@ -40,7 +42,7 @@ class SinglePaperRecInput(BaseModel):
40
42
  ge=1,
41
43
  le=500,
42
44
  )
43
- year: Optional[str] = Field(
45
+ year: str | None = Field(
44
46
  default=None,
45
47
  description="Publication year filter; supports formats::"
46
48
  "'YYYY', 'YYYY-', '-YYYY', 'YYYY:YYYY'",
@@ -57,7 +59,7 @@ def get_single_paper_recommendations(
57
59
  paper_id: str,
58
60
  tool_call_id: Annotated[str, InjectedToolCallId],
59
61
  limit: int = 10,
60
- year: Optional[str] = None,
62
+ year: str | None = None,
61
63
  ) -> Command[Any]:
62
64
  """
63
65
  Recommend related research papers using the Semantic Scholar API for a single paper ID.
@@ -1,7 +1,5 @@
1
1
  """This module contains utility functions for the Semantic Scholar search tool."""
2
2
 
3
- from . import search_helper
4
- from . import single_helper
5
- from . import multi_helper
3
+ from . import multi_helper, search_helper, single_helper
6
4
 
7
5
  __all__ = ["search_helper", "single_helper", "multi_helper"]
@@ -6,11 +6,11 @@ Utility for fetching recommendations based on multiple papers.
6
6
 
7
7
  import json
8
8
  import logging
9
- from typing import Any, List, Optional, Dict
9
+ from typing import Any
10
+
10
11
  import hydra
11
12
  import requests
12
13
 
13
-
14
14
  # Configure logging
15
15
  logging.basicConfig(level=logging.INFO)
16
16
  logger = logging.getLogger(__name__)
@@ -21,9 +21,9 @@ class MultiPaperRecData:
21
21
 
22
22
  def __init__(
23
23
  self,
24
- paper_ids: List[str],
24
+ paper_ids: list[str],
25
25
  limit: int,
26
- year: Optional[str],
26
+ year: str | None,
27
27
  tool_call_id: str,
28
28
  ):
29
29
  self.paper_ids = paper_ids
@@ -51,7 +51,7 @@ class MultiPaperRecData:
51
51
  logger.info("Loaded configuration for multi-paper recommendation tool")
52
52
  return cfg.tools.multi_paper_recommendation
53
53
 
54
- def _create_params(self) -> Dict[str, Any]:
54
+ def _create_params(self) -> dict[str, Any]:
55
55
  """Create parameters for the API request."""
56
56
  params = {
57
57
  "limit": min(self.limit, 500),
@@ -94,9 +94,7 @@ class MultiPaperRecData:
94
94
  ) from e
95
95
 
96
96
  if self.response is None:
97
- raise RuntimeError(
98
- "Failed to obtain a response from the Semantic Scholar API."
99
- )
97
+ raise RuntimeError("Failed to obtain a response from the Semantic Scholar API.")
100
98
 
101
99
  logger.info(
102
100
  "API Response Status for multi-paper recommendations: %s",
@@ -117,9 +115,7 @@ class MultiPaperRecData:
117
115
 
118
116
  self.recommendations = self.data.get("recommendedPapers", [])
119
117
  if not self.recommendations:
120
- logger.error(
121
- "No recommendations returned from API for paper IDs: %s", self.paper_ids
122
- )
118
+ logger.error("No recommendations returned from API for paper IDs: %s", self.paper_ids)
123
119
  raise RuntimeError(
124
120
  "No recommendations were found for your query. Consider refining your search "
125
121
  "by using more specific keywords or different terms."
@@ -128,12 +124,12 @@ class MultiPaperRecData:
128
124
  def _filter_papers(self) -> None:
129
125
  """Filter and format papers."""
130
126
  # Build filtered recommendations with unified paper_ids
131
- filtered: Dict[str, Any] = {}
127
+ filtered: dict[str, Any] = {}
132
128
  for paper in self.recommendations:
133
129
  if not paper.get("title") or not paper.get("authors"):
134
130
  continue
135
131
  ext = paper.get("externalIds", {}) or {}
136
- ids: List[str] = []
132
+ ids: list[str] = []
137
133
  arxiv = ext.get("ArXiv")
138
134
  if arxiv:
139
135
  ids.append(f"arxiv:{arxiv}")
@@ -191,7 +187,7 @@ class MultiPaperRecData:
191
187
  title = paper.get("Title", "N/A")
192
188
  year = paper.get("Year", "N/A")
193
189
  snippet = self._get_snippet(paper.get("Abstract", ""))
194
- entry = f"{i+1}. {title} ({year})"
190
+ entry = f"{i + 1}. {title} ({year})"
195
191
  if snippet:
196
192
  entry += f"\n Abstract snippet: {snippet}"
197
193
  entries.append(entry)
@@ -202,14 +198,12 @@ class MultiPaperRecData:
202
198
  "Papers are attached as an artifact."
203
199
  )
204
200
  self.content += " Here is a summary of the recommendations:\n"
205
- self.content += (
206
- f"Number of recommended papers found: {self.get_paper_count()}\n"
207
- )
201
+ self.content += f"Number of recommended papers found: {self.get_paper_count()}\n"
208
202
  self.content += f"Query Paper IDs: {', '.join(self.paper_ids)}\n"
209
203
  self.content += f"Year: {self.year}\n" if self.year else ""
210
204
  self.content += "Here are a few of these papers:\n" + top_papers_info
211
205
 
212
- def process_recommendations(self) -> Dict[str, Any]:
206
+ def process_recommendations(self) -> dict[str, Any]:
213
207
  """Process the recommendations request and return results."""
214
208
  self._fetch_recommendations()
215
209
  self._filter_papers()
@@ -5,7 +5,8 @@ Utility for fetching recommendations based on a single paper.
5
5
  """
6
6
 
7
7
  import logging
8
- from typing import Any, Optional, Dict
8
+ from typing import Any
9
+
9
10
  import hydra
10
11
  import requests
11
12
 
@@ -21,7 +22,7 @@ class SearchData:
21
22
  self,
22
23
  query: str,
23
24
  limit: int,
24
- year: Optional[str],
25
+ year: str | None,
25
26
  tool_call_id: str,
26
27
  ):
27
28
  self.query = query
@@ -40,13 +41,11 @@ class SearchData:
40
41
  def _load_config(self) -> Any:
41
42
  """Load hydra configuration."""
42
43
  with hydra.initialize(version_base=None, config_path="../../../configs"):
43
- cfg = hydra.compose(
44
- config_name="config", overrides=["tools/search=default"]
45
- )
44
+ cfg = hydra.compose(config_name="config", overrides=["tools/search=default"])
46
45
  logger.info("Loaded configuration for search tool")
47
46
  return cfg.tools.search
48
47
 
49
- def _create_params(self) -> Dict[str, Any]:
48
+ def _create_params(self) -> dict[str, Any]:
50
49
  """Create parameters for the API request."""
51
50
  params = {
52
51
  "query": self.query,
@@ -64,9 +63,7 @@ class SearchData:
64
63
  # Wrap API call in try/except to catch connectivity issues
65
64
  for attempt in range(10):
66
65
  try:
67
- self.response = requests.get(
68
- self.endpoint, params=self.params, timeout=10
69
- )
66
+ self.response = requests.get(self.endpoint, params=self.params, timeout=10)
70
67
  self.response.raise_for_status() # Raises HTTPError for bad responses
71
68
  break # Exit loop if request is successful
72
69
  except requests.exceptions.RequestException as e:
@@ -82,9 +79,7 @@ class SearchData:
82
79
  ) from e
83
80
 
84
81
  if self.response is None:
85
- raise RuntimeError(
86
- "Failed to obtain a response from the Semantic Scholar API."
87
- )
82
+ raise RuntimeError("Failed to obtain a response from the Semantic Scholar API.")
88
83
 
89
84
  self.data = self.response.json()
90
85
 
@@ -99,9 +94,7 @@ class SearchData:
99
94
 
100
95
  self.papers = self.data.get("data", [])
101
96
  if not self.papers:
102
- logger.error(
103
- "No papers returned from Semantic Scholar API for query: %s", self.query
104
- )
97
+ logger.error("No papers returned from Semantic Scholar API for query: %s", self.query)
105
98
  raise RuntimeError(
106
99
  "No papers were found for your query. Consider refining your search "
107
100
  "by using more specific keywords or different terms."
@@ -110,7 +103,7 @@ class SearchData:
110
103
  def _filter_papers(self) -> None:
111
104
  """Filter and format papers."""
112
105
  # Build filtered papers mapping with unified paper_ids list
113
- filtered: Dict[str, Any] = {}
106
+ filtered: dict[str, Any] = {}
114
107
  for paper in self.papers:
115
108
  if not paper.get("title") or not paper.get("authors"):
116
109
  continue
@@ -175,7 +168,7 @@ class SearchData:
175
168
  title = paper.get("Title", "N/A")
176
169
  year = paper.get("Year", "N/A")
177
170
  snippet = self._get_snippet(paper.get("Abstract", ""))
178
- entry = f"{i+1}. {title} ({year})"
171
+ entry = f"{i + 1}. {title} ({year})"
179
172
  if snippet:
180
173
  entry += f"\n Abstract snippet: {snippet}"
181
174
  entries.append(entry)
@@ -192,7 +185,7 @@ class SearchData:
192
185
  self.content += f"Year: {self.year}\n" if self.year else ""
193
186
  self.content += "Top 3 papers:\n" + top_papers_info
194
187
 
195
- def process_search(self) -> Dict[str, Any]:
188
+ def process_search(self) -> dict[str, Any]:
196
189
  """Process the search request and return results."""
197
190
  self._fetch_papers()
198
191
  self._filter_papers()
@@ -5,7 +5,8 @@ Utility for fetching recommendations based on a single paper.
5
5
  """
6
6
 
7
7
  import logging
8
- from typing import Any, Optional, Dict, List
8
+ from typing import Any
9
+
9
10
  import hydra
10
11
  import requests
11
12
 
@@ -21,7 +22,7 @@ class SinglePaperRecData:
21
22
  self,
22
23
  paper_id: str,
23
24
  limit: int,
24
- year: Optional[str],
25
+ year: str | None,
25
26
  tool_call_id: str,
26
27
  ):
27
28
  self.paper_id = paper_id
@@ -47,7 +48,7 @@ class SinglePaperRecData:
47
48
  logger.info("Loaded configuration for single paper recommendation tool")
48
49
  return cfg.tools.single_paper_recommendation
49
50
 
50
- def _create_params(self) -> Dict[str, Any]:
51
+ def _create_params(self) -> dict[str, Any]:
51
52
  """Create parameters for the API request."""
52
53
  params = {
53
54
  "limit": min(self.limit, 500), # Max 500 per API docs
@@ -86,9 +87,7 @@ class SinglePaperRecData:
86
87
  ) from e
87
88
 
88
89
  if self.response is None:
89
- raise RuntimeError(
90
- "Failed to obtain a response from the Semantic Scholar API."
91
- )
90
+ raise RuntimeError("Failed to obtain a response from the Semantic Scholar API.")
92
91
 
93
92
  logger.info(
94
93
  "API Response Status for recommendations of paper %s: %s",
@@ -110,9 +109,7 @@ class SinglePaperRecData:
110
109
 
111
110
  self.recommendations = self.data.get("recommendedPapers", [])
112
111
  if not self.recommendations:
113
- logger.error(
114
- "No recommendations returned from API for paper: %s", self.paper_id
115
- )
112
+ logger.error("No recommendations returned from API for paper: %s", self.paper_id)
116
113
  raise RuntimeError(
117
114
  "No recommendations were found for your query. Consider refining your search "
118
115
  "by using more specific keywords or different terms."
@@ -121,12 +118,12 @@ class SinglePaperRecData:
121
118
  def _filter_papers(self) -> None:
122
119
  """Filter and format papers."""
123
120
  # Build filtered recommendations with unified paper_ids
124
- filtered: Dict[str, Any] = {}
121
+ filtered: dict[str, Any] = {}
125
122
  for paper in self.recommendations:
126
123
  if not paper.get("title") or not paper.get("authors"):
127
124
  continue
128
125
  ext = paper.get("externalIds", {}) or {}
129
- ids: List[str] = []
126
+ ids: list[str] = []
130
127
  arxiv = ext.get("ArXiv")
131
128
  if arxiv:
132
129
  ids.append(f"arxiv:{arxiv}")
@@ -184,7 +181,7 @@ class SinglePaperRecData:
184
181
  title = paper.get("Title", "N/A")
185
182
  year = paper.get("Year", "N/A")
186
183
  snippet = self._get_snippet(paper.get("Abstract", ""))
187
- entry = f"{i+1}. {title} ({year})"
184
+ entry = f"{i + 1}. {title} ({year})"
188
185
  if snippet:
189
186
  entry += f"\n Abstract snippet: {snippet}"
190
187
  entries.append(entry)
@@ -195,13 +192,11 @@ class SinglePaperRecData:
195
192
  "Papers are attached as an artifact. "
196
193
  "Here is a summary of the recommendations:\n"
197
194
  )
198
- self.content += (
199
- f"Number of recommended papers found: {self.get_paper_count()}\n"
200
- )
195
+ self.content += f"Number of recommended papers found: {self.get_paper_count()}\n"
201
196
  self.content += f"Query Paper ID: {self.paper_id}\n"
202
197
  self.content += "Here are a few of these papers:\n" + top_papers_info
203
198
 
204
- def process_recommendations(self) -> Dict[str, Any]:
199
+ def process_recommendations(self) -> dict[str, Any]:
205
200
  """Process the recommendations request and return results."""
206
201
  self._fetch_recommendations()
207
202
  self._filter_papers()
@@ -2,9 +2,6 @@
2
2
  Import statements
3
3
  """
4
4
 
5
- from . import zotero_read
6
- from . import zotero_write
7
- from . import utils
8
- from . import zotero_review
5
+ from . import utils, zotero_read, zotero_review, zotero_write
9
6
 
10
7
  __all__ = ["zotero_read", "zotero_write", "utils", "zotero_review"]
@@ -2,9 +2,6 @@
2
2
  Import statements
3
3
  """
4
4
 
5
- from . import zotero_path
6
- from . import read_helper
7
- from . import write_helper
8
- from . import review_helper
5
+ from . import read_helper, review_helper, write_helper, zotero_path
9
6
 
10
7
  __all__ = ["zotero_path", "read_helper", "write_helper", "review_helper"]
@@ -5,7 +5,7 @@ Utility for zotero read tool.
5
5
  """
6
6
 
7
7
  import logging
8
- from typing import Any, Dict, List
8
+ from typing import Any
9
9
 
10
10
  import hydra
11
11
  import requests
@@ -51,7 +51,7 @@ class ZoteroSearchData:
51
51
  self._filter_and_format_papers(items)
52
52
  self._create_content()
53
53
 
54
- def get_search_results(self) -> Dict[str, Any]:
54
+ def get_search_results(self) -> dict[str, Any]:
55
55
  """Get the search results and content."""
56
56
  return {
57
57
  "article_data": self.article_data,
@@ -61,9 +61,7 @@ class ZoteroSearchData:
61
61
  def _load_config(self) -> Any:
62
62
  """Load hydra configuration."""
63
63
  with hydra.initialize(version_base=None, config_path="../../../configs"):
64
- cfg = hydra.compose(
65
- config_name="config", overrides=["tools/zotero_read=default"]
66
- )
64
+ cfg = hydra.compose(config_name="config", overrides=["tools/zotero_read=default"])
67
65
  logger.info("Loaded configuration for Zotero search tool")
68
66
  return cfg.tools.zotero_read
69
67
 
@@ -77,7 +75,7 @@ class ZoteroSearchData:
77
75
  )
78
76
  return zotero.Zotero(self.cfg.user_id, self.cfg.library_type, self.cfg.api_key)
79
77
 
80
- def _fetch_items(self) -> List[Dict[str, Any]]:
78
+ def _fetch_items(self) -> list[dict[str, Any]]:
81
79
  """Fetch items from Zotero."""
82
80
  try:
83
81
  if self.query.strip() == "":
@@ -100,15 +98,13 @@ class ZoteroSearchData:
100
98
 
101
99
  if not items:
102
100
  logger.error("No items returned from Zotero for query: '%s'", self.query)
103
- raise RuntimeError(
104
- "No items returned from Zotero. Please retry the same query."
105
- )
101
+ raise RuntimeError("No items returned from Zotero. Please retry the same query.")
106
102
 
107
103
  return items
108
104
 
109
- def _collect_item_attachments(self) -> Dict[str, str]:
105
+ def _collect_item_attachments(self) -> dict[str, str]:
110
106
  """Collect PDF attachment keys for non-orphan items."""
111
- item_attachments: Dict[str, str] = {}
107
+ item_attachments: dict[str, str] = {}
112
108
  for item_key, item_data in self.article_data.items():
113
109
  if item_data.get("Type") == "orphan_attachment":
114
110
  continue
@@ -127,7 +123,7 @@ class ZoteroSearchData:
127
123
  logger.error("Failed to get attachments for item %s: %s", item_key, e)
128
124
  return item_attachments
129
125
 
130
- def _process_orphaned_pdfs(self, orphaned_pdfs: Dict[str, str]) -> None:
126
+ def _process_orphaned_pdfs(self, orphaned_pdfs: dict[str, str]) -> None:
131
127
  """Download or record orphaned PDF attachments."""
132
128
  if self.download_pdfs:
133
129
  logger.info("Downloading %d orphaned PDFs in parallel", len(orphaned_pdfs))
@@ -147,16 +143,14 @@ class ZoteroSearchData:
147
143
  logger.info("Skipping orphaned PDF downloads (download_pdfs=False)")
148
144
  for attachment_key in orphaned_pdfs:
149
145
  self.article_data[attachment_key]["attachment_key"] = attachment_key
150
- self.article_data[attachment_key]["filename"] = (
151
- self.article_data[attachment_key].get("Title", attachment_key)
152
- )
146
+ self.article_data[attachment_key]["filename"] = self.article_data[
147
+ attachment_key
148
+ ].get("Title", attachment_key)
153
149
 
154
- def _process_item_pdfs(self, item_attachments: Dict[str, str]) -> None:
150
+ def _process_item_pdfs(self, item_attachments: dict[str, str]) -> None:
155
151
  """Download or record regular item PDF attachments."""
156
152
  if self.download_pdfs:
157
- logger.info(
158
- "Downloading %d regular item PDFs in parallel", len(item_attachments)
159
- )
153
+ logger.info("Downloading %d regular item PDFs in parallel", len(item_attachments))
160
154
  results = download_pdfs_in_parallel(
161
155
  self.session,
162
156
  self.cfg.user_id,
@@ -175,15 +169,13 @@ class ZoteroSearchData:
175
169
  self.article_data[item_key]["attachment_key"] = attachment_key
176
170
  logger.info("Downloaded Zotero PDF to: %s", file_path)
177
171
 
178
- def _filter_and_format_papers(self, items: List[Dict[str, Any]]) -> None:
172
+ def _filter_and_format_papers(self, items: list[dict[str, Any]]) -> None:
179
173
  """Filter and format papers from Zotero items, including standalone PDFs."""
180
- filter_item_types = (
181
- self.cfg.zotero.filter_item_types if self.only_articles else []
182
- )
174
+ filter_item_types = self.cfg.zotero.filter_item_types if self.only_articles else []
183
175
  logger.debug("Filtering item types: %s", filter_item_types)
184
176
 
185
177
  # Maps to track attachments for batch processing
186
- orphaned_pdfs: Dict[str, str] = {} # attachment_key -> item key (same for orphans)
178
+ orphaned_pdfs: dict[str, str] = {} # attachment_key -> item key (same for orphans)
187
179
 
188
180
  # First pass: process all items without downloading PDFs
189
181
  for item in items:
@@ -214,17 +206,14 @@ class ZoteroSearchData:
214
206
  "Authors": [
215
207
  f"{creator.get('firstName', '')} {creator.get('lastName', '')}".strip()
216
208
  for creator in data.get("creators", [])
217
- if isinstance(creator, dict)
218
- and creator.get("creatorType") == "author"
209
+ if isinstance(creator, dict) and creator.get("creatorType") == "author"
219
210
  ],
220
211
  "source": "zotero",
221
212
  }
222
213
  # We'll collect attachment info in second pass
223
214
 
224
215
  # CASE 2: Standalone orphaned PDF attachment
225
- elif data.get("contentType") == "application/pdf" and not data.get(
226
- "parentItem"
227
- ):
216
+ elif data.get("contentType") == "application/pdf" and not data.get("parentItem"):
228
217
  attachment_key = key
229
218
  filename = data.get("filename", "unknown.pdf")
230
219
 
@@ -260,25 +249,18 @@ class ZoteroSearchData:
260
249
 
261
250
  # Ensure we have some results
262
251
  if not self.article_data:
263
- logger.error(
264
- "No matching papers returned from Zotero for query: '%s'", self.query
265
- )
252
+ logger.error("No matching papers returned from Zotero for query: '%s'", self.query)
266
253
  raise RuntimeError(
267
254
  "No matching papers returned from Zotero. Please retry the same query."
268
255
  )
269
256
 
270
- logger.info(
271
- "Filtered %d items (including orphaned attachments)", len(self.article_data)
272
- )
257
+ logger.info("Filtered %d items (including orphaned attachments)", len(self.article_data))
273
258
 
274
259
  def _create_content(self) -> None:
275
260
  """Create the content message for the response."""
276
261
  top_papers = list(self.article_data.values())[:2]
277
262
  top_papers_info = "\n".join(
278
- [
279
- f"{i+1}. {paper['Title']} ({paper['Type']})"
280
- for i, paper in enumerate(top_papers)
281
- ]
263
+ [f"{i + 1}. {paper['Title']} ({paper['Type']})" for i, paper in enumerate(top_papers)]
282
264
  )
283
265
 
284
266
  self.content = "Retrieval was successful. Papers are attached as an artifact."
@@ -5,7 +5,6 @@ Utility for reviewing papers and saving them to Zotero.
5
5
  """
6
6
 
7
7
  import logging
8
- from typing import List
9
8
 
10
9
  # Configure logging
11
10
  logging.basicConfig(level=logging.INFO)
@@ -40,12 +39,9 @@ class ReviewData:
40
39
 
41
40
  def get_custom_path_approval_message(self, custom_path: str) -> str:
42
41
  """Get the formatted approval message for a custom collection path."""
43
- return (
44
- f"Human approved saving papers to custom Zotero "
45
- f"collection '{custom_path}'."
46
- )
42
+ return f"Human approved saving papers to custom Zotero collection '{custom_path}'."
47
43
 
48
- def _create_papers_summary(self) -> List[str]:
44
+ def _create_papers_summary(self) -> list[str]:
49
45
  """Create a summary of papers for review."""
50
46
  summary = []
51
47
  for paper_id, paper in list(self.fetched_papers.items())[:5]: