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