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