From 758f0df6ba02a2c229c0992ad42fd5a6097fc2b8 Mon Sep 17 00:00:00 2001 From: Evgeniya Sukhodolskaya Date: Wed, 11 Feb 2026 14:12:48 +0100 Subject: [PATCH 1/3] db to engine --- Makefile | 6 +++--- .../application/services/hybrid_service/qdrant_query.py | 2 +- .../services/query_vectorstore_service/qdrant_query.py | 2 +- .../infrastructure/{qdrant_db => qdrant_engine}/__init__.py | 0 .../{qdrant_db => qdrant_engine}/create_collection.py | 2 +- .../{qdrant_db => qdrant_engine}/delete_collection.py | 2 +- .../{qdrant_db => qdrant_engine}/qdrant_ingestion.py | 2 +- .../{qdrant_db => qdrant_engine}/qdrant_vectorstore.py | 0 tests/integration/test_databases.py | 6 +++--- 9 files changed, 11 insertions(+), 11 deletions(-) rename src/biomedical_graphrag/infrastructure/{qdrant_db => qdrant_engine}/__init__.py (100%) rename src/biomedical_graphrag/infrastructure/{qdrant_db => qdrant_engine}/create_collection.py (79%) rename src/biomedical_graphrag/infrastructure/{qdrant_db => qdrant_engine}/delete_collection.py (79%) rename src/biomedical_graphrag/infrastructure/{qdrant_db => qdrant_engine}/qdrant_ingestion.py (95%) rename src/biomedical_graphrag/infrastructure/{qdrant_db => qdrant_engine}/qdrant_vectorstore.py (100%) diff --git a/Makefile b/Makefile index 5c743ea..67a56eb 100644 --- a/Makefile +++ b/Makefile @@ -63,17 +63,17 @@ custom-graph-query: ## Run a custom natural language query using Neo4j GraphRAG ################################################################################# create-qdrant-collection: ## Create the Qdrant collection for embeddings @echo "Creating Qdrant collection for embeddings..." - uv run src/biomedical_graphrag/infrastructure/qdrant_db/create_collection.py + uv run src/biomedical_graphrag/infrastructure/qdrant_engine/create_collection.py @echo "Qdrant collection creation complete." delete-qdrant-collection: ## Delete the Qdrant collection for embeddings @echo "Deleting Qdrant collection for embeddings..." - uv run src/biomedical_graphrag/infrastructure/qdrant_db/delete_collection.py + uv run src/biomedical_graphrag/infrastructure/qdrant_engine/delete_collection.py @echo "Qdrant collection deletion complete." ingest-qdrant-data: ## Ingest embeddings into the Qdrant collection @echo "Ingesting embeddings into the Qdrant collection..." - uv run src/biomedical_graphrag/infrastructure/qdrant_db/qdrant_ingestion.py + uv run src/biomedical_graphrag/infrastructure/qdrant_engine/qdrant_ingestion.py @echo "Embeddings ingestion complete." custom-qdrant-query: ## Run a custom query on the Qdrant collection (modify the --ask parameter as needed) diff --git a/src/biomedical_graphrag/application/services/hybrid_service/qdrant_query.py b/src/biomedical_graphrag/application/services/hybrid_service/qdrant_query.py index 9632476..5cdebe5 100644 --- a/src/biomedical_graphrag/application/services/hybrid_service/qdrant_query.py +++ b/src/biomedical_graphrag/application/services/hybrid_service/qdrant_query.py @@ -1,6 +1,6 @@ from qdrant_client.models import models -from biomedical_graphrag.infrastructure.qdrant_db.qdrant_vectorstore import AsyncQdrantVectorStore +from biomedical_graphrag.infrastructure.qdrant_engine.qdrant_vectorstore import AsyncQdrantVectorStore from biomedical_graphrag.utils.logger_util import setup_logging logger = setup_logging() diff --git a/src/biomedical_graphrag/application/services/query_vectorstore_service/qdrant_query.py b/src/biomedical_graphrag/application/services/query_vectorstore_service/qdrant_query.py index 642fa71..6d081c2 100644 --- a/src/biomedical_graphrag/application/services/query_vectorstore_service/qdrant_query.py +++ b/src/biomedical_graphrag/application/services/query_vectorstore_service/qdrant_query.py @@ -4,7 +4,7 @@ QDRANT_GENERATION_PROMPT, ) from biomedical_graphrag.config import settings -from biomedical_graphrag.infrastructure.qdrant_db.qdrant_vectorstore import AsyncQdrantVectorStore +from biomedical_graphrag.infrastructure.qdrant_engine.qdrant_vectorstore import AsyncQdrantVectorStore from biomedical_graphrag.utils.logger_util import setup_logging from qdrant_client.models import models diff --git a/src/biomedical_graphrag/infrastructure/qdrant_db/__init__.py b/src/biomedical_graphrag/infrastructure/qdrant_engine/__init__.py similarity index 100% rename from src/biomedical_graphrag/infrastructure/qdrant_db/__init__.py rename to src/biomedical_graphrag/infrastructure/qdrant_engine/__init__.py diff --git a/src/biomedical_graphrag/infrastructure/qdrant_db/create_collection.py b/src/biomedical_graphrag/infrastructure/qdrant_engine/create_collection.py similarity index 79% rename from src/biomedical_graphrag/infrastructure/qdrant_db/create_collection.py rename to src/biomedical_graphrag/infrastructure/qdrant_engine/create_collection.py index 841fe6f..3f08b30 100644 --- a/src/biomedical_graphrag/infrastructure/qdrant_db/create_collection.py +++ b/src/biomedical_graphrag/infrastructure/qdrant_engine/create_collection.py @@ -1,6 +1,6 @@ import asyncio -from biomedical_graphrag.infrastructure.qdrant_db.qdrant_vectorstore import AsyncQdrantVectorStore +from biomedical_graphrag.infrastructure.qdrant_engine.qdrant_vectorstore import AsyncQdrantVectorStore async def create_collection() -> None: diff --git a/src/biomedical_graphrag/infrastructure/qdrant_db/delete_collection.py b/src/biomedical_graphrag/infrastructure/qdrant_engine/delete_collection.py similarity index 79% rename from src/biomedical_graphrag/infrastructure/qdrant_db/delete_collection.py rename to src/biomedical_graphrag/infrastructure/qdrant_engine/delete_collection.py index 0cfe57e..301a5f1 100644 --- a/src/biomedical_graphrag/infrastructure/qdrant_db/delete_collection.py +++ b/src/biomedical_graphrag/infrastructure/qdrant_engine/delete_collection.py @@ -1,6 +1,6 @@ import asyncio -from biomedical_graphrag.infrastructure.qdrant_db.qdrant_vectorstore import AsyncQdrantVectorStore +from biomedical_graphrag.infrastructure.qdrant_engine.qdrant_vectorstore import AsyncQdrantVectorStore async def delete_collection() -> None: diff --git a/src/biomedical_graphrag/infrastructure/qdrant_db/qdrant_ingestion.py b/src/biomedical_graphrag/infrastructure/qdrant_engine/qdrant_ingestion.py similarity index 95% rename from src/biomedical_graphrag/infrastructure/qdrant_db/qdrant_ingestion.py rename to src/biomedical_graphrag/infrastructure/qdrant_engine/qdrant_ingestion.py index 485c323..2fd3210 100644 --- a/src/biomedical_graphrag/infrastructure/qdrant_db/qdrant_ingestion.py +++ b/src/biomedical_graphrag/infrastructure/qdrant_engine/qdrant_ingestion.py @@ -1,6 +1,6 @@ import asyncio -from biomedical_graphrag.infrastructure.qdrant_db.qdrant_vectorstore import AsyncQdrantVectorStore +from biomedical_graphrag.infrastructure.qdrant_engine.qdrant_vectorstore import AsyncQdrantVectorStore from biomedical_graphrag.utils.json_util import load_gene_json, load_pubmed_json from biomedical_graphrag.utils.logger_util import setup_logging diff --git a/src/biomedical_graphrag/infrastructure/qdrant_db/qdrant_vectorstore.py b/src/biomedical_graphrag/infrastructure/qdrant_engine/qdrant_vectorstore.py similarity index 100% rename from src/biomedical_graphrag/infrastructure/qdrant_db/qdrant_vectorstore.py rename to src/biomedical_graphrag/infrastructure/qdrant_engine/qdrant_vectorstore.py diff --git a/tests/integration/test_databases.py b/tests/integration/test_databases.py index 12a0b8e..2670cad 100644 --- a/tests/integration/test_databases.py +++ b/tests/integration/test_databases.py @@ -6,7 +6,7 @@ from biomedical_graphrag.config import Neo4jSettings, QdrantSettings from biomedical_graphrag.infrastructure.neo4j_db.neo4j_client import AsyncNeo4jClient -from biomedical_graphrag.infrastructure.qdrant_db.qdrant_vectorstore import AsyncQdrantVectorStore +from biomedical_graphrag.infrastructure.qdrant_engine.qdrant_vectorstore import AsyncQdrantVectorStore class TestQdrantIntegration: @@ -16,7 +16,7 @@ class TestQdrantIntegration: async def test_qdrant_vectorstore_initialization(self) -> None: """Test that QdrantVectorStore can be initialized with settings.""" with patch( - "biomedical_graphrag.infrastructure.qdrant_db.qdrant_vectorstore.settings" + "biomedical_graphrag.infrastructure.qdrant_engine.qdrant_vectorstore.settings" ) as mock_settings: mock_settings.qdrant.url = "http://localhost:6333" mock_settings.qdrant.api_key.get_secret_value.return_value = "test-key" @@ -27,7 +27,7 @@ async def test_qdrant_vectorstore_initialization(self) -> None: mock_settings.openai.api_key.get_secret_value.return_value = "test-openai-key" with patch( - "biomedical_graphrag.infrastructure.qdrant_db.qdrant_vectorstore.AsyncQdrantClient" + "biomedical_graphrag.infrastructure.qdrant_engine.qdrant_vectorstore.AsyncQdrantClient" ) as mock_client_class: mock_client = AsyncMock() mock_client_class.return_value = mock_client From a91c59fdf4efd76c1e2d0c705ae041a61f4423cc Mon Sep 17 00:00:00 2001 From: Evgeniya Sukhodolskaya Date: Wed, 11 Feb 2026 15:21:17 +0100 Subject: [PATCH 2/3] fixed ingestion bug --- .../infrastructure/qdrant_engine/qdrant_vectorstore.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/biomedical_graphrag/infrastructure/qdrant_engine/qdrant_vectorstore.py b/src/biomedical_graphrag/infrastructure/qdrant_engine/qdrant_vectorstore.py index 47a5af0..cf586d1 100644 --- a/src/biomedical_graphrag/infrastructure/qdrant_engine/qdrant_vectorstore.py +++ b/src/biomedical_graphrag/infrastructure/qdrant_engine/qdrant_vectorstore.py @@ -139,7 +139,7 @@ def _define_bm25_vectors(self, text: str, avg_len: int = 256) -> models.Document ) async def upsert_points( - self, pubmed_data: dict[str, Any], gene_data: dict[str, Any] | None = None, only_new: bool = False, batch_size: int = 30 + self, pubmed_data: dict[str, Any], gene_data: dict[str, Any] | None = None, only_new: bool = False, batch_size: int = 32 ) -> None: """ Upsert points into a collection from pubmed_dataset.json structure, @@ -324,9 +324,7 @@ async def upsert_points( try: await self.client.upsert( collection_name=self.collection_name, - points=PointsList( - points=batch_points, - ), + points=batch_points, ) total_skipped += batch_skipped total_processed += len(batch_points) From 772d9b6046fd9da6f8af78c367607daf9cfaf787 Mon Sep 17 00:00:00 2001 From: Evgeniya Sukhodolskaya Date: Wed, 25 Feb 2026 20:12:19 +0100 Subject: [PATCH 3/3] Claude & I updated README + make file + terms & dropped stale Qdrant service part --- .env.example | 20 +-- Makefile | 16 +- README.md | 104 ++++------- pyproject.toml | 4 +- src/biomedical_graphrag/api/server.py | 10 +- .../application/cli/fusion_query.py | 8 +- .../application/cli/query_vectorstore.py | 47 ----- .../hybrid_service/prompts/__init__.py | 2 +- .../hybrid_service/prompts/hybrid_prompts.py | 2 +- .../services/hybrid_service/qdrant_query.py | 2 +- .../services/hybrid_service/tool_calling.py | 8 +- .../services/hybrid_service/tools/__init__.py | 2 +- .../hybrid_service/tools/qdrant_tools.py | 4 +- .../query_vectorstore_service/__init__.py | 5 - .../prompts/__init__.py | 5 - .../prompts/qdrant_prompts.py | 13 -- .../query_vectorstore_service/qdrant_query.py | 163 ----------------- .../qdrant_engine/qdrant_ingestion.py | 2 +- uv.lock | 166 ------------------ 19 files changed, 71 insertions(+), 512 deletions(-) delete mode 100644 src/biomedical_graphrag/application/cli/query_vectorstore.py delete mode 100644 src/biomedical_graphrag/application/services/query_vectorstore_service/__init__.py delete mode 100644 src/biomedical_graphrag/application/services/query_vectorstore_service/prompts/__init__.py delete mode 100644 src/biomedical_graphrag/application/services/query_vectorstore_service/prompts/qdrant_prompts.py delete mode 100644 src/biomedical_graphrag/application/services/query_vectorstore_service/qdrant_query.py diff --git a/.env.example b/.env.example index 07c0b37..9ecfa4b 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,8 @@ # OpenAI Configuration OPENAI__API_KEY=your_openai_api_key_here +OPENAI__MODEL=gpt-4o-mini +OPENAI__TEMPERATURE=0.0 +OPENAI__MAX_TOKENS=1500 # Neo4j Configuration NEO4J__URI=your_neo4j_uri_here @@ -11,23 +14,16 @@ NEO4J__DATABASE=neo4j QDRANT__URL=your_qdrant_url_here QDRANT__API_KEY=your_qdrant_api_key_here QDRANT__COLLECTION_NAME=biomedical_papers +QDRANT__EMBEDDING_MODEL=text-embedding-3-large QDRANT__EMBEDDING_DIMENSION=1536 QDRANT__RERANKER_EMBEDDING_DIMENSION=3072 -QDRANT__EMBEDDING_MODEL=text-embedding-3-large -QDRANT__ESTIMATE_BM25_AVG_LEN_ON_X_DOCS=500 +QDRANT__ESTIMATE_BM25_AVG_LEN_ON_X_DOCS=300 QDRANT__CLOUD_INFERENCE=true -JSON__DATA_PATH=data/pubmed_sample.json # PubMed Configuration PUBMED__API_KEY=your_pubmed_api_key_here PUBMED__EMAIL=your_email@example.com -# OpenAI Settings -OPENAI__API_KEY=your_openai_api_key_here -OPENAI__MODEL=gpt-4o-mini -OPENAI__TEMPERATURE=0.0 -OPENAI__MAX_TOKENS=1500 - -# JSON Paths -JSON__PUBMED_JSON_PATH=data/pubmed_dataset.json -JSON__GENE_JSON_PATH=data/gene_dataset.json +# JSON Data Paths (optional — defaults are data/pubmed_dataset.json and data/gene_dataset.json) +JSON_DATA__PUBMED_JSON_PATH=data/pubmed_dataset.json +JSON_DATA__GENE_JSON_PATH=data/gene_dataset.json diff --git a/Makefile b/Makefile index 67a56eb..5600da0 100644 --- a/Makefile +++ b/Makefile @@ -48,14 +48,9 @@ delete-graph: ## Delete all nodes and relationships in the Neo4j graph uv run src/biomedical_graphrag/infrastructure/neo4j_db/delete_graph.py @echo "Neo4j graph deletion complete." -example-graph-query: ## Run example queries on the Neo4j graph using GraphRAG - @echo "Running example queries on the Neo4j graph..." - uv run src/biomedical_graphrag/application/cli/fusion_query.py --examples - @echo "Example queries complete." - -custom-graph-query: ## Run a custom natural language query using Neo4j GraphRAG (use QUESTION="your question") - @echo "Running custom query on the Neo4j graph with GraphRAG..." - uv run src/biomedical_graphrag/application/cli/fusion_query.py $(if $(QUESTION),--ask "$(QUESTION)") +custom-graph-query: ## Run a custom natural language query (use QUESTION="your question") + @echo "Running custom query..." + uv run src/biomedical_graphrag/application/cli/fusion_query.py "$(QUESTION)" @echo "Custom query complete." ################################################################################# @@ -76,11 +71,6 @@ ingest-qdrant-data: ## Ingest embeddings into the Qdrant collection uv run src/biomedical_graphrag/infrastructure/qdrant_engine/qdrant_ingestion.py @echo "Embeddings ingestion complete." -custom-qdrant-query: ## Run a custom query on the Qdrant collection (modify the --ask parameter as needed) - @echo "Running custom query on the Qdrant collection..." - uv run src/biomedical_graphrag/application/cli/query_vectorstore.py $(if $(QUESTION),--ask "$(QUESTION)") - @echo "Custom query complete." - ################################################################################# ## API Server Commands ################################################################################# diff --git a/README.md b/README.md index 9fcd7a9..49816ac 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,9 @@ - [Data Collection](#data-collection) - [Infrastructure Setup](#infrastructure-setup) - [Neo4j Graph Database](#neo4j-graph-database) - - [Qdrant Vector Database](#qdrant-vector-database) + - [Qdrant Vector Search Engine](#qdrant-vector-search-engine) - [Query Commands](#query-commands) - - [Qdrant Vector Search](#qdrant-vector-search) - [Hybrid Neo4j + Qdrant Queries](#hybrid-neo4j--qdrant-queries) - - [Available Query Types](#available-query-types) - [Sample Queries](#sample-queries) - [API Server](#api-server) - [Frontend](#frontend) @@ -46,15 +44,20 @@ ## Overview -A comprehensive GraphRAG (Graph Retrieval-Augmented Generation) system designed for biomedical research. It combines knowledge graphs with vector search engine to provide intelligent querying and analysis of biomedical literature and genomic data. +A biomedical context engineering system. An agent uses Qdrant vector search engine tools (hybrid retrieval, recommendations) and Neo4j graph database tools (graph enrichment) to gather context, then fuses it into a single biomedical answer. -Article: [Building a Biomedical GraphRAG: When Knowledge Graphs Meet Vector Search](https://aiechoes.substack.com/p/building-a-biomedical-graphrag-when) +> Originally forked from [benitomartin/biomedical-graphrag](https://github.com/benitomartin/biomedical-graphrag). + +**References:** +- Video: [PubMed Navigator](https://www.youtube.com/watch?v=3NWTi90i6C4) +- Article: [Building a Biomedical GraphRAG: When Knowledge Graphs Meet Vector Search](https://aiechoes.substack.com/p/building-a-biomedical-graphrag-when) **Key Features:** -- **Hybrid Query System**: Combines Neo4j graph database with Qdrant vector search engine for comprehensive biomedical insights +- **Context Engineering**: Agent orchestrates Qdrant and Neo4j tools, fusing results into a single answer +- **Qdrant Vector Search Engine**: Hybrid retrieval (dense + BM25 with reranking) and constraint-based recommendations +- **Neo4j Graph Database**: Graph enrichment via ontology-based tools (collaborator networks, MeSH relations, gene co-mentions) - **Data Integration**: Processes PubMed papers, gene data, and research citations -- **Intelligent Querying**: Uses LLM-powered tool selection for graph enrichment and hybrid (semantic + lexical) search - **Biomedical Schema**: Specialized graph schema for papers, authors, institutions, genes, and MeSH terms - **Async Processing**: High-performance async data collection and processing @@ -67,7 +70,7 @@ biomedical-graphrag/ ├── src/ │ └── biomedical_graphrag/ │ ├── api/ # FastAPI server -│ │ └── server.py # GraphRAG API endpoints +│ │ └── server.py # PubMed Navigator API endpoints │ ├── application/ # Application layer │ │ ├── cli/ # Command-line interfaces │ │ └── services/ # Business logic services @@ -102,7 +105,7 @@ biomedical-graphrag/ 1. Clone the repository: ```bash - git clone git@github.com:benitomartin/biomedical-graphrag.git + git clone git@github.com:thierrypdamiba/biomedical-graphrag.git cd biomedical-graphrag ``` @@ -127,7 +130,7 @@ biomedical-graphrag/ 1. Create a `.env` file in the root directory: ```bash - cp env.example .env + cp .env.example .env ``` ## Usage @@ -221,7 +224,7 @@ make delete-qdrant-collection Notes: - Embeddings are built from **PubMed paper abstracts**. -- This project uses OpenAI eembeddings **Matryoshka Representation Learning (MRL)** feature: +- This project uses OpenAI embeddings **Matryoshka Representation Learning (MRL)** feature: - `QDRANT__EMBEDDING_DIMENSION` is the prefix dimension used for **retrieval** (stored in Qdrant as the `Dense` vector). - `QDRANT__RERANKER_EMBEDDING_DIMENSION` is the (larger) prefix dimension used for **reranking** (stored in Qdrant as the `Reranker` vector). - `make ingest-qdrant-data` currently recreates the collection each run (see `qdrant_ingestion.py`). @@ -233,48 +236,27 @@ Notes: ### Query Commands -#### Qdrant Vector Search - -```bash -# Run a custom query on the Qdrant vector store -make custom-qdrant-query QUESTION="Which institutions have collaborated most frequently on papers about 'Gene Editing' and 'Immunotherapy'?" - -# Or run directly with the CLI -uv run src/biomedical_graphrag/application/cli/query_vectorstore.py --ask "Which institutions have collaborated most frequently on papers about 'Gene Editing' and 'Immunotherapy'?" -``` - #### Hybrid Neo4j + Qdrant Queries ```bash -# Run example queries on the Neo4j graph using GraphRAG -make example-graph-query - -# Run a custom natural language query using hybrid GraphRAG +# Run a custom natural language query make custom-graph-query QUESTION="What are the latest research trends in cancer immunotherapy?" # Or run directly with the CLI (positional args) uv run src/biomedical_graphrag/application/cli/fusion_query.py "What are the latest research trends in cancer immunotherapy?" ``` -#### Available Query Types - -**Qdrant Queries:** - -- Semantic search across paper abstracts and content -- Similarity-based retrieval using embeddings and BM25 fusion +The hybrid query system combines vector search engine (Qdrant) with graph enrichment (Neo4j): +- Author collaboration networks +- Citation analysis and paper relationships +- Gene-paper associations +- MeSH term relationships +- Institution affiliations -**Hybrid Queries:** - -- Combines vector search engine (Qdrant) with graph enrichment (Neo4j): - - Author collaboration networks - - Citation analysis and paper relationships - - Gene-paper associations - - MeSH term relationships - - Institution affiliations -- LLM-powered tool selection & fusion: - - Runs one Qdrant tool: hybrid retrieval (BM25 + dense & reranking) or recommendations with contraints, - to fetch relevant papers. - - Calls Neo4j enrichment tools for graph evidence. - - Produces one fused answer from both sources. +LLM-powered tool selection & fusion: +- Runs one Qdrant tool: hybrid retrieval (BM25 + dense & reranking) or recommendations with constraints — to fetch relevant papers. +- Calls Neo4j enrichment tools for graph evidence. +- Produces one fused answer from both sources. Output: @@ -283,27 +265,12 @@ Output: #### Sample Queries - Who collaborates with Jennifer Doudna on CRISPR research? - Which researchers work with Emmanuelle Charpentier on gene editing or genome engineering papers? - -- Who are George Church’s collaborators publishing on synthetic biology and genome sequencing? - -- List scientists collaborating with Feng Zhang on neuroscience studies - -- Which papers are related to PMID 31295471 based on shared MeSH terms? - -- Find papers similar to the CRISPR-Cas9 genome editing study with PMID 31295471 - -- Show other studies linked by MeSH terms to PMID 27562951 - -- Which genes are mentioned in the same papers as gag? - -- What genes appear together with HIF1A in cancer research? - Which genes are frequently co-mentioned with TP53? ### API Server -The project includes a FastAPI server that exposes the GraphRAG functionality via HTTP endpoints: +The project includes a FastAPI server (PubMed Navigator) that exposes the context engineering pipeline via HTTP endpoints: ```bash # Start the API server (runs on port 8765) @@ -316,21 +283,28 @@ make run-api |--------|----------|-------------| | GET | `/health` | Health check | | GET | `/api/neo4j/stats` | Neo4j graph statistics (node/relationship counts) | -| POST | `/api/search` | Hybrid GraphRAG search | +| POST | `/api/graphrag-query` | Context engineering search (Qdrant + Neo4j) | **Search Request Example:** ```bash -curl -X POST http://localhost:8765/api/search \ +curl -X POST http://localhost:8765/api/graphrag-query \ -H "Content-Type: application/json" \ - -d '{"query": "What genes are associated with breast cancer?", "limit": 10}' + -d '{"query": "What genes are associated with breast cancer?", "limit": 5}' ``` ### Frontend -The frontend is maintained in a separate repository: +The frontend is maintained in a separate repository: **[biomedical-graphrag-frontend](https://github.com/thierrypdamiba/biomedical-graphrag-frontend)** + +The quickest way to run it locally: + +```bash +# Auto-clones the frontend repo and starts it (requires pnpm) +make run-frontend +``` -**[biomedical-graphrag-frontend](https://github.com/thierrypdamiba/biomedical-graphrag-frontend)** +Or manually: ```bash git clone https://github.com/thierrypdamiba/biomedical-graphrag-frontend.git @@ -359,7 +333,7 @@ curl http://localhost:8765/health ### Troubleshooting - **Make fails immediately with ".env file is missing"** - - Create it with `cp env.example .env` and fill in required values. + - Create it with `cp .env.example .env` and fill in required values. - **Qdrant ingestion/query fails** - Confirm Qdrant is running and `QDRANT__URL` points to it. diff --git a/pyproject.toml b/pyproject.toml index 9943099..78ba0e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "biomedical-graphrag" version = "0.1.0" -description = "GraphRAG system for biomedical research, combining knowledge graphs and vector search" +description = "Context engineering for biomedical research, combining Qdrant vector search engine and Neo4j graph database" readme = "README.md" authors = [ {name = "Benito Martin"} @@ -14,8 +14,6 @@ dependencies = [ "fastapi>=0.115.0", "loguru>=0.7.3", "neo4j>=5.28.2", - "neo4j-graphrag>=1.10.0", - "nest-asyncio>=1.5.8", "openai>=1.0.0", "pydantic>=2.12.0", "pydantic-settings>=2.11.0", diff --git a/src/biomedical_graphrag/api/server.py b/src/biomedical_graphrag/api/server.py index 3e5eae9..47b4c68 100644 --- a/src/biomedical_graphrag/api/server.py +++ b/src/biomedical_graphrag/api/server.py @@ -64,7 +64,7 @@ async def _preload_services() -> None: app = FastAPI( title="PubMed Navigator API", - description="Hybrid search API combining Qdrant vector search with Neo4j knowledge graph", + description="Context engineering API combining Qdrant vector search engine with Neo4j graph database", version="0.1.0", lifespan=lifespan, ) @@ -84,8 +84,8 @@ class SearchRequest(BaseModel): """Search request body.""" query: str = Field(..., description="The search query") - limit: int = Field(default=5, ge=1, le=5, description="Maximum number of results") - mode: str = Field(default="graphrag", description="Search mode: graphrag, dense, sparse, hybrid") + limit: int = Field(default=5, ge=1, le=5, description="Maximum number of results (vector search)") + mode: str = Field(default="graphrag", description="Search mode: graphrag (Qdrant + Neo4j context engineering)") class TraceStep(BaseModel): @@ -188,9 +188,9 @@ async def get_neo4j_stats() -> Neo4jStatsResponse: @app.post("/api/graphrag-query", response_model=SearchResponse) async def search(request: SearchRequest) -> SearchResponse: """ - Perform hybrid GraphRAG search. + Run context engineering pipeline. - Combines Qdrant vector search with Neo4j knowledge graph enrichment. + Combines Qdrant vector search engine with Neo4j graph enrichment and fuses the results. """ try: _load_services() diff --git a/src/biomedical_graphrag/application/cli/fusion_query.py b/src/biomedical_graphrag/application/cli/fusion_query.py index ba2ffb0..5d45460 100644 --- a/src/biomedical_graphrag/application/cli/fusion_query.py +++ b/src/biomedical_graphrag/application/cli/fusion_query.py @@ -1,7 +1,7 @@ """ -CLI for hybrid GraphRAG querying: -1. Retrieve relevant papers from Qdrant based on the tool selected. -2. Use LLM to select and run Neo4j enrichment tools. +CLI for context engineering pipeline: +1. Retrieve relevant papers from Qdrant (hybrid retrieval or recommendations). +2. Use LLM to select and run Neo4j graph enrichment tools. 3. Fuse both sources into one concise biomedical summary. """ @@ -36,7 +36,7 @@ async def main() -> None: answer = await run_tools_sequence_and_summarize(question) print("\n=== Unified Biomedical Answer ===\n") - print(answer) + print(answer.summary) except Exception as e: logger.error(f"Error during query processing: {e}") raise diff --git a/src/biomedical_graphrag/application/cli/query_vectorstore.py b/src/biomedical_graphrag/application/cli/query_vectorstore.py deleted file mode 100644 index 22788fd..0000000 --- a/src/biomedical_graphrag/application/cli/query_vectorstore.py +++ /dev/null @@ -1,47 +0,0 @@ -import argparse -import asyncio - -from biomedical_graphrag.application.services.query_vectorstore_service.qdrant_query import ( - AsyncQdrantQuery, -) -from biomedical_graphrag.utils.logger_util import setup_logging - -logger = setup_logging() - - -async def main() -> None: - """ - Command-line interface to query the biomedical vector store (async). - Args: - --ask: The question to ask (default: example question) - - Returns: - Prints the answer to the console. - - """ - parser = argparse.ArgumentParser(description="Query the biomedical vector store.") - - parser.add_argument( - "--ask", - type=str, - default=( - "Which institutions have collaborated most" - "frequently on papers about 'Gene Editing' and 'Immunotherapy'?" - ), - help="The question to ask.", - ) - - args = parser.parse_args() - - # Initialize AsyncQdrantQuery - qdrant_query = AsyncQdrantQuery() - try: - # Perform the query - answer = await qdrant_query.get_answer(args.ask) - logger.info(f"Answer: {answer}") - finally: - await qdrant_query.close() - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/src/biomedical_graphrag/application/services/hybrid_service/prompts/__init__.py b/src/biomedical_graphrag/application/services/hybrid_service/prompts/__init__.py index 4e56ffb..3ec8e45 100644 --- a/src/biomedical_graphrag/application/services/hybrid_service/prompts/__init__.py +++ b/src/biomedical_graphrag/application/services/hybrid_service/prompts/__init__.py @@ -1,4 +1,4 @@ -"""Prompts for hybrid GraphRAG querying. +"""Prompts for context engineering. This package exports the prompt templates/helpers used by the hybrid service. """ diff --git a/src/biomedical_graphrag/application/services/hybrid_service/prompts/hybrid_prompts.py b/src/biomedical_graphrag/application/services/hybrid_service/prompts/hybrid_prompts.py index f7c0f3c..399134b 100644 --- a/src/biomedical_graphrag/application/services/hybrid_service/prompts/hybrid_prompts.py +++ b/src/biomedical_graphrag/application/services/hybrid_service/prompts/hybrid_prompts.py @@ -1,4 +1,4 @@ -"""Prompt templates for hybrid GraphRAG querying.""" +"""Prompt templates for context engineering.""" import json from typing import Any diff --git a/src/biomedical_graphrag/application/services/hybrid_service/qdrant_query.py b/src/biomedical_graphrag/application/services/hybrid_service/qdrant_query.py index 5cdebe5..dbce3f1 100644 --- a/src/biomedical_graphrag/application/services/hybrid_service/qdrant_query.py +++ b/src/biomedical_graphrag/application/services/hybrid_service/qdrant_query.py @@ -21,7 +21,7 @@ async def close(self) -> None: async def retrieve_papers_dense(self, query: str, top_k: int = 5) -> list[dict]: """ - Query the Qdrant vector store for similar papers (async). + Query the Qdrant vector search engine for similar papers (async). Vanilla dense search on quantized openAI embeddings. Args: diff --git a/src/biomedical_graphrag/application/services/hybrid_service/tool_calling.py b/src/biomedical_graphrag/application/services/hybrid_service/tool_calling.py index a63c7ca..86f0176 100644 --- a/src/biomedical_graphrag/application/services/hybrid_service/tool_calling.py +++ b/src/biomedical_graphrag/application/services/hybrid_service/tool_calling.py @@ -28,7 +28,7 @@ openai_client = OpenAI(api_key=settings.openai.api_key.get_secret_value()) -def _extract_qdrant_context(qdrant_results: list[dict]) -> dict[str, list[str]]: +def _extract_qdrant_context(qdrant_results: list[dict]) -> dict[str, list[str]]: #Is it a reverse engineering approach of Paper class? """Extract structured entities from Qdrant results for Neo4j tool pre-fill.""" pmids: list[str] = [] authors: list[str] = [] @@ -58,7 +58,7 @@ def _extract_qdrant_context(qdrant_results: list[dict]) -> dict[str, list[str]]: } -def _score_authors(neo4j: Neo4jGraphQuery, authors: list[str], mesh_terms: list[str]) -> list[str]: +def _score_authors(neo4j: Neo4jGraphQuery, authors: list[str], mesh_terms: list[str]) -> list[str]: #Might be problematic because terms have different importance to a person using the assistant """Score authors by paper count on relevant topics. Returns 'Name (N papers)' sorted by count.""" if not authors: return [] @@ -212,7 +212,7 @@ def run_graph_enrichment(question: str, qdrant_results: list[dict]) -> Neo4jEnri results: dict[str, Any] = {} tool_call_counts: dict[str, int] = {} - max_calls_per_tool = 3 + max_calls_per_tool = 3 #TBD if response.output: for tool_call in response.output: @@ -243,7 +243,7 @@ def run_graph_enrichment(question: str, qdrant_results: list[dict]) -> Neo4jEnri count = len(result) if isinstance(result, list) else None except Exception as e: logger.error(f"Neo4j tool {name} failed: {e}") - results[name] = f"Error: {e}" + results[name] = f"Error: {e}" #This is dangerous, we leak errors which can be showing some sensitive information result = None count = 0 tools_executed.append(ToolExecution(name=name, arguments=args, result_count=count, results=result)) diff --git a/src/biomedical_graphrag/application/services/hybrid_service/tools/__init__.py b/src/biomedical_graphrag/application/services/hybrid_service/tools/__init__.py index 99785a9..0bf6ea9 100644 --- a/src/biomedical_graphrag/application/services/hybrid_service/tools/__init__.py +++ b/src/biomedical_graphrag/application/services/hybrid_service/tools/__init__.py @@ -1,4 +1,4 @@ -"""Tools for hybrid GraphRAG querying.""" +"""Tool definitions for context engineering.""" from .enrichment_tools import NEO4J_ENRICHMENT_TOOLS from .qdrant_tools import QDRANT_TOOLS diff --git a/src/biomedical_graphrag/application/services/hybrid_service/tools/qdrant_tools.py b/src/biomedical_graphrag/application/services/hybrid_service/tools/qdrant_tools.py index 3012adc..843507d 100644 --- a/src/biomedical_graphrag/application/services/hybrid_service/tools/qdrant_tools.py +++ b/src/biomedical_graphrag/application/services/hybrid_service/tools/qdrant_tools.py @@ -1,9 +1,9 @@ """ -Qdrant vector-search tool definitions. +Qdrant tool definitions for hybrid retrieval and recommendations. """ # ---------------------------- -# Qdrant Vector Search tools definition +# Qdrant tools definition # ---------------------------- QDRANT_TOOLS = [ diff --git a/src/biomedical_graphrag/application/services/query_vectorstore_service/__init__.py b/src/biomedical_graphrag/application/services/query_vectorstore_service/__init__.py deleted file mode 100644 index 9cf0d1a..0000000 --- a/src/biomedical_graphrag/application/services/query_vectorstore_service/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Query vectorstore service for Qdrant operations.""" - -from .qdrant_query import AsyncQdrantQuery - -__all__ = ["AsyncQdrantQuery"] diff --git a/src/biomedical_graphrag/application/services/query_vectorstore_service/prompts/__init__.py b/src/biomedical_graphrag/application/services/query_vectorstore_service/prompts/__init__.py deleted file mode 100644 index 52bf0b0..0000000 --- a/src/biomedical_graphrag/application/services/query_vectorstore_service/prompts/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Prompts for Qdrant vectorstore querying.""" - -from .qdrant_prompts import QDRANT_GENERATION_PROMPT - -__all__ = ["QDRANT_GENERATION_PROMPT"] diff --git a/src/biomedical_graphrag/application/services/query_vectorstore_service/prompts/qdrant_prompts.py b/src/biomedical_graphrag/application/services/query_vectorstore_service/prompts/qdrant_prompts.py deleted file mode 100644 index fcd8529..0000000 --- a/src/biomedical_graphrag/application/services/query_vectorstore_service/prompts/qdrant_prompts.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Prompt templates for OpenAI Qdrant generation.""" - -QDRANT_GENERATION_PROMPT = """ -You are a biomedical research assistant. - -Generate an answer based on the following context from biomedical research papers. - -Use the context to answer the question as accurately as possible. - -Context: {context} -Question: {question} -Answer: -""" diff --git a/src/biomedical_graphrag/application/services/query_vectorstore_service/qdrant_query.py b/src/biomedical_graphrag/application/services/query_vectorstore_service/qdrant_query.py deleted file mode 100644 index 6d081c2..0000000 --- a/src/biomedical_graphrag/application/services/query_vectorstore_service/qdrant_query.py +++ /dev/null @@ -1,163 +0,0 @@ -from openai import AsyncOpenAI - -from biomedical_graphrag.application.services.query_vectorstore_service.prompts.qdrant_prompts import ( - QDRANT_GENERATION_PROMPT, -) -from biomedical_graphrag.config import settings -from biomedical_graphrag.infrastructure.qdrant_engine.qdrant_vectorstore import AsyncQdrantVectorStore -from biomedical_graphrag.utils.logger_util import setup_logging - -from qdrant_client.models import models - -logger = setup_logging() - - -class AsyncQdrantQuery: - """Handles querying Qdrant vector store for natural language questions (async).""" - - def __init__(self) -> None: - """ - Initialize the async Qdrant client with connection parameters. - """ - self.url = settings.qdrant.url - self.api_key = settings.qdrant.api_key - self.collection_name = settings.qdrant.collection_name - self.embedding_dimension = settings.qdrant.embedding_dimension - self.reranker_embedding_dimension = settings.qdrant.reranker_embedding_dimension - self.cloud_inference = settings.qdrant.cloud_inference - - self.openai_client = AsyncOpenAI(api_key=settings.openai.api_key.get_secret_value()) - - self.qdrant_client = AsyncQdrantVectorStore() - - async def close(self) -> None: - """Close the async Qdrant client.""" - await self.qdrant_client.close() - - async def retrieve_documents_dense(self, question: str, top_k: int = 3) -> list[dict]: - """ - Query the Qdrant vector store for similar documents (async). - Vanilla dense search on quantized openAI embeddings. - - Args: - question (str): Input question to query. - top_k (int): Number of top similar documents to retrieve. - Returns: - List of dictionaries containing the top_k similar documents. - """ - if self.cloud_inference: - dense_vector = self.qdrant_client._define_openai_vectors(question, dimensions=self.embedding_dimension) - else: - dense_vector = await self.qdrant_client._get_openai_vectors(question, dimensions=self.embedding_dimension) - - search_result = await self.qdrant_client.client.query_points( - collection_name=self.collection_name, - query=dense_vector, - using="Dense", - search_params=models.SearchParams( - quantization=models.QuantizationSearchParams( - oversampling=3.0, # retrieve 3 * top_k quantized vectors - rescore=True, # to rescore with original vectors - ) - ), - limit=top_k, - with_payload=True, - ) - results = [ - { - "id": point.id, - "score": point.score, - "payload": point.payload, - } - for point in search_result.points - ] - return results - - async def retrieve_documents_hybrid(self, question: str, top_k: int = 3) -> list[dict]: - """ - Query the Qdrant vector search engine (async). - Hybrid dense + lexical search, fused by reranking with text-embedding-3-large. - - Args: - question (str): Input question - query. - top_k (int): Number of top similar documents to retrieve. - Returns: - List of dictionaries containing the top_k similar documents. - """ - if self.cloud_inference: - retriever_vector = self.qdrant_client._define_openai_vectors( - question, dimensions=self.embedding_dimension - ) - reranker_vector = self.qdrant_client._define_openai_vectors( - question, dimensions=self.reranker_embedding_dimension - ) - else: - openai_vector = await self.qdrant_client._get_openai_vectors( - question, dimensions=self.reranker_embedding_dimension - ) - retriever_vector = openai_vector[:self.embedding_dimension] # Qdrant normalizes vectors used with COSINE automatically on upsert/query - reranker_vector = openai_vector # full precision for reranking - - sparse_vector = self.qdrant_client._define_bm25_vectors(question) - - search_result = await self.qdrant_client.client.query_points( - collection_name=self.collection_name, - prefetch=[ - models.Prefetch( - query=retriever_vector, - using="Dense", - params=models.SearchParams( - quantization=models.QuantizationSearchParams( - oversampling=3.0, # retrieve 3 * top_k quantized vectors - rescore=True, # to rescore with original vectors - ) - ), - limit=top_k, - ), - models.Prefetch( - query=sparse_vector, - using="Lexical", - limit=top_k, - ), - ], - # query=models.RrfQuery(rrf=models.Rrf(k=60)), - query=reranker_vector, - using="Reranker", - limit=top_k, - with_payload=True, - ) - - results = [ - { - "id": point.id, - "score": point.score, - "payload": point.payload, - } - for point in search_result.points - ] - return results - - async def get_answer(self, question: str) -> str: - """ - Get an answer to the question by retrieving relevant documents from Qdrant (async). - - Args: - question (str): The question to answer. - Returns: - str: The answer generated from the retrieved documents. - """ - - context = QDRANT_GENERATION_PROMPT.format( - question=question, - context="\n".join( - f"Content: {doc['payload']}" for doc in await self.retrieve_documents_dense(question) - ), - ) - logger.debug(f"Qdrant context: {context}") - - response = await self.openai_client.chat.completions.create( - model=settings.openai.model, - messages=[{"role": "user", "content": context}], - max_tokens=settings.openai.max_tokens, - ) - return response.choices[0].message.content or "" diff --git a/src/biomedical_graphrag/infrastructure/qdrant_engine/qdrant_ingestion.py b/src/biomedical_graphrag/infrastructure/qdrant_engine/qdrant_ingestion.py index 2fd3210..1051da0 100644 --- a/src/biomedical_graphrag/infrastructure/qdrant_engine/qdrant_ingestion.py +++ b/src/biomedical_graphrag/infrastructure/qdrant_engine/qdrant_ingestion.py @@ -52,4 +52,4 @@ async def ingest_data(recreate: bool = False, only_new: bool = True) -> None: if __name__ == "__main__": - asyncio.run(ingest_data(recreate=False, only_new=True)) + asyncio.run(ingest_data(recreate=True, only_new=False)) diff --git a/uv.lock b/uv.lock index 54afc80..0fd5ce6 100644 --- a/uv.lock +++ b/uv.lock @@ -46,8 +46,6 @@ dependencies = [ { name = "fastapi" }, { name = "loguru" }, { name = "neo4j" }, - { name = "neo4j-graphrag" }, - { name = "nest-asyncio" }, { name = "openai" }, { name = "pydantic" }, { name = "pydantic-settings" }, @@ -74,8 +72,6 @@ requires-dist = [ { name = "fastapi", specifier = ">=0.115.0" }, { name = "loguru", specifier = ">=0.7.3" }, { name = "neo4j", specifier = ">=5.28.2" }, - { name = "neo4j-graphrag", specifier = ">=1.10.0" }, - { name = "nest-asyncio", specifier = ">=1.5.8" }, { name = "openai", specifier = ">=1.0.0" }, { name = "pydantic", specifier = ">=2.12.0" }, { name = "pydantic-settings", specifier = ">=2.11.0" }, @@ -166,15 +162,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/05/5cbb59154b093548acd0f4c7c474a118eda06da25aa75c616b72d8fcd92a/fastapi-0.128.0-py3-none-any.whl", hash = "sha256:aebd93f9716ee3b4f4fcfe13ffb7cf308d99c9f3ab5622d8877441072561582d", size = 103094, upload-time = "2025-12-27T15:21:12.154Z" }, ] -[[package]] -name = "fsspec" -version = "2024.12.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/11/de70dee31455c546fbc88301971ec03c328f3d1138cfba14263f651e9551/fsspec-2024.12.0.tar.gz", hash = "sha256:670700c977ed2fb51e0d9f9253177ed20cbde4a3e5c0283cc5385b5870c8533f", size = 291600, upload-time = "2024-12-19T19:57:30.333Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/86/5486b0188d08aa643e127774a99bac51ffa6cf343e3deb0583956dca5b22/fsspec-2024.12.0-py3-none-any.whl", hash = "sha256:b520aed47ad9804237ff878b504267a3b0b441e97508bd6d2d8774e3db85cee2", size = 183862, upload-time = "2024-12-19T19:57:28.258Z" }, -] - [[package]] name = "grpcio" version = "1.75.1" @@ -333,15 +320,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/af/22/7ab7b4ec3a1c1f03aef376af11d23b05abcca3fb31fbca1e7557053b1ba2/jiter-0.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e2bbf24f16ba5ad4441a9845e40e4ea0cb9eed00e76ba94050664ef53ef4406", size = 347102, upload-time = "2025-09-15T09:20:20.16Z" }, ] -[[package]] -name = "json-repair" -version = "0.44.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a8/6b/ed6e92efc5acfbc9c35ccae1676b70e4adb1552421e64f838c2a3f097d9a/json_repair-0.44.1.tar.gz", hash = "sha256:1130eb9733b868dac1340b43cb2effebb519ae6d52dd2d0728c6cca517f1e0b4", size = 32886, upload-time = "2025-04-30T16:09:38.54Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/b4/3cbd27a3240b2962c3b87bbb1c20eb6c56e5b26cde61f141f86ca98e2f68/json_repair-0.44.1-py3-none-any.whl", hash = "sha256:51d82532c3b8263782a301eb7904c75dce5fee8c0d1aba490287fc0ab779ac50", size = 22478, upload-time = "2025-04-30T16:09:37.303Z" }, -] - [[package]] name = "loguru" version = "0.7.3" @@ -402,36 +380,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/00/1f74089c06aec1fac9390e2300a6a6b2381e0dac281783d64ccca9d681fd/neo4j-5.28.2-py3-none-any.whl", hash = "sha256:5c53b5c3eee6dee7e920c9724391aa38d7135a651e71b766da00533b92a91a94", size = 313156, upload-time = "2025-07-30T06:04:31.438Z" }, ] -[[package]] -name = "neo4j-graphrag" -version = "1.10.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "fsspec" }, - { name = "json-repair" }, - { name = "neo4j" }, - { name = "numpy", marker = "python_full_version < '3.14'" }, - { name = "pydantic" }, - { name = "pypdf" }, - { name = "pyyaml" }, - { name = "scipy", marker = "python_full_version < '3.14'" }, - { name = "tenacity" }, - { name = "types-pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/20/ea/472d83d19a5da46957ff8a427861aa3954a424e8f4273023e87a940ee0f4/neo4j_graphrag-1.10.0.tar.gz", hash = "sha256:b74d57ebcaebb64b8496f7cb3cb8a3a3dd8d8f3b0df277635eade4584e7606a8", size = 121640, upload-time = "2025-09-04T13:10:05.673Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/38/71757c01c627402647c13a87f342b697e445623e21ad2aace3a4142b6bf9/neo4j_graphrag-1.10.0-py3-none-any.whl", hash = "sha256:7e016c5bf281df545309ff139c04b9a3ac426056bb68ba6a5508fa6578650f9d", size = 201746, upload-time = "2025-09-04T13:10:03.888Z" }, -] - -[[package]] -name = "nest-asyncio" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, -] - [[package]] name = "numpy" version = "2.3.3" @@ -665,15 +613,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] -[[package]] -name = "pypdf" -version = "6.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/85/4c0f12616db83c2e3ef580c3cfa98bd082e88fc8d02e136bad3bede1e3fa/pypdf-6.1.1.tar.gz", hash = "sha256:10f44d49bf2a82e54c3c5ba3cdcbb118f2a44fc57df8ce51d6fb9b1ed9bfbe8b", size = 5074507, upload-time = "2025-09-28T13:29:16.165Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/ed/adae13756d9dabdddee483fc7712905bb5585fbf6e922b1a19aca3a29cd1/pypdf-6.1.1-py3-none-any.whl", hash = "sha256:7781f99493208a37a7d4275601d883e19af24e62a525c25844d22157c2e4cde7", size = 323455, upload-time = "2025-09-28T13:29:14.392Z" }, -] - [[package]] name = "pytest" version = "8.4.2" @@ -733,42 +672,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, ] -[[package]] -name = "pyyaml" -version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, -] - [[package]] name = "qdrant-client" version = "1.16.2" @@ -813,57 +716,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/2a/65880dfd0e13f7f13a775998f34703674a4554906167dce02daf7865b954/ruff-0.14.0-py3-none-win_arm64.whl", hash = "sha256:f42c9495f5c13ff841b1da4cb3c2a42075409592825dada7c5885c2c844ac730", size = 12565142, upload-time = "2025-10-07T18:21:53.577Z" }, ] -[[package]] -name = "scipy" -version = "1.16.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy", marker = "python_full_version < '3.14'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4c/3b/546a6f0bfe791bbb7f8d591613454d15097e53f906308ec6f7c1ce588e8e/scipy-1.16.2.tar.gz", hash = "sha256:af029b153d243a80afb6eabe40b0a07f8e35c9adc269c019f364ad747f826a6b", size = 30580599, upload-time = "2025-09-11T17:48:08.271Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:84f7bf944b43e20b8a894f5fe593976926744f6c185bacfcbdfbb62736b5cc70", size = 36604856, upload-time = "2025-09-11T17:41:47.695Z" }, - { url = "https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5c39026d12edc826a1ef2ad35ad1e6d7f087f934bb868fc43fa3049c8b8508f9", size = 28864626, upload-time = "2025-09-11T17:41:52.642Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fc/ea36098df653cca26062a627c1a94b0de659e97127c8491e18713ca0e3b9/scipy-1.16.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e52729ffd45b68777c5319560014d6fd251294200625d9d70fd8626516fc49f5", size = 20855689, upload-time = "2025-09-11T17:41:57.886Z" }, - { url = "https://files.pythonhosted.org/packages/dc/6f/d0b53be55727f3e6d7c72687ec18ea6d0047cf95f1f77488b99a2bafaee1/scipy-1.16.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:024dd4a118cccec09ca3209b7e8e614931a6ffb804b2a601839499cb88bdf925", size = 23512151, upload-time = "2025-09-11T17:42:02.303Z" }, - { url = "https://files.pythonhosted.org/packages/11/85/bf7dab56e5c4b1d3d8eef92ca8ede788418ad38a7dc3ff50262f00808760/scipy-1.16.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7a5dc7ee9c33019973a470556081b0fd3c9f4c44019191039f9769183141a4d9", size = 33329824, upload-time = "2025-09-11T17:42:07.549Z" }, - { url = "https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c2275ff105e508942f99d4e3bc56b6ef5e4b3c0af970386ca56b777608ce95b7", size = 35681881, upload-time = "2025-09-11T17:42:13.255Z" }, - { url = "https://files.pythonhosted.org/packages/c1/5f/331148ea5780b4fcc7007a4a6a6ee0a0c1507a796365cc642d4d226e1c3a/scipy-1.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:af80196eaa84f033e48444d2e0786ec47d328ba00c71e4299b602235ffef9acb", size = 36006219, upload-time = "2025-09-11T17:42:18.765Z" }, - { url = "https://files.pythonhosted.org/packages/46/3a/e991aa9d2aec723b4a8dcfbfc8365edec5d5e5f9f133888067f1cbb7dfc1/scipy-1.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9fb1eb735fe3d6ed1f89918224e3385fbf6f9e23757cacc35f9c78d3b712dd6e", size = 38682147, upload-time = "2025-09-11T17:42:25.177Z" }, - { url = "https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:fda714cf45ba43c9d3bae8f2585c777f64e3f89a2e073b668b32ede412d8f52c", size = 38520766, upload-time = "2025-09-11T17:43:25.342Z" }, - { url = "https://files.pythonhosted.org/packages/1b/a5/85d3e867b6822d331e26c862a91375bb7746a0b458db5effa093d34cdb89/scipy-1.16.2-cp313-cp313-win_arm64.whl", hash = "sha256:2f5350da923ccfd0b00e07c3e5cfb316c1c0d6c1d864c07a72d092e9f20db104", size = 25451169, upload-time = "2025-09-11T17:43:30.198Z" }, - { url = "https://files.pythonhosted.org/packages/09/d9/60679189bcebda55992d1a45498de6d080dcaf21ce0c8f24f888117e0c2d/scipy-1.16.2-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:53d8d2ee29b925344c13bda64ab51785f016b1b9617849dac10897f0701b20c1", size = 37012682, upload-time = "2025-09-11T17:42:30.677Z" }, - { url = "https://files.pythonhosted.org/packages/83/be/a99d13ee4d3b7887a96f8c71361b9659ba4ef34da0338f14891e102a127f/scipy-1.16.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:9e05e33657efb4c6a9d23bd8300101536abd99c85cca82da0bffff8d8764d08a", size = 29389926, upload-time = "2025-09-11T17:42:35.845Z" }, - { url = "https://files.pythonhosted.org/packages/bf/0a/130164a4881cec6ca8c00faf3b57926f28ed429cd6001a673f83c7c2a579/scipy-1.16.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:7fe65b36036357003b3ef9d37547abeefaa353b237e989c21027b8ed62b12d4f", size = 21381152, upload-time = "2025-09-11T17:42:40.07Z" }, - { url = "https://files.pythonhosted.org/packages/47/a6/503ffb0310ae77fba874e10cddfc4a1280bdcca1d13c3751b8c3c2996cf8/scipy-1.16.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6406d2ac6d40b861cccf57f49592f9779071655e9f75cd4f977fa0bdd09cb2e4", size = 23914410, upload-time = "2025-09-11T17:42:44.313Z" }, - { url = "https://files.pythonhosted.org/packages/fa/c7/1147774bcea50d00c02600aadaa919facbd8537997a62496270133536ed6/scipy-1.16.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff4dc42bd321991fbf611c23fc35912d690f731c9914bf3af8f417e64aca0f21", size = 33481880, upload-time = "2025-09-11T17:42:49.325Z" }, - { url = "https://files.pythonhosted.org/packages/6a/74/99d5415e4c3e46b2586f30cdbecb95e101c7192628a484a40dd0d163811a/scipy-1.16.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:654324826654d4d9133e10675325708fb954bc84dae6e9ad0a52e75c6b1a01d7", size = 35791425, upload-time = "2025-09-11T17:42:54.711Z" }, - { url = "https://files.pythonhosted.org/packages/1b/ee/a6559de7c1cc710e938c0355d9d4fbcd732dac4d0d131959d1f3b63eb29c/scipy-1.16.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63870a84cd15c44e65220eaed2dac0e8f8b26bbb991456a033c1d9abfe8a94f8", size = 36178622, upload-time = "2025-09-11T17:43:00.375Z" }, - { url = "https://files.pythonhosted.org/packages/4e/7b/f127a5795d5ba8ece4e0dce7d4a9fb7cb9e4f4757137757d7a69ab7d4f1a/scipy-1.16.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fa01f0f6a3050fa6a9771a95d5faccc8e2f5a92b4a2e5440a0fa7264a2398472", size = 38783985, upload-time = "2025-09-11T17:43:06.661Z" }, - { url = "https://files.pythonhosted.org/packages/3e/9f/bc81c1d1e033951eb5912cd3750cc005943afa3e65a725d2443a3b3c4347/scipy-1.16.2-cp313-cp313t-win_amd64.whl", hash = "sha256:116296e89fba96f76353a8579820c2512f6e55835d3fad7780fece04367de351", size = 38631367, upload-time = "2025-09-11T17:43:14.44Z" }, - { url = "https://files.pythonhosted.org/packages/d6/5e/2cc7555fd81d01814271412a1d59a289d25f8b63208a0a16c21069d55d3e/scipy-1.16.2-cp313-cp313t-win_arm64.whl", hash = "sha256:98e22834650be81d42982360382b43b17f7ba95e0e6993e2a4f5b9ad9283a94d", size = 25787992, upload-time = "2025-09-11T17:43:19.745Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ac/ad8951250516db71619f0bd3b2eb2448db04b720a003dd98619b78b692c0/scipy-1.16.2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:567e77755019bb7461513c87f02bb73fb65b11f049aaaa8ca17cfaa5a5c45d77", size = 36595109, upload-time = "2025-09-11T17:43:35.713Z" }, - { url = "https://files.pythonhosted.org/packages/ff/f6/5779049ed119c5b503b0f3dc6d6f3f68eefc3a9190d4ad4c276f854f051b/scipy-1.16.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:17d9bb346194e8967296621208fcdfd39b55498ef7d2f376884d5ac47cec1a70", size = 28859110, upload-time = "2025-09-11T17:43:40.814Z" }, - { url = "https://files.pythonhosted.org/packages/82/09/9986e410ae38bf0a0c737ff8189ac81a93b8e42349aac009891c054403d7/scipy-1.16.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:0a17541827a9b78b777d33b623a6dcfe2ef4a25806204d08ead0768f4e529a88", size = 20850110, upload-time = "2025-09-11T17:43:44.981Z" }, - { url = "https://files.pythonhosted.org/packages/0d/ad/485cdef2d9215e2a7df6d61b81d2ac073dfacf6ae24b9ae87274c4e936ae/scipy-1.16.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:d7d4c6ba016ffc0f9568d012f5f1eb77ddd99412aea121e6fa8b4c3b7cbad91f", size = 23497014, upload-time = "2025-09-11T17:43:49.074Z" }, - { url = "https://files.pythonhosted.org/packages/a7/74/f6a852e5d581122b8f0f831f1d1e32fb8987776ed3658e95c377d308ed86/scipy-1.16.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9702c4c023227785c779cba2e1d6f7635dbb5b2e0936cdd3a4ecb98d78fd41eb", size = 33401155, upload-time = "2025-09-11T17:43:54.661Z" }, - { url = "https://files.pythonhosted.org/packages/d9/f5/61d243bbc7c6e5e4e13dde9887e84a5cbe9e0f75fd09843044af1590844e/scipy-1.16.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1cdf0ac28948d225decdefcc45ad7dd91716c29ab56ef32f8e0d50657dffcc7", size = 35691174, upload-time = "2025-09-11T17:44:00.101Z" }, - { url = "https://files.pythonhosted.org/packages/03/99/59933956331f8cc57e406cdb7a483906c74706b156998f322913e789c7e1/scipy-1.16.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:70327d6aa572a17c2941cdfb20673f82e536e91850a2e4cb0c5b858b690e1548", size = 36070752, upload-time = "2025-09-11T17:44:05.619Z" }, - { url = "https://files.pythonhosted.org/packages/c6/7d/00f825cfb47ee19ef74ecf01244b43e95eae74e7e0ff796026ea7cd98456/scipy-1.16.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5221c0b2a4b58aa7c4ed0387d360fd90ee9086d383bb34d9f2789fafddc8a936", size = 38701010, upload-time = "2025-09-11T17:44:11.322Z" }, - { url = "https://files.pythonhosted.org/packages/e4/9f/b62587029980378304ba5a8563d376c96f40b1e133daacee76efdcae32de/scipy-1.16.2-cp314-cp314-win_amd64.whl", hash = "sha256:f5a85d7b2b708025af08f060a496dd261055b617d776fc05a1a1cc69e09fe9ff", size = 39360061, upload-time = "2025-09-11T17:45:09.814Z" }, - { url = "https://files.pythonhosted.org/packages/82/04/7a2f1609921352c7fbee0815811b5050582f67f19983096c4769867ca45f/scipy-1.16.2-cp314-cp314-win_arm64.whl", hash = "sha256:2cc73a33305b4b24556957d5857d6253ce1e2dcd67fa0ff46d87d1670b3e1e1d", size = 26126914, upload-time = "2025-09-11T17:45:14.73Z" }, - { url = "https://files.pythonhosted.org/packages/51/b9/60929ce350c16b221928725d2d1d7f86cf96b8bc07415547057d1196dc92/scipy-1.16.2-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:9ea2a3fed83065d77367775d689401a703d0f697420719ee10c0780bcab594d8", size = 37013193, upload-time = "2025-09-11T17:44:16.757Z" }, - { url = "https://files.pythonhosted.org/packages/2a/41/ed80e67782d4bc5fc85a966bc356c601afddd175856ba7c7bb6d9490607e/scipy-1.16.2-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7280d926f11ca945c3ef92ba960fa924e1465f8d07ce3a9923080363390624c4", size = 29390172, upload-time = "2025-09-11T17:44:21.783Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a3/2f673ace4090452696ccded5f5f8efffb353b8f3628f823a110e0170b605/scipy-1.16.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:8afae1756f6a1fe04636407ef7dbece33d826a5d462b74f3d0eb82deabefd831", size = 21381326, upload-time = "2025-09-11T17:44:25.982Z" }, - { url = "https://files.pythonhosted.org/packages/42/bf/59df61c5d51395066c35836b78136accf506197617c8662e60ea209881e1/scipy-1.16.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:5c66511f29aa8d233388e7416a3f20d5cae7a2744d5cee2ecd38c081f4e861b3", size = 23915036, upload-time = "2025-09-11T17:44:30.527Z" }, - { url = "https://files.pythonhosted.org/packages/91/c3/edc7b300dc16847ad3672f1a6f3f7c5d13522b21b84b81c265f4f2760d4a/scipy-1.16.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efe6305aeaa0e96b0ccca5ff647a43737d9a092064a3894e46c414db84bc54ac", size = 33484341, upload-time = "2025-09-11T17:44:35.981Z" }, - { url = "https://files.pythonhosted.org/packages/26/c7/24d1524e72f06ff141e8d04b833c20db3021020563272ccb1b83860082a9/scipy-1.16.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f3a337d9ae06a1e8d655ee9d8ecb835ea5ddcdcbd8d23012afa055ab014f374", size = 35790840, upload-time = "2025-09-11T17:44:41.76Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b7/5aaad984eeedd56858dc33d75efa59e8ce798d918e1033ef62d2708f2c3d/scipy-1.16.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bab3605795d269067d8ce78a910220262711b753de8913d3deeaedb5dded3bb6", size = 36174716, upload-time = "2025-09-11T17:44:47.316Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c2/e276a237acb09824822b0ada11b028ed4067fdc367a946730979feacb870/scipy-1.16.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b0348d8ddb55be2a844c518cd8cc8deeeb8aeba707cf834db5758fc89b476a2c", size = 38790088, upload-time = "2025-09-11T17:44:53.011Z" }, - { url = "https://files.pythonhosted.org/packages/c6/b4/5c18a766e8353015439f3780f5fc473f36f9762edc1a2e45da3ff5a31b21/scipy-1.16.2-cp314-cp314t-win_amd64.whl", hash = "sha256:26284797e38b8a75e14ea6631d29bda11e76ceaa6ddb6fdebbfe4c4d90faf2f9", size = 39457455, upload-time = "2025-09-11T17:44:58.899Z" }, - { url = "https://files.pythonhosted.org/packages/97/30/2f9a5243008f76dfc5dee9a53dfb939d9b31e16ce4bd4f2e628bfc5d89d2/scipy-1.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d2a4472c231328d4de38d5f1f68fdd6d28a615138f842580a8a321b5845cf779", size = 26448374, upload-time = "2025-09-11T17:45:03.45Z" }, -] - [[package]] name = "sniffio" version = "1.3.1" @@ -885,15 +737,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, ] -[[package]] -name = "tenacity" -version = "9.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, -] - [[package]] name = "tqdm" version = "4.67.1" @@ -906,15 +749,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, ] -[[package]] -name = "types-pyyaml" -version = "6.0.12.20250915" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0"