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,180 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Utility functions for Zotero path operations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
# Configure logging
|
|
10
|
+
logging.basicConfig(level=logging.INFO)
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
# pylint: disable=broad-exception-caught
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_item_collections(zot):
|
|
16
|
+
"""
|
|
17
|
+
Fetch all Zotero collections and map item keys to their full collection paths.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
zot (Zotero): An initialized Zotero client.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
dict: A dictionary mapping item keys to a list of full collection paths.
|
|
24
|
+
"""
|
|
25
|
+
logger.info("Fetching Zotero collections...")
|
|
26
|
+
|
|
27
|
+
# Fetch all collections
|
|
28
|
+
collections = zot.collections()
|
|
29
|
+
|
|
30
|
+
# Create mappings: collection key → name and collection key → parent key
|
|
31
|
+
collection_map = {col["key"]: col["data"]["name"] for col in collections}
|
|
32
|
+
parent_map = {col["key"]: col["data"].get("parentCollection") for col in collections}
|
|
33
|
+
|
|
34
|
+
# Build full paths for collections
|
|
35
|
+
def build_collection_path(col_key):
|
|
36
|
+
"""build collection path from collection key"""
|
|
37
|
+
path = []
|
|
38
|
+
while col_key:
|
|
39
|
+
path.insert(0, collection_map.get(col_key, "Unknown"))
|
|
40
|
+
col_key = parent_map.get(col_key)
|
|
41
|
+
return "/" + "/".join(path) # Convert to "/path/to/collection"
|
|
42
|
+
|
|
43
|
+
collection_paths = {key: build_collection_path(key) for key in collection_map}
|
|
44
|
+
|
|
45
|
+
# Manually create an item-to-collection mapping with full paths
|
|
46
|
+
item_to_collections = {}
|
|
47
|
+
|
|
48
|
+
for collection in collections:
|
|
49
|
+
collection_key = collection["key"]
|
|
50
|
+
collection_items = zot.collection_items(collection_key) # Fetch items in the collection
|
|
51
|
+
|
|
52
|
+
for item in collection_items:
|
|
53
|
+
item_key = item["data"]["key"]
|
|
54
|
+
if item_key in item_to_collections:
|
|
55
|
+
item_to_collections[item_key].append(collection_paths[collection_key])
|
|
56
|
+
else:
|
|
57
|
+
item_to_collections[item_key] = [collection_paths[collection_key]]
|
|
58
|
+
|
|
59
|
+
logger.info("Successfully mapped items to collection paths.")
|
|
60
|
+
|
|
61
|
+
return item_to_collections
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def find_or_create_collection(zot, path, create_missing=False):
|
|
65
|
+
"""find collection or create if missing"""
|
|
66
|
+
logger.info("Finding collection for path: %s (create_missing=%s)", path, create_missing)
|
|
67
|
+
# Normalize path: remove leading/trailing slashes and convert to lowercase
|
|
68
|
+
normalized = path.strip("/").lower()
|
|
69
|
+
path_parts = normalized.split("/") if normalized else []
|
|
70
|
+
|
|
71
|
+
if not path_parts:
|
|
72
|
+
logger.warning("Empty path provided")
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
# Get all collections from Zotero
|
|
76
|
+
all_collections = zot.collections()
|
|
77
|
+
logger.info("Found %d collections in Zotero", len(all_collections))
|
|
78
|
+
|
|
79
|
+
# Determine target name (last part) and, if nested, find the parent's key
|
|
80
|
+
target_name = path_parts[-1]
|
|
81
|
+
parent_key = None
|
|
82
|
+
if len(path_parts) > 1:
|
|
83
|
+
parent_name = path_parts[-2]
|
|
84
|
+
# Look for a collection with name matching the parent (case-insensitive)
|
|
85
|
+
for col in all_collections:
|
|
86
|
+
if col["data"]["name"].lower() == parent_name:
|
|
87
|
+
parent_key = col["key"]
|
|
88
|
+
break
|
|
89
|
+
|
|
90
|
+
# Try to find an existing collection by direct match (ignoring hierarchy)
|
|
91
|
+
for col in all_collections:
|
|
92
|
+
if col["data"]["name"].lower() == target_name:
|
|
93
|
+
logger.info("Found direct match for %s: %s", target_name, col["key"])
|
|
94
|
+
return col["key"]
|
|
95
|
+
|
|
96
|
+
# No match found: create one if allowed
|
|
97
|
+
if create_missing:
|
|
98
|
+
payload = {"name": target_name}
|
|
99
|
+
if parent_key:
|
|
100
|
+
payload["parentCollection"] = parent_key
|
|
101
|
+
try:
|
|
102
|
+
result = zot.create_collection(payload)
|
|
103
|
+
# Interpret result based on structure
|
|
104
|
+
if "success" in result:
|
|
105
|
+
new_key = result["success"]["0"]
|
|
106
|
+
else:
|
|
107
|
+
new_key = result["successful"]["0"]["data"]["key"]
|
|
108
|
+
logger.info("Created collection %s with key %s", target_name, new_key)
|
|
109
|
+
return new_key
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logger.error("Failed to create collection: %s", e)
|
|
112
|
+
return None
|
|
113
|
+
else:
|
|
114
|
+
logger.warning("No matching collection found for %s", target_name)
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_all_collection_paths(zot):
|
|
119
|
+
"""
|
|
120
|
+
Get all available collection paths in Zotero.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
zot (Zotero): An initialized Zotero client.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
list: List of all available collection paths
|
|
127
|
+
"""
|
|
128
|
+
logger.info("Getting all collection paths")
|
|
129
|
+
collections = zot.collections()
|
|
130
|
+
|
|
131
|
+
# Create mappings: collection key → name and collection key → parent key
|
|
132
|
+
collection_map = {col["key"]: col["data"]["name"] for col in collections}
|
|
133
|
+
parent_map = {col["key"]: col["data"].get("parentCollection") for col in collections}
|
|
134
|
+
|
|
135
|
+
# Build full paths for collections
|
|
136
|
+
def build_collection_path(col_key):
|
|
137
|
+
path = []
|
|
138
|
+
while col_key:
|
|
139
|
+
path.insert(0, collection_map.get(col_key, "Unknown"))
|
|
140
|
+
col_key = parent_map.get(col_key)
|
|
141
|
+
return "/" + "/".join(path)
|
|
142
|
+
|
|
143
|
+
collection_paths = [build_collection_path(key) for key in collection_map]
|
|
144
|
+
logger.info("Found %d collection paths", len(collection_paths))
|
|
145
|
+
return collection_paths
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def fetch_papers_for_save(state):
|
|
149
|
+
"""
|
|
150
|
+
Retrieve papers from the state for saving to Zotero.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
state (dict): The state containing previously fetched papers.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
dict: Dictionary of papers to save, or None if no papers found
|
|
157
|
+
"""
|
|
158
|
+
logger.info("Fetching papers from state for saving")
|
|
159
|
+
|
|
160
|
+
# Retrieve last displayed papers from the agent state
|
|
161
|
+
last_displayed_key = state.get("last_displayed_papers", "")
|
|
162
|
+
|
|
163
|
+
if not last_displayed_key:
|
|
164
|
+
logger.warning("No last_displayed_papers key in state")
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
if isinstance(last_displayed_key, str):
|
|
168
|
+
# If it's a string (key to another state object), get that object
|
|
169
|
+
fetched_papers = state.get(last_displayed_key, {})
|
|
170
|
+
logger.info("Using papers from '%s' state key", last_displayed_key)
|
|
171
|
+
else:
|
|
172
|
+
# If it's already the papers object
|
|
173
|
+
fetched_papers = last_displayed_key
|
|
174
|
+
logger.info("Using papers directly from last_displayed_papers")
|
|
175
|
+
|
|
176
|
+
if not fetched_papers:
|
|
177
|
+
logger.warning("No fetched papers found to save.")
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
return fetched_papers
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Utility functions for downloading PDFs from Zotero.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import concurrent.futures
|
|
7
|
+
import logging
|
|
8
|
+
import tempfile
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def download_zotero_pdf(
|
|
16
|
+
session: requests.Session,
|
|
17
|
+
user_id: str,
|
|
18
|
+
api_key: str,
|
|
19
|
+
attachment_key: str,
|
|
20
|
+
**kwargs,
|
|
21
|
+
) -> tuple[str, str] | None:
|
|
22
|
+
"""
|
|
23
|
+
Download a PDF from Zotero by attachment key.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
session: requests.Session for HTTP requests.
|
|
27
|
+
user_id: Zotero user ID.
|
|
28
|
+
api_key: Zotero API key.
|
|
29
|
+
attachment_key: Zotero attachment item key.
|
|
30
|
+
kwargs:
|
|
31
|
+
timeout (int): Request timeout in seconds (default: 10).
|
|
32
|
+
chunk_size (int, optional): Chunk size for streaming.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Tuple of (local_file_path, filename) if successful, else None.
|
|
36
|
+
"""
|
|
37
|
+
# Extract optional parameters
|
|
38
|
+
timeout = kwargs.get("timeout", 10)
|
|
39
|
+
chunk_size = kwargs.get("chunk_size")
|
|
40
|
+
# Log configured parameters for verification
|
|
41
|
+
logger.info("download_zotero_pdf params -> timeout=%s, chunk_size=%s", timeout, chunk_size)
|
|
42
|
+
# Log download start
|
|
43
|
+
logger.info("Downloading Zotero PDF for attachment %s from Zotero API", attachment_key)
|
|
44
|
+
zotero_pdf_url = f"https://api.zotero.org/users/{user_id}/items/{attachment_key}/file"
|
|
45
|
+
headers = {"Zotero-API-Key": api_key}
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
response = session.get(zotero_pdf_url, headers=headers, stream=True, timeout=timeout)
|
|
49
|
+
response.raise_for_status()
|
|
50
|
+
|
|
51
|
+
# Download to a temporary file first
|
|
52
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as temp_file:
|
|
53
|
+
for chunk in response.iter_content(chunk_size=chunk_size):
|
|
54
|
+
temp_file.write(chunk)
|
|
55
|
+
temp_file_path = temp_file.name
|
|
56
|
+
# Temp file written to %s
|
|
57
|
+
logger.info("Zotero PDF downloaded to temporary file: %s", temp_file_path)
|
|
58
|
+
|
|
59
|
+
# Determine filename from Content-Disposition header or default
|
|
60
|
+
if "filename=" in response.headers.get("Content-Disposition", ""):
|
|
61
|
+
filename = (
|
|
62
|
+
response.headers.get("Content-Disposition", "").split("filename=")[-1].strip('"')
|
|
63
|
+
)
|
|
64
|
+
else:
|
|
65
|
+
filename = "downloaded.pdf"
|
|
66
|
+
|
|
67
|
+
return temp_file_path, filename
|
|
68
|
+
|
|
69
|
+
except (requests.exceptions.RequestException, OSError) as e:
|
|
70
|
+
logger.error("Failed to download Zotero PDF for attachment %s: %s", attachment_key, e)
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def download_pdfs_in_parallel(
|
|
75
|
+
session: requests.Session,
|
|
76
|
+
user_id: str,
|
|
77
|
+
api_key: str,
|
|
78
|
+
attachment_item_map: dict[str, str],
|
|
79
|
+
**kwargs,
|
|
80
|
+
) -> dict[str, tuple[str, str, str]]:
|
|
81
|
+
"""
|
|
82
|
+
Download multiple PDFs in parallel using ThreadPoolExecutor.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
session: requests.Session for HTTP requests.
|
|
86
|
+
user_id: Zotero user ID.
|
|
87
|
+
api_key: Zotero API key.
|
|
88
|
+
attachment_item_map: Mapping of attachment_key to parent item_key.
|
|
89
|
+
kwargs:
|
|
90
|
+
max_workers (int, optional): Maximum number of worker threads (default: min(10, n)).
|
|
91
|
+
chunk_size (int, optional): Chunk size for streaming.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Mapping of parent item_key to (local_file_path, filename, attachment_key).
|
|
95
|
+
"""
|
|
96
|
+
# Extract optional parameters
|
|
97
|
+
max_workers = kwargs.get("max_workers")
|
|
98
|
+
chunk_size = kwargs.get("chunk_size")
|
|
99
|
+
# Log configured parameters for verification
|
|
100
|
+
logger.info(
|
|
101
|
+
"download_pdfs_in_parallel params -> max_workers=%s, chunk_size=%s",
|
|
102
|
+
max_workers,
|
|
103
|
+
chunk_size,
|
|
104
|
+
)
|
|
105
|
+
results: dict[str, tuple[str, str, str]] = {}
|
|
106
|
+
if not attachment_item_map:
|
|
107
|
+
return results
|
|
108
|
+
|
|
109
|
+
with concurrent.futures.ThreadPoolExecutor(
|
|
110
|
+
max_workers=(max_workers if max_workers is not None else min(10, len(attachment_item_map)))
|
|
111
|
+
) as executor:
|
|
112
|
+
future_to_keys = {
|
|
113
|
+
executor.submit(
|
|
114
|
+
download_zotero_pdf,
|
|
115
|
+
session,
|
|
116
|
+
user_id,
|
|
117
|
+
api_key,
|
|
118
|
+
attachment_key,
|
|
119
|
+
chunk_size=chunk_size,
|
|
120
|
+
): (attachment_key, item_key)
|
|
121
|
+
for attachment_key, item_key in attachment_item_map.items()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for future in concurrent.futures.as_completed(future_to_keys):
|
|
125
|
+
attachment_key, item_key = future_to_keys[future]
|
|
126
|
+
try:
|
|
127
|
+
res = future.result()
|
|
128
|
+
if res:
|
|
129
|
+
results[item_key] = (*res, attachment_key)
|
|
130
|
+
except (requests.exceptions.RequestException, OSError) as e:
|
|
131
|
+
logger.error("Failed to download PDF for key %s: %s", attachment_key, e)
|
|
132
|
+
|
|
133
|
+
return results
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Zotero Read Tool
|
|
5
|
+
|
|
6
|
+
This LangGraph tool searches a user's Zotero library for items matching a query
|
|
7
|
+
and optionally downloads their PDF attachments. It returns structured metadata
|
|
8
|
+
for each found item and makes the results available as an artifact.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Annotated, Any
|
|
13
|
+
|
|
14
|
+
from langchain_core.messages import ToolMessage
|
|
15
|
+
from langchain_core.tools import tool
|
|
16
|
+
from langchain_core.tools.base import InjectedToolCallId
|
|
17
|
+
from langgraph.types import Command
|
|
18
|
+
from pydantic import BaseModel, Field
|
|
19
|
+
|
|
20
|
+
from .utils.read_helper import ZoteroSearchData
|
|
21
|
+
|
|
22
|
+
# Configure logging
|
|
23
|
+
logging.basicConfig(level=logging.INFO)
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ZoteroSearchInput(BaseModel):
|
|
28
|
+
"""Input schema for the Zotero search tool.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
query (str): Search string to match against item metadata.
|
|
32
|
+
only_articles (bool): If True, restrict results to 'journalArticle' and similar types.
|
|
33
|
+
limit (int): Maximum number of items to fetch from Zotero.
|
|
34
|
+
download_pdfs (bool): If True, download PDF attachments for each item.
|
|
35
|
+
tool_call_id (str): Internal identifier for this tool invocation.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
query: str = Field(description="Search query string to find papers in Zotero library.")
|
|
39
|
+
only_articles: bool = Field(
|
|
40
|
+
default=True,
|
|
41
|
+
description="Whether to only search for journal articles/conference papers.",
|
|
42
|
+
)
|
|
43
|
+
limit: int = Field(default=2, description="Maximum number of results to return", ge=1, le=100)
|
|
44
|
+
download_pdfs: bool = Field(
|
|
45
|
+
default=False,
|
|
46
|
+
description="Whether to download PDF attachments immediately (default True).",
|
|
47
|
+
)
|
|
48
|
+
tool_call_id: Annotated[str, InjectedToolCallId]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@tool(args_schema=ZoteroSearchInput, parse_docstring=True)
|
|
52
|
+
def zotero_read(
|
|
53
|
+
query: str,
|
|
54
|
+
only_articles: bool,
|
|
55
|
+
tool_call_id: Annotated[str, InjectedToolCallId],
|
|
56
|
+
limit: int = 2,
|
|
57
|
+
download_pdfs: bool = False,
|
|
58
|
+
) -> Command[Any]:
|
|
59
|
+
"""
|
|
60
|
+
Execute a search on the Zotero library and return matching items.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
query (str): Text query to search in titles, abstracts, tags, etc.
|
|
64
|
+
only_articles (bool): When True, only include items of type 'journalArticle'
|
|
65
|
+
or 'conferencePaper'.
|
|
66
|
+
tool_call_id (str): Internal ID injected by LangGraph to track this tool call.
|
|
67
|
+
limit (int, optional): Max number of items to return (1–100). Defaults to 2.
|
|
68
|
+
download_pdfs (bool, optional): If True, PDFs for each returned item will be downloaded now.
|
|
69
|
+
If False, only metadata is fetched. Defaults to False.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Command[Any]: A LangGraph Command updating the agent state:
|
|
73
|
+
- 'article_data': dict mapping item keys to metadata (and 'pdf_url' if downloaded).
|
|
74
|
+
- 'last_displayed_papers': identifier pointing to the articles in state.
|
|
75
|
+
- 'messages': list containing a ToolMessage with a human-readable summary
|
|
76
|
+
and an 'artifact' referencing the raw article_data.
|
|
77
|
+
"""
|
|
78
|
+
# Create search data object to organize variables
|
|
79
|
+
# download_pdfs flag controls whether PDFs are fetched now or deferred
|
|
80
|
+
search_data = ZoteroSearchData(
|
|
81
|
+
query=query,
|
|
82
|
+
only_articles=only_articles,
|
|
83
|
+
limit=limit,
|
|
84
|
+
download_pdfs=download_pdfs,
|
|
85
|
+
tool_call_id=tool_call_id,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Process the search
|
|
89
|
+
search_data.process_search()
|
|
90
|
+
results = search_data.get_search_results()
|
|
91
|
+
|
|
92
|
+
return Command(
|
|
93
|
+
update={
|
|
94
|
+
"article_data": results["article_data"],
|
|
95
|
+
# Store the latest article_data mapping directly for display
|
|
96
|
+
"last_displayed_papers": results["article_data"],
|
|
97
|
+
"messages": [
|
|
98
|
+
ToolMessage(
|
|
99
|
+
content=results["content"],
|
|
100
|
+
tool_call_id=tool_call_id,
|
|
101
|
+
artifact=results["article_data"],
|
|
102
|
+
)
|
|
103
|
+
],
|
|
104
|
+
}
|
|
105
|
+
)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
This tool implements human-in-the-loop review for Zotero write operations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Annotated, Any, Literal
|
|
9
|
+
|
|
10
|
+
from langchain_core.messages import HumanMessage, ToolMessage
|
|
11
|
+
from langchain_core.tools import tool
|
|
12
|
+
from langchain_core.tools.base import InjectedToolCallId
|
|
13
|
+
from langgraph.prebuilt import InjectedState
|
|
14
|
+
from langgraph.types import Command, interrupt
|
|
15
|
+
from pydantic import BaseModel, Field
|
|
16
|
+
|
|
17
|
+
from .utils.review_helper import ReviewData
|
|
18
|
+
from .utils.zotero_path import fetch_papers_for_save
|
|
19
|
+
|
|
20
|
+
# Configure logging
|
|
21
|
+
logging.basicConfig(level=logging.INFO)
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ZoteroReviewDecision(BaseModel):
|
|
26
|
+
"""
|
|
27
|
+
Structured output schema for the human review decision.
|
|
28
|
+
- decision: "approve", "reject", or "custom"
|
|
29
|
+
- custom_path: Optional custom collection path if the decision is "custom"
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
decision: Literal["approve", "reject", "custom"]
|
|
33
|
+
custom_path: str | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ZoteroReviewInput(BaseModel):
|
|
37
|
+
"""Input schema for the Zotero review tool."""
|
|
38
|
+
|
|
39
|
+
tool_call_id: Annotated[str, InjectedToolCallId]
|
|
40
|
+
collection_path: str = Field(
|
|
41
|
+
description="The path where the paper should be saved in the Zotero library."
|
|
42
|
+
)
|
|
43
|
+
state: Annotated[dict, InjectedState]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@tool(args_schema=ZoteroReviewInput, parse_docstring=True)
|
|
47
|
+
def zotero_review(
|
|
48
|
+
tool_call_id: Annotated[str, InjectedToolCallId],
|
|
49
|
+
collection_path: str,
|
|
50
|
+
state: Annotated[dict, InjectedState],
|
|
51
|
+
) -> Command[Any]:
|
|
52
|
+
"""
|
|
53
|
+
Use this tool to get human review and approval before saving papers to Zotero.
|
|
54
|
+
This tool should be called before the zotero_write to ensure the user approves
|
|
55
|
+
the operation.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
tool_call_id (str): The tool call ID.
|
|
59
|
+
collection_path (str): The Zotero collection path where papers should be saved.
|
|
60
|
+
state (dict): The state containing previously fetched papers.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Command[Any]: The next action to take based on human input.
|
|
64
|
+
"""
|
|
65
|
+
logger.info("Requesting human review for saving to collection: %s", collection_path)
|
|
66
|
+
|
|
67
|
+
# Use our utility function to fetch papers from state
|
|
68
|
+
fetched_papers = fetch_papers_for_save(state)
|
|
69
|
+
|
|
70
|
+
if not fetched_papers:
|
|
71
|
+
raise ValueError(
|
|
72
|
+
"No fetched papers were found to save. "
|
|
73
|
+
"Please retrieve papers using Zotero Read or Semantic Scholar first."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Create review data object to organize variables
|
|
77
|
+
review_data = ReviewData(collection_path, fetched_papers, tool_call_id, state)
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
# Interrupt the graph to get human approval
|
|
81
|
+
human_review = interrupt(review_data.review_info)
|
|
82
|
+
# Process human response using structured output via LLM
|
|
83
|
+
llm_model = state.get("llm_model")
|
|
84
|
+
if llm_model is None:
|
|
85
|
+
raise ValueError("LLM model is not available in the state.")
|
|
86
|
+
structured_llm = llm_model.with_structured_output(ZoteroReviewDecision)
|
|
87
|
+
# Convert the raw human response to a message for structured parsing
|
|
88
|
+
decision_response = structured_llm.invoke([HumanMessage(content=str(human_review))])
|
|
89
|
+
|
|
90
|
+
# Process the structured response
|
|
91
|
+
if decision_response.decision == "approve":
|
|
92
|
+
logger.info("User approved saving papers to Zotero")
|
|
93
|
+
return Command(
|
|
94
|
+
update={
|
|
95
|
+
"messages": [
|
|
96
|
+
ToolMessage(
|
|
97
|
+
content=review_data.get_approval_message(),
|
|
98
|
+
tool_call_id=tool_call_id,
|
|
99
|
+
)
|
|
100
|
+
],
|
|
101
|
+
"zotero_write_approval_status": {
|
|
102
|
+
"collection_path": review_data.collection_path,
|
|
103
|
+
"approved": True,
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
if decision_response.decision == "custom" and decision_response.custom_path:
|
|
108
|
+
logger.info("User approved with custom path: %s", decision_response.custom_path)
|
|
109
|
+
return Command(
|
|
110
|
+
update={
|
|
111
|
+
"messages": [
|
|
112
|
+
ToolMessage(
|
|
113
|
+
content=review_data.get_custom_path_approval_message(
|
|
114
|
+
decision_response.custom_path
|
|
115
|
+
),
|
|
116
|
+
tool_call_id=tool_call_id,
|
|
117
|
+
)
|
|
118
|
+
],
|
|
119
|
+
"zotero_write_approval_status": {
|
|
120
|
+
"collection_path": decision_response.custom_path,
|
|
121
|
+
"approved": True,
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
logger.info("User rejected saving papers to Zotero")
|
|
126
|
+
return Command(
|
|
127
|
+
update={
|
|
128
|
+
"messages": [
|
|
129
|
+
ToolMessage(
|
|
130
|
+
content="Human rejected saving papers to Zotero.",
|
|
131
|
+
tool_call_id=tool_call_id,
|
|
132
|
+
)
|
|
133
|
+
],
|
|
134
|
+
"zotero_write_approval_status": {"approved": False},
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
# pylint: disable=broad-except
|
|
138
|
+
except Exception as e:
|
|
139
|
+
# If interrupt or structured output processing fails, fallback to explicit confirmation
|
|
140
|
+
logger.warning("Structured review processing failed: %s", e)
|
|
141
|
+
return Command(
|
|
142
|
+
update={
|
|
143
|
+
"messages": [
|
|
144
|
+
ToolMessage(
|
|
145
|
+
content=(
|
|
146
|
+
f"REVIEW REQUIRED: Would you like to save "
|
|
147
|
+
f"{review_data.total_papers} papers to Zotero collection "
|
|
148
|
+
f"'{review_data.collection_path}'?\n\n"
|
|
149
|
+
f"Papers to save:\n{review_data.papers_preview}\n\n"
|
|
150
|
+
"Please respond with 'Yes' to confirm or 'No' to cancel."
|
|
151
|
+
),
|
|
152
|
+
tool_call_id=tool_call_id,
|
|
153
|
+
)
|
|
154
|
+
],
|
|
155
|
+
"zotero_write_approval_status": {
|
|
156
|
+
"collection_path": review_data.collection_path,
|
|
157
|
+
"papers_reviewed": True,
|
|
158
|
+
"approved": False, # Not approved yet
|
|
159
|
+
"papers_count": review_data.total_papers,
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
This tool is used to save fetched papers to Zotero library after human approval.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Annotated, Any
|
|
9
|
+
|
|
10
|
+
from langchain_core.messages import ToolMessage
|
|
11
|
+
from langchain_core.tools import tool
|
|
12
|
+
from langchain_core.tools.base import InjectedToolCallId
|
|
13
|
+
from langgraph.prebuilt import InjectedState
|
|
14
|
+
from langgraph.types import Command
|
|
15
|
+
from pydantic import BaseModel, Field
|
|
16
|
+
|
|
17
|
+
from .utils.write_helper import ZoteroWriteData
|
|
18
|
+
|
|
19
|
+
# Configure logging
|
|
20
|
+
logging.basicConfig(level=logging.INFO)
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ZoteroSaveInput(BaseModel):
|
|
25
|
+
"""Input schema for the Zotero save tool."""
|
|
26
|
+
|
|
27
|
+
tool_call_id: Annotated[str, InjectedToolCallId]
|
|
28
|
+
collection_path: str = Field(
|
|
29
|
+
description="The path where the paper should be saved in the Zotero library."
|
|
30
|
+
)
|
|
31
|
+
state: Annotated[dict, InjectedState]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@tool(args_schema=ZoteroSaveInput, parse_docstring=True)
|
|
35
|
+
def zotero_write(
|
|
36
|
+
tool_call_id: Annotated[str, InjectedToolCallId],
|
|
37
|
+
collection_path: str,
|
|
38
|
+
state: Annotated[dict, InjectedState],
|
|
39
|
+
) -> Command[Any]:
|
|
40
|
+
"""
|
|
41
|
+
Use this tool to save previously fetched papers from Semantic Scholar
|
|
42
|
+
to a specified Zotero collection after human approval.
|
|
43
|
+
|
|
44
|
+
This tool checks if the user has approved the save operation via the
|
|
45
|
+
zotero_review. If approved, it will save the papers to the
|
|
46
|
+
approved collection path.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
tool_call_id (Annotated[str, InjectedToolCallId]): The tool call ID.
|
|
50
|
+
collection_path (str): The Zotero collection path where papers should be saved.
|
|
51
|
+
state (Annotated[dict, InjectedState]): The state containing previously fetched papers.
|
|
52
|
+
user_confirmation (str, optional): User confirmation message when interrupt is
|
|
53
|
+
not available.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Command[Any]: The save results and related information.
|
|
57
|
+
"""
|
|
58
|
+
# Create write data object to organize variables
|
|
59
|
+
write_data = ZoteroWriteData(tool_call_id, collection_path, state)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
# Process the write operation
|
|
63
|
+
results = write_data.process_write()
|
|
64
|
+
|
|
65
|
+
return Command(
|
|
66
|
+
update={
|
|
67
|
+
"messages": [
|
|
68
|
+
ToolMessage(
|
|
69
|
+
content=results["content"],
|
|
70
|
+
tool_call_id=tool_call_id,
|
|
71
|
+
artifact=results["fetched_papers"],
|
|
72
|
+
)
|
|
73
|
+
],
|
|
74
|
+
"zotero_write_approval_status": {}, # Clear approval info
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
except ValueError as e:
|
|
78
|
+
# Only handle collection not found errors with a Command
|
|
79
|
+
if "collection path" in str(e).lower():
|
|
80
|
+
return Command(
|
|
81
|
+
update={
|
|
82
|
+
"messages": [
|
|
83
|
+
ToolMessage(
|
|
84
|
+
content=str(e),
|
|
85
|
+
tool_call_id=tool_call_id,
|
|
86
|
+
)
|
|
87
|
+
],
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
# Let other ValueErrors (like no papers) propagate up
|
|
91
|
+
raise
|