From 1c6d942177ad82b27993e5ec813b76cc3ae1f5a6 Mon Sep 17 00:00:00 2001 From: Anibal Angulo Date: Mon, 23 Feb 2026 03:29:21 +0000 Subject: [PATCH] Lean MCP implementation --- AGENTS.md | 1 + pyproject.toml | 24 +- rag_agent/agent.py | 59 - rag_agent/base.py | 68 - rag_agent/config_helper.py | 120 -- rag_agent/file_storage/__init__.py | 1 - rag_agent/file_storage/base.py | 56 - rag_agent/file_storage/google_cloud.py | 188 -- rag_agent/vector_search_tool.py | 176 -- rag_agent/vertex_ai.py | 310 ---- scripts/diagnose_embeddings.py | 79 - scripts/diagnose_rag_endpoint.py | 99 -- scripts/stress_test.py | 91 - scripts/submit_pipeline.py | 84 - scripts/test_rerank.py | 42 - scripts/test_server.py | 12 - src/rag_eval/__init__.py | 1 - src/rag_eval/agent.py | 92 - src/rag_eval/config.py | 92 - src/rag_eval/file_storage/__init__.py | 1 - src/rag_eval/file_storage/base.py | 56 - src/rag_eval/file_storage/google_cloud.py | 188 -- src/rag_eval/logging.py | 47 - src/rag_eval/server.py | 61 - src/rag_eval/vector_search/__init__.py | 1 - src/rag_eval/vector_search/base.py | 68 - src/rag_eval/vector_search/vertex_ai.py | 310 ---- {rag_agent => src/va_agent}/__init__.py | 4 - src/va_agent/agent.py | 29 + src/va_agent/config.py | 53 + src/va_agent/server.py | 10 + src/va_agent/session.py | 582 +++++++ tests/__init__.py | 1 + tests/conftest.py | 35 + tests/test_compaction.py | 515 ++++++ tests/test_firestore_session_service.py | 428 +++++ uv.lock | 1937 ++++++++------------- 37 files changed, 2380 insertions(+), 3541 deletions(-) delete mode 100644 rag_agent/agent.py delete mode 100644 rag_agent/base.py delete mode 100644 rag_agent/config_helper.py delete mode 100644 rag_agent/file_storage/__init__.py delete mode 100644 rag_agent/file_storage/base.py delete mode 100644 rag_agent/file_storage/google_cloud.py delete mode 100644 rag_agent/vector_search_tool.py delete mode 100644 rag_agent/vertex_ai.py delete mode 100644 scripts/diagnose_embeddings.py delete mode 100644 scripts/diagnose_rag_endpoint.py delete mode 100644 scripts/stress_test.py delete mode 100644 scripts/submit_pipeline.py delete mode 100644 scripts/test_rerank.py delete mode 100644 scripts/test_server.py delete mode 100644 src/rag_eval/__init__.py delete mode 100644 src/rag_eval/agent.py delete mode 100644 src/rag_eval/config.py delete mode 100644 src/rag_eval/file_storage/__init__.py delete mode 100644 src/rag_eval/file_storage/base.py delete mode 100644 src/rag_eval/file_storage/google_cloud.py delete mode 100644 src/rag_eval/logging.py delete mode 100644 src/rag_eval/server.py delete mode 100644 src/rag_eval/vector_search/__init__.py delete mode 100644 src/rag_eval/vector_search/base.py delete mode 100644 src/rag_eval/vector_search/vertex_ai.py rename {rag_agent => src/va_agent}/__init__.py (77%) create mode 100644 src/va_agent/agent.py create mode 100644 src/va_agent/config.py create mode 100644 src/va_agent/server.py create mode 100644 src/va_agent/session.py create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/test_compaction.py create mode 100644 tests/test_firestore_session_service.py diff --git a/AGENTS.md b/AGENTS.md index 9ac8834..3434537 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,2 +1,3 @@ Use `uv` for project management. Use `uv run ruff check` for linting, and `uv run ty check` for type checking +Use `uv run pytest` for testing. diff --git a/pyproject.toml b/pyproject.toml index b27bf29..cf5937a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "rag-eval" +name = "va-agent" version = "0.1.0" description = "Add your description here" readme = "README.md" @@ -9,28 +9,20 @@ authors = [ ] requires-python = "~=3.12.0" dependencies = [ - "aiohttp>=3.13.3", - "gcloud-aio-auth>=5.4.2", - "gcloud-aio-storage>=9.6.1", "google-adk>=1.14.1", - "google-cloud-aiplatform>=1.126.1", - "google-cloud-storage>=2.19.0", + "google-cloud-firestore>=2.23.0", "pydantic-settings[yaml]>=2.13.1", - "structlog>=25.5.0", ] -[project.scripts] -ragops = "rag_eval.cli:app" - [build-system] requires = ["uv_build>=0.8.3,<0.9.0"] build-backend = "uv_build" [dependency-groups] dev = [ - "clai>=1.62.0", - "marimo>=0.20.1", "pytest>=8.4.1", + "pytest-asyncio>=1.3.0", + "pytest-sugar>=1.1.1", "ruff>=0.12.10", "ty>=0.0.1a19", ] @@ -43,4 +35,10 @@ exclude = ["scripts"] [tool.ruff.lint] select = ['ALL'] -ignore = ['D203', 'D213', 'COM812'] +ignore = [ + 'D203', # one-blank-line-before-class + 'D213', # multi-line-summary-second-line + 'COM812', # missing-trailing-comma + 'ANN401', # dynamically-typed-any + 'ERA001', # commented-out-code +] diff --git a/rag_agent/agent.py b/rag_agent/agent.py deleted file mode 100644 index afbc6ec..0000000 --- a/rag_agent/agent.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""ADK agent with vector search RAG tool.""" - -from __future__ import annotations - -import os - -from google.adk.agents.llm_agent import Agent - -from .config_helper import settings -from .vector_search_tool import VectorSearchTool - -# Set environment variables for Google GenAI Client to use Vertex AI -os.environ["GOOGLE_CLOUD_PROJECT"] = settings.project_id -os.environ["GOOGLE_CLOUD_LOCATION"] = settings.location - -# Create vector search tool with configuration -vector_search_tool = VectorSearchTool( - name='conocimiento', - description='Search the vector index for company products and services information', - embedder=settings.embedder, - project_id=settings.project_id, - location=settings.location, - bucket=settings.bucket, - index_name=settings.index_name, - index_endpoint=settings.index_endpoint, - index_deployed_id=settings.index_deployed_id, - similarity_top_k=5, - min_similarity_threshold=0.6, - relative_threshold_factor=0.9, -) - -# Create agent with vector search tool -# Configure model with Vertex AI fully qualified path -model_path = ( - f'projects/{settings.project_id}/locations/{settings.location}/' - f'publishers/google/models/{settings.agent_language_model}' -) - -root_agent = Agent( - model=model_path, - name=settings.agent_name, - description='A helpful assistant for user questions.', - instruction=settings.agent_instructions, - tools=[vector_search_tool], -) diff --git a/rag_agent/base.py b/rag_agent/base.py deleted file mode 100644 index ab00142..0000000 --- a/rag_agent/base.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Abstract base class for vector search providers.""" - -from abc import ABC, abstractmethod -from typing import Any, TypedDict - - -class SearchResult(TypedDict): - """A single vector search result.""" - - id: str - distance: float - content: str - - -class BaseVectorSearch(ABC): - """Abstract base class for a vector search provider. - - This class defines the standard interface for creating a vector search - index and running queries against it. - """ - - @abstractmethod - def create_index( - self, name: str, content_path: str, **kwargs: Any # noqa: ANN401 - ) -> None: - """Create a new vector search index with the provided content. - - Args: - name: The desired name for the new index. - content_path: Path to the data used to populate the index. - **kwargs: Additional provider-specific arguments. - - """ - ... - - @abstractmethod - def update_index( - self, index_name: str, content_path: str, **kwargs: Any # noqa: ANN401 - ) -> None: - """Update an existing vector search index with new content. - - Args: - index_name: The name of the index to update. - content_path: Path to the data used to populate the index. - **kwargs: Additional provider-specific arguments. - - """ - ... - - @abstractmethod - def run_query( - self, - deployed_index_id: str, - query: list[float], - limit: int, - ) -> list[SearchResult]: - """Run a similarity search query against the index. - - Args: - deployed_index_id: The ID of the deployed index. - query: The embedding vector for the search query. - limit: Maximum number of nearest neighbors to return. - - Returns: - A list of matched items with id, distance, and content. - - """ - ... diff --git a/rag_agent/config_helper.py b/rag_agent/config_helper.py deleted file mode 100644 index 1f773bf..0000000 --- a/rag_agent/config_helper.py +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Configuration helper for ADK agent with vector search.""" - -from __future__ import annotations - -import os -from dataclasses import dataclass -from functools import cached_property - -import vertexai -from pydantic_settings import ( - BaseSettings, - PydanticBaseSettingsSource, - SettingsConfigDict, - YamlConfigSettingsSource, -) -from vertexai.language_models import TextEmbeddingModel - -CONFIG_FILE_PATH = os.getenv("CONFIG_YAML", "config.yaml") - - -@dataclass -class EmbeddingResult: - """Result from embedding a query.""" - - embeddings: list[list[float]] - - -class VertexAIEmbedder: - """Embedder using Vertex AI TextEmbeddingModel.""" - - def __init__(self, model_name: str, project_id: str, location: str) -> None: - """Initialize the embedder. - - Args: - model_name: Name of the embedding model (e.g., 'text-embedding-004') - project_id: GCP project ID - location: GCP location - - """ - vertexai.init(project=project_id, location=location) - self.model = TextEmbeddingModel.from_pretrained(model_name) - - async def embed_query(self, query: str) -> EmbeddingResult: - """Embed a single query string. - - Args: - query: Text to embed - - Returns: - EmbeddingResult with embeddings list - - """ - embeddings = self.model.get_embeddings([query]) - return EmbeddingResult(embeddings=[list(embeddings[0].values)]) - - -class AgentSettings(BaseSettings): - """Settings for ADK agent with vector search.""" - - # Google Cloud settings - project_id: str - location: str - bucket: str - - # Agent configuration - agent_name: str - agent_instructions: str - agent_language_model: str - agent_embedding_model: str - - # Vector index configuration - index_name: str - index_deployed_id: str - index_endpoint: str - - model_config = SettingsConfigDict( - yaml_file=CONFIG_FILE_PATH, - extra="ignore", # Ignore extra fields from config.yaml - ) - - @classmethod - def settings_customise_sources( - cls, - settings_cls: type[BaseSettings], - init_settings: PydanticBaseSettingsSource, # noqa: ARG003 - env_settings: PydanticBaseSettingsSource, - dotenv_settings: PydanticBaseSettingsSource, # noqa: ARG003 - file_secret_settings: PydanticBaseSettingsSource, # noqa: ARG003 - ) -> tuple[PydanticBaseSettingsSource, ...]: - """Use env vars and YAML as settings sources.""" - return ( - env_settings, - YamlConfigSettingsSource(settings_cls), - ) - - @cached_property - def embedder(self) -> VertexAIEmbedder: - """Return an embedder configured for the agent's embedding model.""" - return VertexAIEmbedder( - model_name=self.agent_embedding_model, - project_id=self.project_id, - location=self.location, - ) - - -settings = AgentSettings.model_validate({}) diff --git a/rag_agent/file_storage/__init__.py b/rag_agent/file_storage/__init__.py deleted file mode 100644 index 7f88e76..0000000 --- a/rag_agent/file_storage/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""File storage provider implementations.""" diff --git a/rag_agent/file_storage/base.py b/rag_agent/file_storage/base.py deleted file mode 100644 index a98ea39..0000000 --- a/rag_agent/file_storage/base.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Abstract base class for file storage providers.""" - -from abc import ABC, abstractmethod -from typing import BinaryIO - - -class BaseFileStorage(ABC): - """Abstract base class for a remote file processor. - - Defines the interface for listing and processing files from - a remote source. - """ - - @abstractmethod - def upload_file( - self, - file_path: str, - destination_blob_name: str, - content_type: str | None = None, - ) -> None: - """Upload a file to the remote source. - - Args: - file_path: The local path to the file to upload. - destination_blob_name: Name of the file in remote storage. - content_type: The content type of the file. - - """ - ... - - @abstractmethod - def list_files(self, path: str | None = None) -> list[str]: - """List files from a remote location. - - Args: - path: Path to a specific file or directory. If None, - recursively lists all files in the bucket. - - Returns: - A list of file paths. - - """ - ... - - @abstractmethod - def get_file_stream(self, file_name: str) -> BinaryIO: - """Get a file from the remote source as a file-like object. - - Args: - file_name: The name of the file to retrieve. - - Returns: - A file-like object containing the file data. - - """ - ... diff --git a/rag_agent/file_storage/google_cloud.py b/rag_agent/file_storage/google_cloud.py deleted file mode 100644 index 7848d97..0000000 --- a/rag_agent/file_storage/google_cloud.py +++ /dev/null @@ -1,188 +0,0 @@ -"""Google Cloud Storage file storage implementation.""" - -import asyncio -import io -import logging -from typing import BinaryIO - -import aiohttp -from gcloud.aio.storage import Storage -from google.cloud import storage - -from .base import BaseFileStorage - -logger = logging.getLogger(__name__) - -HTTP_TOO_MANY_REQUESTS = 429 -HTTP_SERVER_ERROR = 500 - - -class GoogleCloudFileStorage(BaseFileStorage): - """File storage backed by Google Cloud Storage.""" - - def __init__(self, bucket: str) -> None: # noqa: D107 - self.bucket_name = bucket - - self.storage_client = storage.Client() - self.bucket_client = self.storage_client.bucket(self.bucket_name) - self._aio_session: aiohttp.ClientSession | None = None - self._aio_storage: Storage | None = None - self._cache: dict[str, bytes] = {} - - def upload_file( - self, - file_path: str, - destination_blob_name: str, - content_type: str | None = None, - ) -> None: - """Upload a file to Cloud Storage. - - Args: - file_path: The local path to the file to upload. - destination_blob_name: Name of the blob in the bucket. - content_type: The content type of the file. - - """ - blob = self.bucket_client.blob(destination_blob_name) - blob.upload_from_filename( - file_path, - content_type=content_type, - if_generation_match=0, - ) - self._cache.pop(destination_blob_name, None) - - def list_files(self, path: str | None = None) -> list[str]: - """List all files at the given path in the bucket. - - If path is None, recursively lists all files. - - Args: - path: Prefix to filter files by. - - Returns: - A list of blob names. - - """ - blobs = self.storage_client.list_blobs( - self.bucket_name, prefix=path, - ) - return [blob.name for blob in blobs] - - def get_file_stream(self, file_name: str) -> BinaryIO: - """Get a file as a file-like object, using cache. - - Args: - file_name: The blob name to retrieve. - - Returns: - A BytesIO stream with the file contents. - - """ - if file_name not in self._cache: - blob = self.bucket_client.blob(file_name) - self._cache[file_name] = blob.download_as_bytes() - file_stream = io.BytesIO(self._cache[file_name]) - file_stream.name = file_name - return file_stream - - def _get_aio_session(self) -> aiohttp.ClientSession: - if self._aio_session is None or self._aio_session.closed: - connector = aiohttp.TCPConnector( - limit=300, limit_per_host=50, - ) - timeout = aiohttp.ClientTimeout(total=60) - self._aio_session = aiohttp.ClientSession( - timeout=timeout, connector=connector, - ) - return self._aio_session - - def _get_aio_storage(self) -> Storage: - if self._aio_storage is None: - self._aio_storage = Storage( - session=self._get_aio_session(), - ) - return self._aio_storage - - async def async_get_file_stream( - self, file_name: str, max_retries: int = 3, - ) -> BinaryIO: - """Get a file asynchronously with retry on transient errors. - - Args: - file_name: The blob name to retrieve. - max_retries: Maximum number of retry attempts. - - Returns: - A BytesIO stream with the file contents. - - Raises: - TimeoutError: If all retry attempts fail. - - """ - if file_name in self._cache: - file_stream = io.BytesIO(self._cache[file_name]) - file_stream.name = file_name - return file_stream - - storage_client = self._get_aio_storage() - last_exception: Exception | None = None - - for attempt in range(max_retries): - try: - self._cache[file_name] = await storage_client.download( - self.bucket_name, file_name, - ) - file_stream = io.BytesIO(self._cache[file_name]) - file_stream.name = file_name - except TimeoutError as exc: - last_exception = exc - logger.warning( - "Timeout downloading gs://%s/%s (attempt %d/%d)", - self.bucket_name, - file_name, - attempt + 1, - max_retries, - ) - except aiohttp.ClientResponseError as exc: - last_exception = exc - if ( - exc.status == HTTP_TOO_MANY_REQUESTS - or exc.status >= HTTP_SERVER_ERROR - ): - logger.warning( - "HTTP %d downloading gs://%s/%s " - "(attempt %d/%d)", - exc.status, - self.bucket_name, - file_name, - attempt + 1, - max_retries, - ) - else: - raise - else: - return file_stream - - if attempt < max_retries - 1: - delay = 0.5 * (2**attempt) - await asyncio.sleep(delay) - - msg = ( - f"Failed to download gs://{self.bucket_name}/{file_name} " - f"after {max_retries} attempts" - ) - raise TimeoutError(msg) from last_exception - - def delete_files(self, path: str) -> None: - """Delete all files at the given path in the bucket. - - Args: - path: Prefix of blobs to delete. - - """ - blobs = self.storage_client.list_blobs( - self.bucket_name, prefix=path, - ) - for blob in blobs: - blob.delete() - self._cache.pop(blob.name, None) diff --git a/rag_agent/vector_search_tool.py b/rag_agent/vector_search_tool.py deleted file mode 100644 index 1393065..0000000 --- a/rag_agent/vector_search_tool.py +++ /dev/null @@ -1,176 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""A retrieval tool that uses Vertex AI Vector Search (not RAG Engine).""" - -from __future__ import annotations - -import logging -from typing import Any -from typing import TYPE_CHECKING - -from google.adk.tools.tool_context import ToolContext -from typing_extensions import override - -from .vertex_ai import GoogleCloudVectorSearch - -from google.adk.tools.retrieval.base_retrieval_tool import BaseRetrievalTool - -if TYPE_CHECKING: - from .config_helper import VertexAIEmbedder - -logger = logging.getLogger('google_adk.' + __name__) - - -class VectorSearchTool(BaseRetrievalTool): - """A retrieval tool using Vertex AI Vector Search (not RAG Engine). - - This tool uses GoogleCloudVectorSearch to query a vector index directly, - which is useful when Vertex AI RAG Engine is not available in your GCP project. - """ - - def __init__( - self, - *, - name: str, - description: str, - embedder: VertexAIEmbedder, - project_id: str, - location: str, - bucket: str, - index_name: str, - index_endpoint: str, - index_deployed_id: str, - similarity_top_k: int = 5, - min_similarity_threshold: float = 0.6, - relative_threshold_factor: float = 0.9, - ): - """Initialize the VectorSearchTool. - - Args: - name: Tool name for function declaration - description: Tool description for LLM - embedder: Embedder instance for query embedding - project_id: GCP project ID - location: GCP location (e.g., 'us-central1') - bucket: GCS bucket for content storage - index_name: Vector search index name - index_endpoint: Resource name of index endpoint - index_deployed_id: Deployed index ID - similarity_top_k: Number of results to retrieve (default: 5) - min_similarity_threshold: Minimum similarity score 0.0-1.0 (default: 0.6) - relative_threshold_factor: Factor of max similarity for dynamic filtering (default: 0.9) - """ - super().__init__(name=name, description=description) - - self.embedder = embedder - self.index_endpoint = index_endpoint - self.index_deployed_id = index_deployed_id - self.similarity_top_k = similarity_top_k - self.min_similarity_threshold = min_similarity_threshold - self.relative_threshold_factor = relative_threshold_factor - - # Initialize vector search (endpoint loaded lazily on first use) - self.vector_search = GoogleCloudVectorSearch( - project_id=project_id, - location=location, - bucket=bucket, - index_name=index_name, - ) - self._endpoint_loaded = False - - logger.info( - 'VectorSearchTool initialized with index=%s, deployed_id=%s', - index_name, - index_deployed_id, - ) - - @override - async def run_async( - self, - *, - args: dict[str, Any], - tool_context: ToolContext, - ) -> Any: - """Execute vector search with the user's query. - - Args: - args: Dictionary containing 'query' key - tool_context: Tool execution context - - Returns: - Formatted search results as XML-like documents or error message - """ - query = args['query'] - logger.debug('VectorSearchTool query: %s', query) - - try: - # Load index endpoint on first use (lazy loading) - if not self._endpoint_loaded: - self.vector_search.load_index_endpoint(self.index_endpoint) - self._endpoint_loaded = True - logger.info('Index endpoint loaded successfully') - - # Embed the query using the configured embedder - embedding_result = await self.embedder.embed_query(query) - query_embedding = list(embedding_result.embeddings[0]) - - # Run vector search - search_results = await self.vector_search.async_run_query( - deployed_index_id=self.index_deployed_id, - query=query_embedding, - limit=self.similarity_top_k, - ) - - # Apply similarity filtering (dual threshold approach) - if search_results: - # Dynamic threshold based on max similarity - max_similarity = max(r['distance'] for r in search_results) - dynamic_cutoff = max_similarity * self.relative_threshold_factor - - # Filter by both absolute and relative thresholds - search_results = [ - result - for result in search_results - if ( - result['distance'] > dynamic_cutoff - and result['distance'] > self.min_similarity_threshold - ) - ] - - logger.debug( - 'VectorSearchTool results: %d documents after filtering', - len(search_results), - ) - - # Format results - if not search_results: - return ( - f"No matching documents found for query: '{query}' " - f'(min_threshold={self.min_similarity_threshold})' - ) - - # Format as XML-like documents (matching pydantic_ai pattern) - formatted_results = [ - f'\n' - f'{result["content"]}\n' - f'' - for i, result in enumerate(search_results, start=1) - ] - - return '\n'.join(formatted_results) - - except Exception as e: - logger.error('VectorSearchTool error: %s', e, exc_info=True) - return f'Error during vector search: {str(e)}' diff --git a/rag_agent/vertex_ai.py b/rag_agent/vertex_ai.py deleted file mode 100644 index 1371c00..0000000 --- a/rag_agent/vertex_ai.py +++ /dev/null @@ -1,310 +0,0 @@ -"""Google Cloud Vertex AI Vector Search implementation.""" - -import asyncio -from collections.abc import Sequence -from typing import Any -from uuid import uuid4 - -import aiohttp -import google.auth -import google.auth.credentials -import google.auth.transport.requests -from gcloud.aio.auth import Token -from google.cloud import aiplatform - -from .file_storage.google_cloud import GoogleCloudFileStorage -from .base import BaseVectorSearch, SearchResult - - -class GoogleCloudVectorSearch(BaseVectorSearch): - """A vector search provider using Vertex AI Vector Search.""" - - def __init__( - self, - project_id: str, - location: str, - bucket: str, - index_name: str | None = None, - ) -> None: - """Initialize the GoogleCloudVectorSearch client. - - Args: - project_id: The Google Cloud project ID. - location: The Google Cloud location (e.g., 'us-central1'). - bucket: The GCS bucket to use for file storage. - index_name: The name of the index. - - """ - aiplatform.init(project=project_id, location=location) - self.project_id = project_id - self.location = location - self.storage = GoogleCloudFileStorage(bucket=bucket) - self.index_name = index_name - self._credentials: google.auth.credentials.Credentials | None = None - self._aio_session: aiohttp.ClientSession | None = None - self._async_token: Token | None = None - - def _get_auth_headers(self) -> dict[str, str]: - if self._credentials is None: - self._credentials, _ = google.auth.default( - scopes=["https://www.googleapis.com/auth/cloud-platform"], - ) - if not self._credentials.token or self._credentials.expired: - self._credentials.refresh( - google.auth.transport.requests.Request(), - ) - return { - "Authorization": f"Bearer {self._credentials.token}", - "Content-Type": "application/json", - } - - async def _async_get_auth_headers(self) -> dict[str, str]: - if self._async_token is None: - self._async_token = Token( - session=self._get_aio_session(), - scopes=[ - "https://www.googleapis.com/auth/cloud-platform", - ], - ) - access_token = await self._async_token.get() - return { - "Authorization": f"Bearer {access_token}", - "Content-Type": "application/json", - } - - def _get_aio_session(self) -> aiohttp.ClientSession: - if self._aio_session is None or self._aio_session.closed: - connector = aiohttp.TCPConnector( - limit=300, limit_per_host=50, - ) - timeout = aiohttp.ClientTimeout(total=60) - self._aio_session = aiohttp.ClientSession( - timeout=timeout, connector=connector, - ) - return self._aio_session - - def create_index( - self, - name: str, - content_path: str, - *, - dimensions: int = 3072, - approximate_neighbors_count: int = 150, - distance_measure_type: str = "DOT_PRODUCT_DISTANCE", - **kwargs: Any, # noqa: ANN401, ARG002 - ) -> None: - """Create a new Vertex AI Vector Search index. - - Args: - name: The display name for the new index. - content_path: GCS URI to the embeddings JSON file. - dimensions: Number of dimensions in embedding vectors. - approximate_neighbors_count: Neighbors to find per vector. - distance_measure_type: The distance measure to use. - **kwargs: Additional arguments. - - """ - index = aiplatform.MatchingEngineIndex.create_tree_ah_index( - display_name=name, - contents_delta_uri=content_path, - dimensions=dimensions, - approximate_neighbors_count=approximate_neighbors_count, - distance_measure_type=distance_measure_type, # type: ignore[arg-type] - leaf_node_embedding_count=1000, - leaf_nodes_to_search_percent=10, - ) - self.index = index - - def update_index( - self, index_name: str, content_path: str, **kwargs: Any, # noqa: ANN401, ARG002 - ) -> None: - """Update an existing Vertex AI Vector Search index. - - Args: - index_name: The resource name of the index to update. - content_path: GCS URI to the new embeddings JSON file. - **kwargs: Additional arguments. - - """ - index = aiplatform.MatchingEngineIndex(index_name=index_name) - index.update_embeddings( - contents_delta_uri=content_path, - ) - self.index = index - - def deploy_index( - self, - index_name: str, - machine_type: str = "e2-standard-2", - ) -> None: - """Deploy a Vertex AI Vector Search index to an endpoint. - - Args: - index_name: The name of the index to deploy. - machine_type: The machine type for the endpoint. - - """ - index_endpoint = aiplatform.MatchingEngineIndexEndpoint.create( - display_name=f"{index_name}-endpoint", - public_endpoint_enabled=True, - ) - index_endpoint.deploy_index( - index=self.index, - deployed_index_id=( - f"{index_name.replace('-', '_')}_deployed_{uuid4().hex}" - ), - machine_type=machine_type, - ) - self.index_endpoint = index_endpoint - - def load_index_endpoint(self, endpoint_name: str) -> None: - """Load an existing Vertex AI Vector Search index endpoint. - - Args: - endpoint_name: The resource name of the index endpoint. - - """ - self.index_endpoint = aiplatform.MatchingEngineIndexEndpoint( - endpoint_name, - ) - if not self.index_endpoint.public_endpoint_domain_name: - msg = ( - "The index endpoint does not have a public endpoint. " - "Ensure the endpoint is configured for public access." - ) - raise ValueError(msg) - - def run_query( - self, - deployed_index_id: str, - query: list[float], - limit: int, - ) -> list[SearchResult]: - """Run a similarity search query against the deployed index. - - Args: - deployed_index_id: The ID of the deployed index. - query: The embedding vector for the search query. - limit: Maximum number of nearest neighbors to return. - - Returns: - A list of matched items with id, distance, and content. - - """ - response = self.index_endpoint.find_neighbors( - deployed_index_id=deployed_index_id, - queries=[query], - num_neighbors=limit, - ) - results = [] - for neighbor in response[0]: - file_path = ( - f"{self.index_name}/contents/{neighbor.id}.md" - ) - content = ( - self.storage.get_file_stream(file_path) - .read() - .decode("utf-8") - ) - results.append( - SearchResult( - id=neighbor.id, - distance=float(neighbor.distance or 0), - content=content, - ), - ) - return results - - async def async_run_query( - self, - deployed_index_id: str, - query: Sequence[float], - limit: int, - ) -> list[SearchResult]: - """Run an async similarity search via the REST API. - - Args: - deployed_index_id: The ID of the deployed index. - query: The embedding vector for the search query. - limit: Maximum number of nearest neighbors to return. - - Returns: - A list of matched items with id, distance, and content. - - """ - domain = self.index_endpoint.public_endpoint_domain_name - endpoint_id = self.index_endpoint.name.split("/")[-1] - url = ( - f"https://{domain}/v1/projects/{self.project_id}" - f"/locations/{self.location}" - f"/indexEndpoints/{endpoint_id}:findNeighbors" - ) - payload = { - "deployed_index_id": deployed_index_id, - "queries": [ - { - "datapoint": {"feature_vector": list(query)}, - "neighbor_count": limit, - }, - ], - } - - headers = await self._async_get_auth_headers() - session = self._get_aio_session() - async with session.post( - url, json=payload, headers=headers, - ) as response: - response.raise_for_status() - data = await response.json() - - neighbors = ( - data.get("nearestNeighbors", [{}])[0].get("neighbors", []) - ) - content_tasks = [] - for neighbor in neighbors: - datapoint_id = neighbor["datapoint"]["datapointId"] - file_path = ( - f"{self.index_name}/contents/{datapoint_id}.md" - ) - content_tasks.append( - self.storage.async_get_file_stream(file_path), - ) - - file_streams = await asyncio.gather(*content_tasks) - results: list[SearchResult] = [] - for neighbor, stream in zip( - neighbors, file_streams, strict=True, - ): - results.append( - SearchResult( - id=neighbor["datapoint"]["datapointId"], - distance=neighbor["distance"], - content=stream.read().decode("utf-8"), - ), - ) - return results - - def delete_index(self, index_name: str) -> None: - """Delete a Vertex AI Vector Search index. - - Args: - index_name: The resource name of the index. - - """ - index = aiplatform.MatchingEngineIndex(index_name) - index.delete() - - def delete_index_endpoint( - self, index_endpoint_name: str, - ) -> None: - """Delete a Vertex AI Vector Search index endpoint. - - Args: - index_endpoint_name: The resource name of the endpoint. - - """ - index_endpoint = aiplatform.MatchingEngineIndexEndpoint( - index_endpoint_name, - ) - index_endpoint.undeploy_all() - index_endpoint.delete(force=True) diff --git a/scripts/diagnose_embeddings.py b/scripts/diagnose_embeddings.py deleted file mode 100644 index 78581b8..0000000 --- a/scripts/diagnose_embeddings.py +++ /dev/null @@ -1,79 +0,0 @@ -import asyncio -import logging -import os -import random - -import typer -from dotenv import load_dotenv -from embedder.vertex_ai import VertexAIEmbedder - -load_dotenv() -project = os.getenv("GOOGLE_CLOUD_PROJECT") -location = os.getenv("GOOGLE_CLOUD_LOCATION") - -MODEL_NAME = "gemini-embedding-001" -CONTENT_LIST = [ - "¿Cuáles son los beneficios de una tarjeta de crédito?", - "¿Cómo puedo abrir una cuenta de ahorros?", - "¿Qué es una hipoteca y cómo funciona?", - "¿Cuáles son las tasas de interés para un préstamo personal?", - "¿Cómo puedo solicitar un préstamo para un coche?", - "¿Qué es la banca en línea y cómo me registro?", - "¿Cómo puedo reportar una tarjeta de crédito perdida o robada?", - "¿Qué es el phishing y cómo puedo protegerme?", - "¿Cuáles son los diferentes tipos de cuentas corrientes que ofrecen?", - "¿Cómo puedo transferir dinero a una cuenta internacional?", -] -TASK_TYPE = "RETRIEVAL_DOCUMENT" - -logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") -logger = logging.getLogger(__name__) - -app = typer.Typer() - -logger.info(f"Initializing GenAI Client for project '{project}' in '{location}'") -embedder = VertexAIEmbedder(MODEL_NAME, project, location) - -async def embed_content_task(): - """A single task to send one embedding request using the global client.""" - content_to_embed = random.choice(CONTENT_LIST) - await embedder.async_generate_embedding(content_to_embed) - -async def run_test(concurrency: int): - """Continuously calls the embedding API and tracks requests.""" - total_requests = 0 - - logger.info(f"Starting diagnostic test with {concurrency} concurrent requests on model '{MODEL_NAME}'.") - logger.info("Press Ctrl+C to stop.") - - while True: - # Create tasks, passing project_id and location - tasks = [embed_content_task() for _ in range(concurrency)] - - try: - await asyncio.gather(*tasks) - total_requests += concurrency - logger.info(f"Successfully completed batch. Total requests so far: {total_requests}") - except Exception as e: - logger.error("Caught an error. Stopping test.") - print("\n--- STATS ---") - print(f"Total successful requests: {total_requests}") - print(f"Concurrent requests during failure: {concurrency}") - print(f"Error Type: {e.__class__.__name__}") - print(f"Error Details: {e}") - print("-------------") - break - -@app.command() -def main( - concurrency: int = typer.Option( - 10, "--concurrency", "-c", help="Number of concurrent requests to send in each batch." - ), -): - try: - asyncio.run(run_test(concurrency)) - except KeyboardInterrupt: - logger.info("\nKeyboard interrupt received. Exiting.") - -if __name__ == "__main__": - app() diff --git a/scripts/diagnose_rag_endpoint.py b/scripts/diagnose_rag_endpoint.py deleted file mode 100644 index f9eaa6c..0000000 --- a/scripts/diagnose_rag_endpoint.py +++ /dev/null @@ -1,99 +0,0 @@ -import asyncio -import logging -import random - -import httpx -import typer - -CONTENT_LIST = [ - "¿Cuáles son los beneficios de una tarjeta de crédito?", - "¿Cómo puedo abrir una cuenta de ahorros?", - "¿Qué es una hipoteca y cómo funciona?", - "¿Cuáles son las tasas de interés para un préstamo personal?", - "¿Cómo puedo solicitar un préstamo para un coche?", - "¿Qué es la banca en línea y cómo me registro?", - "¿Cómo puedo reportar una tarjeta de crédito perdida o robada?", - "¿Qué es el phishing y cómo puedo protegerme?", - "¿Cuáles son los diferentes tipos de cuentas corrientes que ofrecen?", - "¿Cómo puedo transferir dinero a una cuenta internacional?", -] - -logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") -logger = logging.getLogger(__name__) - -app = typer.Typer() - -async def call_rag_endpoint_task(client: httpx.AsyncClient, url: str): - """A single task to send one request to the RAG endpoint.""" - question = random.choice(CONTENT_LIST) - json_payload = { - "sessionInfo": { - "parameters": { - "query": question - } - } - } - response = await client.post(url, json=json_payload) - response.raise_for_status() # Raise an exception for bad status codes - response_data = response.json() - response_text = response_data["sessionInfo"]["parameters"]["response"] - logger.info(f"Question: {question[:50]}... Response: {response_text[:100]}...") - -async def run_test(concurrency: int, url: str, timeout_seconds: float): - """Continuously calls the RAG endpoint and tracks requests.""" - total_requests = 0 - - logger.info(f"Starting diagnostic test with {concurrency} concurrent requests on endpoint '{url}'.") - logger.info(f"Request timeout is set to {timeout_seconds} seconds.") - logger.info("Press Ctrl+C to stop.") - - timeout = httpx.Timeout(timeout_seconds) - async with httpx.AsyncClient(timeout=timeout) as client: - while True: - tasks = [call_rag_endpoint_task(client, url) for _ in range(concurrency)] - - try: - await asyncio.gather(*tasks) - total_requests += concurrency - logger.info(f"Successfully completed batch. Total requests so far: {total_requests}") - except httpx.TimeoutException as e: - logger.error(f"A request timed out: {e.request.method} {e.request.url}") - logger.error("Consider increasing the timeout with the --timeout option.") - break - except httpx.HTTPStatusError as e: - logger.error(f"An HTTP error occurred: {e.response.status_code} - {e.request.method} {e.request.url}") - logger.error(f"Response body: {e.response.text}") - break - except httpx.RequestError as e: - logger.error(f"A request error occurred: {e.request.method} {e.request.url}") - logger.error(f"Error details: {e}") - break - except Exception as e: - logger.error("Caught an unexpected error. Stopping test.") - print("\n--- STATS ---") - print(f"Total successful requests: {total_requests}") - print(f"Concurrent requests during failure: {concurrency}") - print(f"Error Type: {e.__class__.__name__}") - print(f"Error Details: {e}") - print("-------------") - break - -@app.command() -def main( - concurrency: int = typer.Option( - 10, "--concurrency", "-c", help="Number of concurrent requests to send in each batch." - ), - url: str = typer.Option( - "http://127.0.0.1:8000/sigma-rag", "--url", "-u", help="The URL of the RAG endpoint to test." - ), - timeout_seconds: float = typer.Option( - 30.0, "--timeout", "-t", help="Request timeout in seconds." - ) -): - try: - asyncio.run(run_test(concurrency, url, timeout_seconds)) - except KeyboardInterrupt: - logger.info("\nKeyboard interrupt received. Exiting.") - -if __name__ == "__main__": - app() diff --git a/scripts/stress_test.py b/scripts/stress_test.py deleted file mode 100644 index 7cb04df..0000000 --- a/scripts/stress_test.py +++ /dev/null @@ -1,91 +0,0 @@ -import concurrent.futures -import random -import threading - -import requests - -# URL for the endpoint -url = "http://localhost:8000/sigma-rag" - -# List of Spanish banking questions -spanish_questions = [ - "¿Cuáles son los beneficios de una tarjeta de crédito?", - "¿Cómo puedo abrir una cuenta de ahorros?", - "¿Qué es una hipoteca y cómo funciona?", - "¿Cuáles son las tasas de interés para un préstamo personal?", - "¿Cómo puedo solicitar un préstamo para un coche?", - "¿Qué es la banca en línea y cómo me registro?", - "¿Cómo puedo reportar una tarjeta de crédito perdida o robada?", - "¿Qué es el phishing y cómo puedo protegerme?", - "¿Cuáles son los diferentes tipos de cuentas corrientes que ofrecen?", - "¿Cómo puedo transferir dinero a una cuenta internacional?", -] - -# A threading Event to signal all threads to stop -stop_event = threading.Event() - -def send_request(question, request_id): - """Sends a single request and handles the response.""" - if stop_event.is_set(): - return - - data = {"sessionInfo": {"parameters": {"query": question}}} - try: - response = requests.post(url, json=data) - - if stop_event.is_set(): - return - - if response.status_code == 500: - print(f"Request {request_id}: Received 500 error with question: '{question}'.") - print("Stopping stress test.") - stop_event.set() - else: - print(f"Request {request_id}: Successful with status code {response.status_code}.") - - except requests.exceptions.RequestException as e: - if not stop_event.is_set(): - print(f"Request {request_id}: An error occurred: {e}") - stop_event.set() - -def main(): - """Runs the stress test with parallel requests.""" - num_workers = 30 # Number of parallel requests - print(f"Starting stress test with {num_workers} parallel workers. Press Ctrl+C to stop.") - - with concurrent.futures.ThreadPoolExecutor(max_workers=num_workers) as executor: - futures = { - executor.submit(send_request, random.choice(spanish_questions), i) - for i in range(1, num_workers + 1) - } - request_id_counter = num_workers + 1 - - try: - while not stop_event.is_set(): - # Wait for any future to complete - done, _ = concurrent.futures.wait( - futures, return_when=concurrent.futures.FIRST_COMPLETED - ) - - for future in done: - # Remove the completed future - futures.remove(future) - - # If we are not stopping, submit a new one - if not stop_event.is_set(): - futures.add( - executor.submit( - send_request, - random.choice(spanish_questions), - request_id_counter, - ) - ) - request_id_counter += 1 - except KeyboardInterrupt: - print("\nKeyboard interrupt received. Stopping threads.") - stop_event.set() - - print("Stress test finished.") - -if __name__ == "__main__": - main() diff --git a/scripts/submit_pipeline.py b/scripts/submit_pipeline.py deleted file mode 100644 index d8aaf93..0000000 --- a/scripts/submit_pipeline.py +++ /dev/null @@ -1,84 +0,0 @@ -from typing import Annotated - -import typer -from google.cloud import aiplatform - -from rag_eval.config import settings - -app = typer.Typer() - - -@app.command() -def main( - pipeline_spec_path: Annotated[ - str, - typer.Option( - "--pipeline-spec-path", - "-p", - help="Path to the compiled pipeline YAML file.", - ), - ], - input_table: Annotated[ - str, - typer.Option( - "--input-table", - "-i", - help="Full BigQuery table name for input (e.g., 'project.dataset.table')", - ), - ], - output_table: Annotated[ - str, - typer.Option( - "--output-table", - "-o", - help="Full BigQuery table name for output (e.g., 'project.dataset.table')", - ), - ], - project_id: Annotated[ - str, - typer.Option( - "--project-id", - help="Google Cloud project ID.", - ), - ] = settings.project_id, - location: Annotated[ - str, - typer.Option( - "--location", - help="Google Cloud location for the pipeline job.", - ), - ] = settings.location, - display_name: Annotated[ - str, - typer.Option( - "--display-name", - help="Display name for the pipeline job.", - ), - ] = "search-eval-pipeline-job", -): - """Submits a Vertex AI pipeline job.""" - parameter_values = { - "project_id": project_id, - "location": location, - "input_table": input_table, - "output_table": output_table, - } - - job = aiplatform.PipelineJob( - display_name=display_name, - template_path=pipeline_spec_path, - pipeline_root=f"gs://{settings.bucket}/pipeline_root", - parameter_values=parameter_values, - project=project_id, - location=location, - ) - - print(f"Submitting pipeline job with parameters: {parameter_values}") - job.submit( - service_account="sa-cicd-gitlab@bnt-orquestador-cognitivo-dev.iam.gserviceaccount.com" - ) - print(f"Pipeline job submitted. You can view it at: {job._dashboard_uri()}") - - -if __name__ == "__main__": - app() diff --git a/scripts/test_rerank.py b/scripts/test_rerank.py deleted file mode 100644 index d5e13af..0000000 --- a/scripts/test_rerank.py +++ /dev/null @@ -1,42 +0,0 @@ -from google.cloud import discoveryengine_v1 as discoveryengine - -# TODO(developer): Uncomment these variables before running the sample. -project_id = "bnt-orquestador-cognitivo-dev" - -client = discoveryengine.RankServiceClient() - -# The full resource name of the ranking config. -# Format: projects/{project_id}/locations/{location}/rankingConfigs/default_ranking_config -ranking_config = client.ranking_config_path( - project=project_id, - location="global", - ranking_config="default_ranking_config", -) -request = discoveryengine.RankRequest( - ranking_config=ranking_config, - model="semantic-ranker-default@latest", - top_n=10, - query="What is Google Gemini?", - records=[ - discoveryengine.RankingRecord( - id="1", - title="Gemini", - content="The Gemini zodiac symbol often depicts two figures standing side-by-side.", - ), - discoveryengine.RankingRecord( - id="2", - title="Gemini", - content="Gemini is a cutting edge large language model created by Google.", - ), - discoveryengine.RankingRecord( - id="3", - title="Gemini Constellation", - content="Gemini is a constellation that can be seen in the night sky.", - ), - ], -) - -response = client.rank(request=request) - -# Handle the response -print(response) diff --git a/scripts/test_server.py b/scripts/test_server.py deleted file mode 100644 index d109b0a..0000000 --- a/scripts/test_server.py +++ /dev/null @@ -1,12 +0,0 @@ -import requests - -# Test the /sigma-rag endpoint -url = "http://localhost:8000/sigma-rag" -data = { - "sessionInfo": {"parameters": {"query": "What are the benefits of a credit card?"}} -} - -response = requests.post(url, json=data) - -print("Response from /sigma-rag:") -print(response.json()) diff --git a/src/rag_eval/__init__.py b/src/rag_eval/__init__.py deleted file mode 100644 index 1495db6..0000000 --- a/src/rag_eval/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""RAG evaluation agent package.""" diff --git a/src/rag_eval/agent.py b/src/rag_eval/agent.py deleted file mode 100644 index 6646802..0000000 --- a/src/rag_eval/agent.py +++ /dev/null @@ -1,92 +0,0 @@ -"""Pydantic AI agent with RAG tool for vector search.""" - -import time - -import structlog -from pydantic import BaseModel -from pydantic_ai import Agent, Embedder, RunContext -from pydantic_ai.models.google import GoogleModel - -from rag_eval.config import settings -from rag_eval.vector_search.vertex_ai import GoogleCloudVectorSearch - -logger = structlog.get_logger(__name__) - - -class Deps(BaseModel): - """Dependencies injected into the agent at runtime.""" - - vector_search: GoogleCloudVectorSearch - embedder: Embedder - - model_config = {"arbitrary_types_allowed": True} - - -model = GoogleModel( - settings.agent_language_model, - provider=settings.provider, -) -agent = Agent( - model, - deps_type=Deps, - system_prompt=settings.agent_instructions, -) - - -@agent.tool -async def conocimiento(ctx: RunContext[Deps], query: str) -> str: - """Search the vector index for the given query. - - Args: - ctx: The run context containing dependencies. - query: The query to search for. - - Returns: - A formatted string containing the search results. - - """ - t0 = time.perf_counter() - min_sim = 0.6 - - query_embedding = await ctx.deps.embedder.embed_query(query) - t_embed = time.perf_counter() - - search_results = await ctx.deps.vector_search.async_run_query( - deployed_index_id=settings.index_deployed_id, - query=list(query_embedding.embeddings[0]), - limit=5, - ) - t_search = time.perf_counter() - - if search_results: - max_sim = max(r["distance"] for r in search_results) - cutoff = max_sim * 0.9 - search_results = [ - s - for s in search_results - if s["distance"] > cutoff and s["distance"] > min_sim - ] - - logger.info( - "conocimiento.timing", - embedding_ms=round((t_embed - t0) * 1000, 1), - vector_search_ms=round((t_search - t_embed) * 1000, 1), - total_ms=round((t_search - t0) * 1000, 1), - chunks=[s["id"] for s in search_results], - ) - - formatted_results = [ - f"\n" - f"{result['content']}\n" - f"" - for i, result in enumerate(search_results, start=1) - ] - return "\n".join(formatted_results) - - -if __name__ == "__main__": - deps = Deps( - vector_search=settings.vector_search, - embedder=settings.embedder, - ) - agent.to_cli_sync(deps=deps) diff --git a/src/rag_eval/config.py b/src/rag_eval/config.py deleted file mode 100644 index b099267..0000000 --- a/src/rag_eval/config.py +++ /dev/null @@ -1,92 +0,0 @@ -"""Application settings loaded from YAML and environment variables.""" - -import os -from functools import cached_property - -from pydantic_ai import Embedder -from pydantic_ai.providers.google import GoogleProvider -from pydantic_settings import ( - BaseSettings, - PydanticBaseSettingsSource, - SettingsConfigDict, - YamlConfigSettingsSource, -) - -from rag_eval.vector_search.vertex_ai import GoogleCloudVectorSearch - -CONFIG_FILE_PATH = os.getenv("CONFIG_YAML", "config.yaml") - - -class Settings(BaseSettings): - """Application settings loaded from config.yaml and env vars.""" - - project_id: str - location: str - bucket: str - - agent_name: str - agent_instructions: str - agent_language_model: str - agent_embedding_model: str - agent_thinking: int - - index_name: str - index_deployed_id: str - index_endpoint: str - index_dimensions: int - index_machine_type: str = "e2-standard-16" - index_origin: str - index_destination: str - index_chunk_limit: int - - - model_config = SettingsConfigDict(yaml_file=CONFIG_FILE_PATH) - - @classmethod - def settings_customise_sources( - cls, - settings_cls: type[BaseSettings], - init_settings: PydanticBaseSettingsSource, # noqa: ARG003 - env_settings: PydanticBaseSettingsSource, - dotenv_settings: PydanticBaseSettingsSource, # noqa: ARG003 - file_secret_settings: PydanticBaseSettingsSource, # noqa: ARG003 - ) -> tuple[PydanticBaseSettingsSource, ...]: - """Use env vars and YAML as settings sources.""" - return ( - env_settings, - YamlConfigSettingsSource(settings_cls), - ) - - @cached_property - def provider(self) -> GoogleProvider: - """Return a Google provider configured for Vertex AI.""" - return GoogleProvider( - project=self.project_id, - location=self.location, - ) - - @cached_property - def vector_search(self) -> GoogleCloudVectorSearch: - """Return a configured vector search client.""" - vs = GoogleCloudVectorSearch( - project_id=self.project_id, - location=self.location, - bucket=self.bucket, - index_name=self.index_name, - ) - vs.load_index_endpoint(self.index_endpoint) - return vs - - @cached_property - def embedder(self) -> Embedder: - """Return an embedder configured for the agent's embedding model.""" - from pydantic_ai.embeddings.google import GoogleEmbeddingModel # noqa: PLC0415 - - model = GoogleEmbeddingModel( - self.agent_embedding_model, - provider=self.provider, - ) - return Embedder(model) - - -settings = Settings.model_validate({}) diff --git a/src/rag_eval/file_storage/__init__.py b/src/rag_eval/file_storage/__init__.py deleted file mode 100644 index 7f88e76..0000000 --- a/src/rag_eval/file_storage/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""File storage provider implementations.""" diff --git a/src/rag_eval/file_storage/base.py b/src/rag_eval/file_storage/base.py deleted file mode 100644 index a98ea39..0000000 --- a/src/rag_eval/file_storage/base.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Abstract base class for file storage providers.""" - -from abc import ABC, abstractmethod -from typing import BinaryIO - - -class BaseFileStorage(ABC): - """Abstract base class for a remote file processor. - - Defines the interface for listing and processing files from - a remote source. - """ - - @abstractmethod - def upload_file( - self, - file_path: str, - destination_blob_name: str, - content_type: str | None = None, - ) -> None: - """Upload a file to the remote source. - - Args: - file_path: The local path to the file to upload. - destination_blob_name: Name of the file in remote storage. - content_type: The content type of the file. - - """ - ... - - @abstractmethod - def list_files(self, path: str | None = None) -> list[str]: - """List files from a remote location. - - Args: - path: Path to a specific file or directory. If None, - recursively lists all files in the bucket. - - Returns: - A list of file paths. - - """ - ... - - @abstractmethod - def get_file_stream(self, file_name: str) -> BinaryIO: - """Get a file from the remote source as a file-like object. - - Args: - file_name: The name of the file to retrieve. - - Returns: - A file-like object containing the file data. - - """ - ... diff --git a/src/rag_eval/file_storage/google_cloud.py b/src/rag_eval/file_storage/google_cloud.py deleted file mode 100644 index 3756f30..0000000 --- a/src/rag_eval/file_storage/google_cloud.py +++ /dev/null @@ -1,188 +0,0 @@ -"""Google Cloud Storage file storage implementation.""" - -import asyncio -import io -import logging -from typing import BinaryIO - -import aiohttp -from gcloud.aio.storage import Storage -from google.cloud import storage - -from rag_eval.file_storage.base import BaseFileStorage - -logger = logging.getLogger(__name__) - -HTTP_TOO_MANY_REQUESTS = 429 -HTTP_SERVER_ERROR = 500 - - -class GoogleCloudFileStorage(BaseFileStorage): - """File storage backed by Google Cloud Storage.""" - - def __init__(self, bucket: str) -> None: # noqa: D107 - self.bucket_name = bucket - - self.storage_client = storage.Client() - self.bucket_client = self.storage_client.bucket(self.bucket_name) - self._aio_session: aiohttp.ClientSession | None = None - self._aio_storage: Storage | None = None - self._cache: dict[str, bytes] = {} - - def upload_file( - self, - file_path: str, - destination_blob_name: str, - content_type: str | None = None, - ) -> None: - """Upload a file to Cloud Storage. - - Args: - file_path: The local path to the file to upload. - destination_blob_name: Name of the blob in the bucket. - content_type: The content type of the file. - - """ - blob = self.bucket_client.blob(destination_blob_name) - blob.upload_from_filename( - file_path, - content_type=content_type, - if_generation_match=0, - ) - self._cache.pop(destination_blob_name, None) - - def list_files(self, path: str | None = None) -> list[str]: - """List all files at the given path in the bucket. - - If path is None, recursively lists all files. - - Args: - path: Prefix to filter files by. - - Returns: - A list of blob names. - - """ - blobs = self.storage_client.list_blobs( - self.bucket_name, prefix=path, - ) - return [blob.name for blob in blobs] - - def get_file_stream(self, file_name: str) -> BinaryIO: - """Get a file as a file-like object, using cache. - - Args: - file_name: The blob name to retrieve. - - Returns: - A BytesIO stream with the file contents. - - """ - if file_name not in self._cache: - blob = self.bucket_client.blob(file_name) - self._cache[file_name] = blob.download_as_bytes() - file_stream = io.BytesIO(self._cache[file_name]) - file_stream.name = file_name - return file_stream - - def _get_aio_session(self) -> aiohttp.ClientSession: - if self._aio_session is None or self._aio_session.closed: - connector = aiohttp.TCPConnector( - limit=300, limit_per_host=50, - ) - timeout = aiohttp.ClientTimeout(total=60) - self._aio_session = aiohttp.ClientSession( - timeout=timeout, connector=connector, - ) - return self._aio_session - - def _get_aio_storage(self) -> Storage: - if self._aio_storage is None: - self._aio_storage = Storage( - session=self._get_aio_session(), - ) - return self._aio_storage - - async def async_get_file_stream( - self, file_name: str, max_retries: int = 3, - ) -> BinaryIO: - """Get a file asynchronously with retry on transient errors. - - Args: - file_name: The blob name to retrieve. - max_retries: Maximum number of retry attempts. - - Returns: - A BytesIO stream with the file contents. - - Raises: - TimeoutError: If all retry attempts fail. - - """ - if file_name in self._cache: - file_stream = io.BytesIO(self._cache[file_name]) - file_stream.name = file_name - return file_stream - - storage_client = self._get_aio_storage() - last_exception: Exception | None = None - - for attempt in range(max_retries): - try: - self._cache[file_name] = await storage_client.download( - self.bucket_name, file_name, - ) - file_stream = io.BytesIO(self._cache[file_name]) - file_stream.name = file_name - except TimeoutError as exc: - last_exception = exc - logger.warning( - "Timeout downloading gs://%s/%s (attempt %d/%d)", - self.bucket_name, - file_name, - attempt + 1, - max_retries, - ) - except aiohttp.ClientResponseError as exc: - last_exception = exc - if ( - exc.status == HTTP_TOO_MANY_REQUESTS - or exc.status >= HTTP_SERVER_ERROR - ): - logger.warning( - "HTTP %d downloading gs://%s/%s " - "(attempt %d/%d)", - exc.status, - self.bucket_name, - file_name, - attempt + 1, - max_retries, - ) - else: - raise - else: - return file_stream - - if attempt < max_retries - 1: - delay = 0.5 * (2**attempt) - await asyncio.sleep(delay) - - msg = ( - f"Failed to download gs://{self.bucket_name}/{file_name} " - f"after {max_retries} attempts" - ) - raise TimeoutError(msg) from last_exception - - def delete_files(self, path: str) -> None: - """Delete all files at the given path in the bucket. - - Args: - path: Prefix of blobs to delete. - - """ - blobs = self.storage_client.list_blobs( - self.bucket_name, prefix=path, - ) - for blob in blobs: - blob.delete() - self._cache.pop(blob.name, None) diff --git a/src/rag_eval/logging.py b/src/rag_eval/logging.py deleted file mode 100644 index 712f99b..0000000 --- a/src/rag_eval/logging.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Structured logging configuration using structlog.""" - -import logging -import sys - -import structlog - - -def setup_logging(*, json: bool = True, level: int = logging.INFO) -> None: - """Configure structlog with JSON or console output.""" - shared_processors: list[structlog.types.Processor] = [ - structlog.contextvars.merge_contextvars, - structlog.stdlib.add_log_level, - structlog.stdlib.add_logger_name, - structlog.processors.TimeStamper(fmt="iso"), - structlog.stdlib.ProcessorFormatter.wrap_for_formatter, - ] - - if json: - formatter = structlog.stdlib.ProcessorFormatter( - processors=[ - structlog.stdlib.ProcessorFormatter.remove_processors_meta, - structlog.processors.JSONRenderer(), - ], - ) - else: - formatter = structlog.stdlib.ProcessorFormatter( - processors=[ - structlog.stdlib.ProcessorFormatter.remove_processors_meta, - structlog.dev.ConsoleRenderer(), - ], - ) - - handler = logging.StreamHandler(sys.stdout) - handler.setFormatter(formatter) - - root = logging.getLogger() - root.handlers.clear() - root.addHandler(handler) - root.setLevel(level) - - structlog.configure( - processors=shared_processors, - logger_factory=structlog.stdlib.LoggerFactory(), - wrapper_class=structlog.stdlib.BoundLogger, - cache_logger_on_first_use=True, - ) diff --git a/src/rag_eval/server.py b/src/rag_eval/server.py deleted file mode 100644 index 6cdb271..0000000 --- a/src/rag_eval/server.py +++ /dev/null @@ -1,61 +0,0 @@ -"""FastAPI server exposing the RAG agent endpoint.""" - -import time -from typing import Literal -from uuid import uuid4 - -import structlog -from fastapi import FastAPI -from pydantic import BaseModel - -from rag_eval.agent import Deps, agent -from rag_eval.config import settings -from rag_eval.logging import setup_logging - -logger = structlog.get_logger(__name__) - -setup_logging() - -app = FastAPI(title="RAG Agent") - - -class Message(BaseModel): - """A single chat message.""" - - role: Literal["system", "user", "assistant"] - content: str - - -class AgentRequest(BaseModel): - """Request body for the agent endpoint.""" - - messages: list[Message] - - -class AgentResponse(BaseModel): - """Response body from the agent endpoint.""" - - response: str - - -@app.post("/agent") -async def run_agent(request: AgentRequest) -> AgentResponse: - """Run the RAG agent with the provided messages.""" - request_id = uuid4().hex[:8] - structlog.contextvars.clear_contextvars() - structlog.contextvars.bind_contextvars(request_id=request_id) - - prompt = request.messages[-1].content - logger.info("request.start", prompt_length=len(prompt)) - t0 = time.perf_counter() - - deps = Deps( - vector_search=settings.vector_search, - embedder=settings.embedder, - ) - result = await agent.run(prompt, deps=deps) - - elapsed = round((time.perf_counter() - t0) * 1000, 1) - logger.info("request.end", elapsed_ms=elapsed) - - return AgentResponse(response=result.output) diff --git a/src/rag_eval/vector_search/__init__.py b/src/rag_eval/vector_search/__init__.py deleted file mode 100644 index f38f5d3..0000000 --- a/src/rag_eval/vector_search/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Vector search provider implementations.""" diff --git a/src/rag_eval/vector_search/base.py b/src/rag_eval/vector_search/base.py deleted file mode 100644 index ab00142..0000000 --- a/src/rag_eval/vector_search/base.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Abstract base class for vector search providers.""" - -from abc import ABC, abstractmethod -from typing import Any, TypedDict - - -class SearchResult(TypedDict): - """A single vector search result.""" - - id: str - distance: float - content: str - - -class BaseVectorSearch(ABC): - """Abstract base class for a vector search provider. - - This class defines the standard interface for creating a vector search - index and running queries against it. - """ - - @abstractmethod - def create_index( - self, name: str, content_path: str, **kwargs: Any # noqa: ANN401 - ) -> None: - """Create a new vector search index with the provided content. - - Args: - name: The desired name for the new index. - content_path: Path to the data used to populate the index. - **kwargs: Additional provider-specific arguments. - - """ - ... - - @abstractmethod - def update_index( - self, index_name: str, content_path: str, **kwargs: Any # noqa: ANN401 - ) -> None: - """Update an existing vector search index with new content. - - Args: - index_name: The name of the index to update. - content_path: Path to the data used to populate the index. - **kwargs: Additional provider-specific arguments. - - """ - ... - - @abstractmethod - def run_query( - self, - deployed_index_id: str, - query: list[float], - limit: int, - ) -> list[SearchResult]: - """Run a similarity search query against the index. - - Args: - deployed_index_id: The ID of the deployed index. - query: The embedding vector for the search query. - limit: Maximum number of nearest neighbors to return. - - Returns: - A list of matched items with id, distance, and content. - - """ - ... diff --git a/src/rag_eval/vector_search/vertex_ai.py b/src/rag_eval/vector_search/vertex_ai.py deleted file mode 100644 index baa2431..0000000 --- a/src/rag_eval/vector_search/vertex_ai.py +++ /dev/null @@ -1,310 +0,0 @@ -"""Google Cloud Vertex AI Vector Search implementation.""" - -import asyncio -from collections.abc import Sequence -from typing import Any -from uuid import uuid4 - -import aiohttp -import google.auth -import google.auth.credentials -import google.auth.transport.requests -from gcloud.aio.auth import Token -from google.cloud import aiplatform - -from rag_eval.file_storage.google_cloud import GoogleCloudFileStorage -from rag_eval.vector_search.base import BaseVectorSearch, SearchResult - - -class GoogleCloudVectorSearch(BaseVectorSearch): - """A vector search provider using Vertex AI Vector Search.""" - - def __init__( - self, - project_id: str, - location: str, - bucket: str, - index_name: str | None = None, - ) -> None: - """Initialize the GoogleCloudVectorSearch client. - - Args: - project_id: The Google Cloud project ID. - location: The Google Cloud location (e.g., 'us-central1'). - bucket: The GCS bucket to use for file storage. - index_name: The name of the index. - - """ - aiplatform.init(project=project_id, location=location) - self.project_id = project_id - self.location = location - self.storage = GoogleCloudFileStorage(bucket=bucket) - self.index_name = index_name - self._credentials: google.auth.credentials.Credentials | None = None - self._aio_session: aiohttp.ClientSession | None = None - self._async_token: Token | None = None - - def _get_auth_headers(self) -> dict[str, str]: - if self._credentials is None: - self._credentials, _ = google.auth.default( - scopes=["https://www.googleapis.com/auth/cloud-platform"], - ) - if not self._credentials.token or self._credentials.expired: - self._credentials.refresh( - google.auth.transport.requests.Request(), - ) - return { - "Authorization": f"Bearer {self._credentials.token}", - "Content-Type": "application/json", - } - - async def _async_get_auth_headers(self) -> dict[str, str]: - if self._async_token is None: - self._async_token = Token( - session=self._get_aio_session(), - scopes=[ - "https://www.googleapis.com/auth/cloud-platform", - ], - ) - access_token = await self._async_token.get() - return { - "Authorization": f"Bearer {access_token}", - "Content-Type": "application/json", - } - - def _get_aio_session(self) -> aiohttp.ClientSession: - if self._aio_session is None or self._aio_session.closed: - connector = aiohttp.TCPConnector( - limit=300, limit_per_host=50, - ) - timeout = aiohttp.ClientTimeout(total=60) - self._aio_session = aiohttp.ClientSession( - timeout=timeout, connector=connector, - ) - return self._aio_session - - def create_index( - self, - name: str, - content_path: str, - *, - dimensions: int = 3072, - approximate_neighbors_count: int = 150, - distance_measure_type: str = "DOT_PRODUCT_DISTANCE", - **kwargs: Any, # noqa: ANN401, ARG002 - ) -> None: - """Create a new Vertex AI Vector Search index. - - Args: - name: The display name for the new index. - content_path: GCS URI to the embeddings JSON file. - dimensions: Number of dimensions in embedding vectors. - approximate_neighbors_count: Neighbors to find per vector. - distance_measure_type: The distance measure to use. - **kwargs: Additional arguments. - - """ - index = aiplatform.MatchingEngineIndex.create_tree_ah_index( - display_name=name, - contents_delta_uri=content_path, - dimensions=dimensions, - approximate_neighbors_count=approximate_neighbors_count, - distance_measure_type=distance_measure_type, # type: ignore[arg-type] - leaf_node_embedding_count=1000, - leaf_nodes_to_search_percent=10, - ) - self.index = index - - def update_index( - self, index_name: str, content_path: str, **kwargs: Any, # noqa: ANN401, ARG002 - ) -> None: - """Update an existing Vertex AI Vector Search index. - - Args: - index_name: The resource name of the index to update. - content_path: GCS URI to the new embeddings JSON file. - **kwargs: Additional arguments. - - """ - index = aiplatform.MatchingEngineIndex(index_name=index_name) - index.update_embeddings( - contents_delta_uri=content_path, - ) - self.index = index - - def deploy_index( - self, - index_name: str, - machine_type: str = "e2-standard-2", - ) -> None: - """Deploy a Vertex AI Vector Search index to an endpoint. - - Args: - index_name: The name of the index to deploy. - machine_type: The machine type for the endpoint. - - """ - index_endpoint = aiplatform.MatchingEngineIndexEndpoint.create( - display_name=f"{index_name}-endpoint", - public_endpoint_enabled=True, - ) - index_endpoint.deploy_index( - index=self.index, - deployed_index_id=( - f"{index_name.replace('-', '_')}_deployed_{uuid4().hex}" - ), - machine_type=machine_type, - ) - self.index_endpoint = index_endpoint - - def load_index_endpoint(self, endpoint_name: str) -> None: - """Load an existing Vertex AI Vector Search index endpoint. - - Args: - endpoint_name: The resource name of the index endpoint. - - """ - self.index_endpoint = aiplatform.MatchingEngineIndexEndpoint( - endpoint_name, - ) - if not self.index_endpoint.public_endpoint_domain_name: - msg = ( - "The index endpoint does not have a public endpoint. " - "Ensure the endpoint is configured for public access." - ) - raise ValueError(msg) - - def run_query( - self, - deployed_index_id: str, - query: list[float], - limit: int, - ) -> list[SearchResult]: - """Run a similarity search query against the deployed index. - - Args: - deployed_index_id: The ID of the deployed index. - query: The embedding vector for the search query. - limit: Maximum number of nearest neighbors to return. - - Returns: - A list of matched items with id, distance, and content. - - """ - response = self.index_endpoint.find_neighbors( - deployed_index_id=deployed_index_id, - queries=[query], - num_neighbors=limit, - ) - results = [] - for neighbor in response[0]: - file_path = ( - f"{self.index_name}/contents/{neighbor.id}.md" - ) - content = ( - self.storage.get_file_stream(file_path) - .read() - .decode("utf-8") - ) - results.append( - SearchResult( - id=neighbor.id, - distance=float(neighbor.distance or 0), - content=content, - ), - ) - return results - - async def async_run_query( - self, - deployed_index_id: str, - query: Sequence[float], - limit: int, - ) -> list[SearchResult]: - """Run an async similarity search via the REST API. - - Args: - deployed_index_id: The ID of the deployed index. - query: The embedding vector for the search query. - limit: Maximum number of nearest neighbors to return. - - Returns: - A list of matched items with id, distance, and content. - - """ - domain = self.index_endpoint.public_endpoint_domain_name - endpoint_id = self.index_endpoint.name.split("/")[-1] - url = ( - f"https://{domain}/v1/projects/{self.project_id}" - f"/locations/{self.location}" - f"/indexEndpoints/{endpoint_id}:findNeighbors" - ) - payload = { - "deployed_index_id": deployed_index_id, - "queries": [ - { - "datapoint": {"feature_vector": list(query)}, - "neighbor_count": limit, - }, - ], - } - - headers = await self._async_get_auth_headers() - session = self._get_aio_session() - async with session.post( - url, json=payload, headers=headers, - ) as response: - response.raise_for_status() - data = await response.json() - - neighbors = ( - data.get("nearestNeighbors", [{}])[0].get("neighbors", []) - ) - content_tasks = [] - for neighbor in neighbors: - datapoint_id = neighbor["datapoint"]["datapointId"] - file_path = ( - f"{self.index_name}/contents/{datapoint_id}.md" - ) - content_tasks.append( - self.storage.async_get_file_stream(file_path), - ) - - file_streams = await asyncio.gather(*content_tasks) - results: list[SearchResult] = [] - for neighbor, stream in zip( - neighbors, file_streams, strict=True, - ): - results.append( - SearchResult( - id=neighbor["datapoint"]["datapointId"], - distance=neighbor["distance"], - content=stream.read().decode("utf-8"), - ), - ) - return results - - def delete_index(self, index_name: str) -> None: - """Delete a Vertex AI Vector Search index. - - Args: - index_name: The resource name of the index. - - """ - index = aiplatform.MatchingEngineIndex(index_name) - index.delete() - - def delete_index_endpoint( - self, index_endpoint_name: str, - ) -> None: - """Delete a Vertex AI Vector Search index endpoint. - - Args: - index_endpoint_name: The resource name of the endpoint. - - """ - index_endpoint = aiplatform.MatchingEngineIndexEndpoint( - index_endpoint_name, - ) - index_endpoint.undeploy_all() - index_endpoint.delete(force=True) diff --git a/rag_agent/__init__.py b/src/va_agent/__init__.py similarity index 77% rename from rag_agent/__init__.py rename to src/va_agent/__init__.py index 855906f..a582c1a 100644 --- a/rag_agent/__init__.py +++ b/src/va_agent/__init__.py @@ -4,7 +4,3 @@ import os # Ensure the Google GenAI SDK talks to Vertex AI instead of the public Gemini API. os.environ.setdefault("GOOGLE_GENAI_USE_VERTEXAI", "true") - -from .agent import root_agent - -__all__ = ["root_agent"] diff --git a/src/va_agent/agent.py b/src/va_agent/agent.py new file mode 100644 index 0000000..6b90ef1 --- /dev/null +++ b/src/va_agent/agent.py @@ -0,0 +1,29 @@ +"""ADK agent with vector search RAG tool.""" + +from google import genai +from google.adk.agents.llm_agent import Agent +from google.adk.runners import Runner +from google.adk.tools.mcp_tool import McpToolset +from google.adk.tools.mcp_tool.mcp_session_manager import SseConnectionParams +from google.cloud.firestore_v1.async_client import AsyncClient + +from va_agent.config import settings +from va_agent.session import FirestoreSessionService + +connection_params = SseConnectionParams(url=settings.mcp_remote_url) +toolset = McpToolset(connection_params=connection_params) + +agent = Agent( + model=settings.agent_model, + name=settings.agent_name, + instruction=settings.agent_instructions, + tools=[toolset], +) + +session_service = FirestoreSessionService( + db=AsyncClient(database=settings.firestore_db), + compaction_token_threshold=10_000, + genai_client=genai.Client(), +) + +runner = Runner(app_name="va_agent", agent=agent, session_service=session_service) diff --git a/src/va_agent/config.py b/src/va_agent/config.py new file mode 100644 index 0000000..e36eade --- /dev/null +++ b/src/va_agent/config.py @@ -0,0 +1,53 @@ +"""Configuration helper for ADK agent.""" + +import os + +from pydantic_settings import ( + BaseSettings, + PydanticBaseSettingsSource, + SettingsConfigDict, + YamlConfigSettingsSource, +) + +CONFIG_FILE_PATH = os.getenv("CONFIG_YAML", "config.yaml") + + +class AgentSettings(BaseSettings): + """Settings for ADK agent with vector search.""" + + google_cloud_project: str + google_cloud_location: str + + # Agent configuration + agent_name: str + agent_instructions: str + agent_model: str + + # Firestore configuration + firestore_db: str + + # MCP configuration + mcp_remote_url: str + + model_config = SettingsConfigDict( + yaml_file=CONFIG_FILE_PATH, + extra="ignore", # Ignore extra fields from config.yaml + ) + + @classmethod + def settings_customise_sources( + cls, + settings_cls: type[BaseSettings], + init_settings: PydanticBaseSettingsSource, # noqa: ARG003 + env_settings: PydanticBaseSettingsSource, + dotenv_settings: PydanticBaseSettingsSource, # noqa: ARG003 + file_secret_settings: PydanticBaseSettingsSource, # noqa: ARG003 + ) -> tuple[PydanticBaseSettingsSource, ...]: + """Use env vars and YAML as settings sources.""" + return ( + env_settings, + YamlConfigSettingsSource(settings_cls), + ) + + +settings = AgentSettings.model_validate({}) diff --git a/src/va_agent/server.py b/src/va_agent/server.py new file mode 100644 index 0000000..91b8bbd --- /dev/null +++ b/src/va_agent/server.py @@ -0,0 +1,10 @@ +"""FastAPI server exposing the RAG agent endpoint. + +NOTE: This file is a stub. The rag_eval module was removed in the +lean MCP implementation. This file is kept for reference but is not +functional. +""" + +from fastapi import FastAPI + +app = FastAPI(title="RAG Agent") diff --git a/src/va_agent/session.py b/src/va_agent/session.py new file mode 100644 index 0000000..bb7873a --- /dev/null +++ b/src/va_agent/session.py @@ -0,0 +1,582 @@ +"""Firestore-backed session service for Google ADK.""" + +from __future__ import annotations + +import asyncio +import logging +import time +import uuid +from typing import TYPE_CHECKING, Any, override + +from google.adk.errors.already_exists_error import AlreadyExistsError +from google.adk.events.event import Event +from google.adk.sessions import _session_util +from google.adk.sessions.base_session_service import ( + BaseSessionService, + GetSessionConfig, + ListSessionsResponse, +) +from google.adk.sessions.session import Session +from google.adk.sessions.state import State +from google.cloud.firestore_v1.async_transaction import async_transactional +from google.cloud.firestore_v1.base_query import FieldFilter +from google.cloud.firestore_v1.field_path import FieldPath +from google.genai.types import Content, Part + +if TYPE_CHECKING: + from google import genai + from google.cloud.firestore_v1.async_client import AsyncClient + +logger = logging.getLogger("google_adk." + __name__) + +_COMPACTION_LOCK_TTL = 300 # seconds + + +@async_transactional +async def _try_claim_compaction_txn(transaction: Any, session_ref: Any) -> bool: + """Atomically claim the compaction lock if it is free or stale.""" + snapshot = await session_ref.get(transaction=transaction) + if not snapshot.exists: + return False + data = snapshot.to_dict() or {} + lock_time = data.get("compaction_lock") + if lock_time and (time.time() - lock_time) < _COMPACTION_LOCK_TTL: + return False + transaction.update(session_ref, {"compaction_lock": time.time()}) + return True + + +class FirestoreSessionService(BaseSessionService): + """A Firestore-backed implementation of BaseSessionService. + + Firestore document layout (given ``collection_prefix="adk"``):: + + adk_app_states/{app_name} + → app-scoped state key/values + + adk_user_states/{app_name}__{user_id} + → user-scoped state key/values + + adk_sessions/{app_name}__{user_id}__{session_id} + → {app_name, user_id, session_id, state: {…}, last_update_time} + └─ events/{event_id} → serialised Event + """ + + def __init__( # noqa: PLR0913 + self, + *, + db: AsyncClient, + collection_prefix: str = "adk", + compaction_token_threshold: int | None = None, + compaction_model: str = "gemini-2.5-flash", + compaction_keep_recent: int = 10, + genai_client: genai.Client | None = None, + ) -> None: + """Initialize FirestoreSessionService. + + Args: + db: Firestore async client + collection_prefix: Prefix for Firestore collections + compaction_token_threshold: Token count threshold for compaction + compaction_model: Model to use for summarization + compaction_keep_recent: Number of recent events to keep + genai_client: GenAI client for compaction summaries + + """ + if compaction_token_threshold is not None and genai_client is None: + msg = "genai_client is required when compaction_token_threshold is set." + raise ValueError(msg) + self._db = db + self._prefix = collection_prefix + self._compaction_threshold = compaction_token_threshold + self._compaction_model = compaction_model + self._compaction_keep_recent = compaction_keep_recent + self._genai_client = genai_client + self._compaction_locks: dict[str, asyncio.Lock] = {} + self._active_tasks: set[asyncio.Task] = set() + + # ------------------------------------------------------------------ + # Document-reference helpers + # ------------------------------------------------------------------ + + def _app_state_ref(self, app_name: str) -> Any: + return self._db.collection(f"{self._prefix}_app_states").document(app_name) + + def _user_state_ref(self, app_name: str, user_id: str) -> Any: + return self._db.collection(f"{self._prefix}_user_states").document( + f"{app_name}__{user_id}" + ) + + def _session_ref(self, app_name: str, user_id: str, session_id: str) -> Any: + return self._db.collection(f"{self._prefix}_sessions").document( + f"{app_name}__{user_id}__{session_id}" + ) + + def _events_col(self, app_name: str, user_id: str, session_id: str) -> Any: + return self._session_ref(app_name, user_id, session_id).collection("events") + + # ------------------------------------------------------------------ + # State helpers + # ------------------------------------------------------------------ + + async def _get_app_state(self, app_name: str) -> dict[str, Any]: + snap = await self._app_state_ref(app_name).get() + return snap.to_dict() or {} if snap.exists else {} + + async def _get_user_state(self, app_name: str, user_id: str) -> dict[str, Any]: + snap = await self._user_state_ref(app_name, user_id).get() + return snap.to_dict() or {} if snap.exists else {} + + @staticmethod + def _merge_state( + app_state: dict[str, Any], + user_state: dict[str, Any], + session_state: dict[str, Any], + ) -> dict[str, Any]: + merged = dict(session_state) + for key, value in app_state.items(): + merged[State.APP_PREFIX + key] = value + for key, value in user_state.items(): + merged[State.USER_PREFIX + key] = value + return merged + + # ------------------------------------------------------------------ + # Compaction helpers + # ------------------------------------------------------------------ + + @staticmethod + def _events_to_text(events: list[Event]) -> str: + lines: list[str] = [] + for event in events: + if event.content and event.content.parts: + text = "".join(p.text or "" for p in event.content.parts) + if text: + role = "User" if event.author == "user" else "Assistant" + lines.append(f"{role}: {text}") + return "\n\n".join(lines) + + async def _generate_summary( + self, existing_summary: str, events: list[Event] + ) -> str: + conversation_text = self._events_to_text(events) + previous = ( + f"Previous summary of earlier conversation:\n{existing_summary}\n\n" + if existing_summary + else "" + ) + prompt = ( + "Summarize the following conversation between a user and an " + "assistant. Preserve:\n" + "- Key decisions and conclusions\n" + "- User preferences and requirements\n" + "- Important facts, names, and numbers\n" + "- The overall topic and direction of the conversation\n" + "- Any pending tasks or open questions\n\n" + f"{previous}" + f"Conversation:\n{conversation_text}\n\n" + "Provide a clear, comprehensive summary." + ) + if self._genai_client is None: + msg = "genai_client is required for compaction" + raise RuntimeError(msg) + response = await self._genai_client.aio.models.generate_content( + model=self._compaction_model, + contents=prompt, + ) + return response.text or "" + + async def _compact_session(self, session: Session) -> None: + app_name = session.app_name + user_id = session.user_id + session_id = session.id + + events_ref = self._events_col(app_name, user_id, session_id) + query = events_ref.order_by("timestamp") + event_docs = await query.get() + + if len(event_docs) <= self._compaction_keep_recent: + return + + all_events = [Event.model_validate(doc.to_dict()) for doc in event_docs] + events_to_summarize = all_events[: -self._compaction_keep_recent] + + session_snap = await self._session_ref(app_name, user_id, session_id).get() + existing_summary = (session_snap.to_dict() or {}).get( + "conversation_summary", "" + ) + + try: + summary = await self._generate_summary( + existing_summary, events_to_summarize + ) + except Exception: + logger.exception("Compaction summary generation failed; skipping.") + return + + # Write summary BEFORE deleting events so a crash between the two + # steps leaves safe duplication rather than data loss. + await self._session_ref(app_name, user_id, session_id).update( + {"conversation_summary": summary} + ) + + docs_to_delete = event_docs[: -self._compaction_keep_recent] + for i in range(0, len(docs_to_delete), 500): + batch = self._db.batch() + for doc in docs_to_delete[i : i + 500]: + batch.delete(doc.reference) + await batch.commit() + + logger.info( + "Compacted session %s: summarised %d events, kept %d.", + session_id, + len(docs_to_delete), + self._compaction_keep_recent, + ) + + async def _guarded_compact(self, session: Session) -> None: + """Run compaction in the background with per-session locking.""" + key = f"{session.app_name}__{session.user_id}__{session.id}" + lock = self._compaction_locks.setdefault(key, asyncio.Lock()) + + if lock.locked(): + logger.debug("Compaction already running locally for %s; skipping.", key) + return + + async with lock: + session_ref = self._session_ref( + session.app_name, session.user_id, session.id + ) + try: + transaction = self._db.transaction() + claimed = await _try_claim_compaction_txn(transaction, session_ref) + except Exception: + logger.exception("Failed to claim compaction lock for %s", key) + return + + if not claimed: + logger.debug( + "Compaction lock held by another instance for %s; skipping.", + key, + ) + return + + try: + await self._compact_session(session) + except Exception: + logger.exception("Background compaction failed for %s", key) + finally: + try: + await session_ref.update({"compaction_lock": None}) + except Exception: + logger.exception("Failed to release compaction lock for %s", key) + + async def close(self) -> None: + """Await all in-flight compaction tasks. Call before shutdown.""" + if self._active_tasks: + await asyncio.gather(*self._active_tasks, return_exceptions=True) + + # ------------------------------------------------------------------ + # BaseSessionService implementation + # ------------------------------------------------------------------ + + @override + async def create_session( + self, + *, + app_name: str, + user_id: str, + state: dict[str, Any] | None = None, + session_id: str | None = None, + ) -> Session: + if session_id and session_id.strip(): + session_id = session_id.strip() + existing = await self._session_ref(app_name, user_id, session_id).get() + if existing.exists: + msg = f"Session with id {session_id} already exists." + raise AlreadyExistsError(msg) + else: + session_id = str(uuid.uuid4()) + + state_deltas = _session_util.extract_state_delta(state) # type: ignore[attr-defined] + app_state_delta = state_deltas["app"] + user_state_delta = state_deltas["user"] + session_state = state_deltas["session"] + + write_coros: list = [] + if app_state_delta: + write_coros.append( + self._app_state_ref(app_name).set(app_state_delta, merge=True) + ) + if user_state_delta: + write_coros.append( + self._user_state_ref(app_name, user_id).set( + user_state_delta, merge=True + ) + ) + + now = time.time() + write_coros.append( + self._session_ref(app_name, user_id, session_id).set( + { + "app_name": app_name, + "user_id": user_id, + "session_id": session_id, + "state": session_state or {}, + "last_update_time": now, + } + ) + ) + await asyncio.gather(*write_coros) + + app_state, user_state = await asyncio.gather( + self._get_app_state(app_name), + self._get_user_state(app_name, user_id), + ) + merged = self._merge_state(app_state, user_state, session_state or {}) + + return Session( + app_name=app_name, + user_id=user_id, + id=session_id, + state=merged, + last_update_time=now, + ) + + @override + async def get_session( + self, + *, + app_name: str, + user_id: str, + session_id: str, + config: GetSessionConfig | None = None, + ) -> Session | None: + snap = await self._session_ref(app_name, user_id, session_id).get() + if not snap.exists: + return None + + session_data = snap.to_dict() + + # Build events query + events_ref = self._events_col(app_name, user_id, session_id) + query = events_ref + if config and config.after_timestamp: + query = query.where( + filter=FieldFilter("timestamp", ">=", config.after_timestamp) + ) + query = query.order_by("timestamp") + + event_docs, app_state, user_state = await asyncio.gather( + query.get(), + self._get_app_state(app_name), + self._get_user_state(app_name, user_id), + ) + events = [Event.model_validate(doc.to_dict()) for doc in event_docs] + + if config and config.num_recent_events: + events = events[-config.num_recent_events :] + + # Prepend conversation summary as synthetic context events + conversation_summary = session_data.get("conversation_summary") + if conversation_summary: + summary_event = Event( + id="summary-context", + author="user", + content=Content( + role="user", + parts=[ + Part( + text=( + "[Conversation context from previous" + " messages]\n" + f"{conversation_summary}" + ) + ) + ], + ), + timestamp=0.0, + invocation_id="compaction-summary", + ) + ack_event = Event( + id="summary-ack", + author=app_name, + content=Content( + role="model", + parts=[ + Part( + text=( + "Understood, I have the context from our" + " previous conversation and will continue" + " accordingly." + ) + ) + ], + ), + timestamp=0.001, + invocation_id="compaction-summary", + ) + events = [summary_event, ack_event, *events] + + # Merge scoped state + merged = self._merge_state(app_state, user_state, session_data.get("state", {})) + + return Session( + app_name=app_name, + user_id=user_id, + id=session_id, + state=merged, + events=events, + last_update_time=session_data.get("last_update_time", 0.0), + ) + + @override + async def list_sessions( + self, *, app_name: str, user_id: str | None = None + ) -> ListSessionsResponse: + query = self._db.collection(f"{self._prefix}_sessions").where( + filter=FieldFilter("app_name", "==", app_name) + ) + if user_id is not None: + query = query.where(filter=FieldFilter("user_id", "==", user_id)) + + docs = await query.get() + if not docs: + return ListSessionsResponse() + + doc_dicts: list[dict[str, Any]] = [doc.to_dict() or {} for doc in docs] + + # Pre-fetch app state and all distinct user states in parallel + unique_user_ids = list({d["user_id"] for d in doc_dicts}) + app_state, *user_states = await asyncio.gather( + self._get_app_state(app_name), + *(self._get_user_state(app_name, uid) for uid in unique_user_ids), + ) + user_state_cache = dict(zip(unique_user_ids, user_states, strict=False)) + + sessions: list[Session] = [] + for data in doc_dicts: + s_user_id = data["user_id"] + merged = self._merge_state( + app_state, + user_state_cache[s_user_id], + data.get("state", {}), + ) + + sessions.append( + Session( + app_name=app_name, + user_id=s_user_id, + id=data["session_id"], + state=merged, + events=[], + last_update_time=data.get("last_update_time", 0.0), + ) + ) + + return ListSessionsResponse(sessions=sessions) + + @override + async def delete_session( + self, *, app_name: str, user_id: str, session_id: str + ) -> None: + ref = self._session_ref(app_name, user_id, session_id) + await self._db.recursive_delete(ref) + + @override + async def append_event(self, session: Session, event: Event) -> Event: + if event.partial: + return event + + t0 = time.monotonic() + + app_name = session.app_name + user_id = session.user_id + session_id = session.id + + # Base class: strips temp state, applies delta to in-memory session, + # appends event to session.events + event = await super().append_event(session=session, event=event) + session.last_update_time = event.timestamp + + # Persist event document + event_data = event.model_dump(mode="json", exclude_none=True) + await ( + self._events_col(app_name, user_id, session_id) + .document(event.id) + .set(event_data) + ) + + # Persist state deltas + session_ref = self._session_ref(app_name, user_id, session_id) + + if event.actions and event.actions.state_delta: + state_deltas = _session_util.extract_state_delta(event.actions.state_delta) + + write_coros: list = [] + if state_deltas["app"]: + write_coros.append( + self._app_state_ref(app_name).set(state_deltas["app"], merge=True) + ) + if state_deltas["user"]: + write_coros.append( + self._user_state_ref(app_name, user_id).set( + state_deltas["user"], merge=True + ) + ) + + if state_deltas["session"]: + field_updates: dict[str, Any] = { + FieldPath("state", k).to_api_repr(): v + for k, v in state_deltas["session"].items() + } + field_updates["last_update_time"] = event.timestamp + write_coros.append(session_ref.update(field_updates)) + else: + write_coros.append( + session_ref.update({"last_update_time": event.timestamp}) + ) + + await asyncio.gather(*write_coros) + else: + await session_ref.update({"last_update_time": event.timestamp}) + + # Log token usage + if event.usage_metadata: + meta = event.usage_metadata + logger.info( + "Token usage for session %s event %s: " + "prompt=%s, candidates=%s, total=%s", + session_id, + event.id, + meta.prompt_token_count, + meta.candidates_token_count, + meta.total_token_count, + ) + + # Trigger compaction if total token count exceeds threshold + if ( + self._compaction_threshold is not None + and event.usage_metadata + and event.usage_metadata.total_token_count + and event.usage_metadata.total_token_count >= self._compaction_threshold + ): + logger.info( + "Compaction triggered for session %s: " + "total_token_count=%d >= threshold=%d", + session_id, + event.usage_metadata.total_token_count, + self._compaction_threshold, + ) + task = asyncio.create_task(self._guarded_compact(session)) + self._active_tasks.add(task) + task.add_done_callback(self._active_tasks.discard) + + elapsed = time.monotonic() - t0 + logger.info( + "append_event completed for session %s event %s in %.3fs", + session_id, + event.id, + elapsed, + ) + + return event diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..ed72390 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,35 @@ +"""Shared fixtures for Firestore session service tests.""" + +from __future__ import annotations + +import os +import uuid + +import pytest +import pytest_asyncio +from google.cloud.firestore_v1.async_client import AsyncClient + +from va_agent.session import FirestoreSessionService + +os.environ.setdefault("FIRESTORE_EMULATOR_HOST", "localhost:8153") + + +@pytest_asyncio.fixture +async def db(): + return AsyncClient(project="test-project") + + +@pytest_asyncio.fixture +async def service(db: AsyncClient): + prefix = f"test_{uuid.uuid4().hex[:8]}" + return FirestoreSessionService(db=db, collection_prefix=prefix) + + +@pytest.fixture +def app_name(): + return f"app_{uuid.uuid4().hex[:8]}" + + +@pytest.fixture +def user_id(): + return f"user_{uuid.uuid4().hex[:8]}" diff --git a/tests/test_compaction.py b/tests/test_compaction.py new file mode 100644 index 0000000..3e7b232 --- /dev/null +++ b/tests/test_compaction.py @@ -0,0 +1,515 @@ +"""Tests for conversation compaction in FirestoreSessionService.""" + +from __future__ import annotations + +import asyncio +import time +from unittest.mock import AsyncMock, MagicMock, patch +import uuid + +import pytest +import pytest_asyncio +from google import genai +from google.adk.events.event import Event +from google.cloud.firestore_v1.async_client import AsyncClient +from google.genai.types import Content, GenerateContentResponseUsageMetadata, Part + +from va_agent.session import FirestoreSessionService, _try_claim_compaction_txn + +pytestmark = pytest.mark.asyncio + + +@pytest_asyncio.fixture +async def mock_genai_client(): + client = MagicMock(spec=genai.Client) + response = MagicMock() + response.text = "Summary of the conversation so far." + client.aio.models.generate_content = AsyncMock(return_value=response) + return client + + +@pytest_asyncio.fixture +async def compaction_service(db: AsyncClient, mock_genai_client): + prefix = f"test_{uuid.uuid4().hex[:8]}" + return FirestoreSessionService( + db=db, + collection_prefix=prefix, + compaction_token_threshold=100, + compaction_keep_recent=2, + genai_client=mock_genai_client, + ) + + +# ------------------------------------------------------------------ +# __init__ validation +# ------------------------------------------------------------------ + + +class TestCompactionInit: + async def test_requires_genai_client(self, db): + with pytest.raises(ValueError, match="genai_client is required"): + FirestoreSessionService( + db=db, + compaction_token_threshold=1000, + ) + + async def test_no_threshold_no_client_ok(self, db): + svc = FirestoreSessionService(db=db) + assert svc._compaction_threshold is None + + +# ------------------------------------------------------------------ +# Compaction trigger +# ------------------------------------------------------------------ + + +class TestCompactionTrigger: + async def test_compaction_triggered_above_threshold( + self, compaction_service, mock_genai_client, app_name, user_id + ): + session = await compaction_service.create_session( + app_name=app_name, user_id=user_id + ) + + # Add 5 events, last one with usage_metadata above threshold + base = time.time() + for i in range(4): + e = Event( + author="user" if i % 2 == 0 else app_name, + content=Content( + role="user" if i % 2 == 0 else "model", + parts=[Part(text=f"message {i}")], + ), + timestamp=base + i, + invocation_id=f"inv-{i}", + ) + await compaction_service.append_event(session, e) + + # This event crosses the threshold + trigger_event = Event( + author=app_name, + content=Content( + role="model", parts=[Part(text="final response")] + ), + timestamp=base + 4, + invocation_id="inv-4", + usage_metadata=GenerateContentResponseUsageMetadata( + total_token_count=200, + ), + ) + await compaction_service.append_event(session, trigger_event) + await compaction_service.close() + + # Summary generation should have been called + mock_genai_client.aio.models.generate_content.assert_called_once() + + # Fetch session: should have summary + only keep_recent events + fetched = await compaction_service.get_session( + app_name=app_name, user_id=user_id, session_id=session.id + ) + # 2 synthetic summary events + 2 kept real events + assert len(fetched.events) == 4 + assert fetched.events[0].id == "summary-context" + assert fetched.events[1].id == "summary-ack" + assert "Summary of the conversation" in fetched.events[0].content.parts[0].text + + async def test_no_compaction_below_threshold( + self, compaction_service, mock_genai_client, app_name, user_id + ): + session = await compaction_service.create_session( + app_name=app_name, user_id=user_id + ) + event = Event( + author=app_name, + content=Content( + role="model", parts=[Part(text="short reply")] + ), + timestamp=time.time(), + invocation_id="inv-1", + usage_metadata=GenerateContentResponseUsageMetadata( + total_token_count=50, + ), + ) + await compaction_service.append_event(session, event) + + mock_genai_client.aio.models.generate_content.assert_not_called() + + async def test_no_compaction_without_usage_metadata( + self, compaction_service, mock_genai_client, app_name, user_id + ): + session = await compaction_service.create_session( + app_name=app_name, user_id=user_id + ) + event = Event( + author="user", + content=Content( + role="user", parts=[Part(text="hello")] + ), + timestamp=time.time(), + invocation_id="inv-1", + ) + await compaction_service.append_event(session, event) + + mock_genai_client.aio.models.generate_content.assert_not_called() + + +# ------------------------------------------------------------------ +# Compaction with too few events (nothing to compact) +# ------------------------------------------------------------------ + + +class TestCompactionEdgeCases: + async def test_skip_when_fewer_events_than_keep_recent( + self, compaction_service, mock_genai_client, app_name, user_id + ): + session = await compaction_service.create_session( + app_name=app_name, user_id=user_id + ) + # Only 2 events, keep_recent=2 → nothing to summarize + for i in range(2): + e = Event( + author="user", + content=Content( + role="user", parts=[Part(text=f"msg {i}")] + ), + timestamp=time.time() + i, + invocation_id=f"inv-{i}", + ) + await compaction_service.append_event(session, e) + + # Trigger compaction manually even though threshold wouldn't fire + await compaction_service._compact_session(session) + + mock_genai_client.aio.models.generate_content.assert_not_called() + + async def test_summary_generation_failure_is_non_fatal( + self, compaction_service, mock_genai_client, app_name, user_id + ): + session = await compaction_service.create_session( + app_name=app_name, user_id=user_id + ) + for i in range(5): + e = Event( + author="user", + content=Content( + role="user", parts=[Part(text=f"msg {i}")] + ), + timestamp=time.time() + i, + invocation_id=f"inv-{i}", + ) + await compaction_service.append_event(session, e) + + # Make summary generation fail + mock_genai_client.aio.models.generate_content = AsyncMock( + side_effect=RuntimeError("API error") + ) + + # Should not raise + await compaction_service._compact_session(session) + + # All events should still be present + fetched = await compaction_service.get_session( + app_name=app_name, user_id=user_id, session_id=session.id + ) + assert len(fetched.events) == 5 + + +# ------------------------------------------------------------------ +# get_session with summary +# ------------------------------------------------------------------ + + +class TestGetSessionWithSummary: + async def test_no_summary_no_synthetic_events( + self, compaction_service, app_name, user_id + ): + session = await compaction_service.create_session( + app_name=app_name, user_id=user_id + ) + event = Event( + author="user", + content=Content( + role="user", parts=[Part(text="hello")] + ), + timestamp=time.time(), + invocation_id="inv-1", + ) + await compaction_service.append_event(session, event) + + fetched = await compaction_service.get_session( + app_name=app_name, user_id=user_id, session_id=session.id + ) + assert len(fetched.events) == 1 + assert fetched.events[0].author == "user" + + +# ------------------------------------------------------------------ +# _events_to_text +# ------------------------------------------------------------------ + + +class TestEventsToText: + async def test_formats_user_and_assistant(self): + events = [ + Event( + author="user", + content=Content( + role="user", parts=[Part(text="Hi there")] + ), + timestamp=1.0, + invocation_id="inv-1", + ), + Event( + author="bot", + content=Content( + role="model", parts=[Part(text="Hello!")] + ), + timestamp=2.0, + invocation_id="inv-2", + ), + ] + text = FirestoreSessionService._events_to_text(events) + assert "User: Hi there" in text + assert "Assistant: Hello!" in text + + async def test_skips_events_without_text(self): + events = [ + Event( + author="user", + timestamp=1.0, + invocation_id="inv-1", + ), + ] + text = FirestoreSessionService._events_to_text(events) + assert text == "" + + +# ------------------------------------------------------------------ +# Firestore distributed lock +# ------------------------------------------------------------------ + + +class TestCompactionLock: + async def test_claim_and_release( + self, compaction_service, app_name, user_id + ): + session = await compaction_service.create_session( + app_name=app_name, user_id=user_id + ) + session_ref = compaction_service._session_ref( + app_name, user_id, session.id + ) + + # Claim the lock + transaction = compaction_service._db.transaction() + claimed = await _try_claim_compaction_txn(transaction, session_ref) + assert claimed is True + + # Lock is now held — second claim should fail + transaction2 = compaction_service._db.transaction() + claimed2 = await _try_claim_compaction_txn(transaction2, session_ref) + assert claimed2 is False + + # Release the lock + await session_ref.update({"compaction_lock": None}) + + # Can claim again after release + transaction3 = compaction_service._db.transaction() + claimed3 = await _try_claim_compaction_txn(transaction3, session_ref) + assert claimed3 is True + + async def test_stale_lock_can_be_reclaimed( + self, compaction_service, app_name, user_id + ): + session = await compaction_service.create_session( + app_name=app_name, user_id=user_id + ) + session_ref = compaction_service._session_ref( + app_name, user_id, session.id + ) + + # Set a stale lock (older than TTL) + await session_ref.update({"compaction_lock": time.time() - 600}) + + # Should be able to reclaim a stale lock + transaction = compaction_service._db.transaction() + claimed = await _try_claim_compaction_txn(transaction, session_ref) + assert claimed is True + + async def test_claim_nonexistent_session(self, compaction_service): + ref = compaction_service._session_ref("no_app", "no_user", "no_id") + transaction = compaction_service._db.transaction() + claimed = await _try_claim_compaction_txn(transaction, ref) + assert claimed is False + + +# ------------------------------------------------------------------ +# Guarded compact +# ------------------------------------------------------------------ + + +class TestGuardedCompact: + async def test_local_lock_skips_concurrent( + self, compaction_service, mock_genai_client, app_name, user_id + ): + session = await compaction_service.create_session( + app_name=app_name, user_id=user_id + ) + for i in range(5): + e = Event( + author="user", + content=Content( + role="user", parts=[Part(text=f"msg {i}")] + ), + timestamp=time.time() + i, + invocation_id=f"inv-{i}", + ) + await compaction_service.append_event(session, e) + + # Hold the in-process lock so _guarded_compact skips + key = f"{app_name}__{user_id}__{session.id}" + lock = compaction_service._compaction_locks.setdefault( + key, asyncio.Lock() + ) + async with lock: + await compaction_service._guarded_compact(session) + + mock_genai_client.aio.models.generate_content.assert_not_called() + + async def test_firestore_lock_held_skips( + self, compaction_service, mock_genai_client, app_name, user_id + ): + session = await compaction_service.create_session( + app_name=app_name, user_id=user_id + ) + for i in range(5): + e = Event( + author="user", + content=Content( + role="user", parts=[Part(text=f"msg {i}")] + ), + timestamp=time.time() + i, + invocation_id=f"inv-{i}", + ) + await compaction_service.append_event(session, e) + + # Set a fresh Firestore lock (simulating another instance) + session_ref = compaction_service._session_ref( + app_name, user_id, session.id + ) + await session_ref.update({"compaction_lock": time.time()}) + + await compaction_service._guarded_compact(session) + + mock_genai_client.aio.models.generate_content.assert_not_called() + + async def test_claim_failure_logs_and_skips( + self, compaction_service, mock_genai_client, app_name, user_id + ): + session = await compaction_service.create_session( + app_name=app_name, user_id=user_id + ) + + with patch( + "va_agent.session._try_claim_compaction_txn", + side_effect=RuntimeError("Firestore down"), + ): + await compaction_service._guarded_compact(session) + + mock_genai_client.aio.models.generate_content.assert_not_called() + + async def test_compaction_failure_releases_lock( + self, compaction_service, mock_genai_client, app_name, user_id + ): + session = await compaction_service.create_session( + app_name=app_name, user_id=user_id + ) + + # Make _compact_session raise an unhandled exception + with patch.object( + compaction_service, + "_compact_session", + side_effect=RuntimeError("unexpected crash"), + ): + await compaction_service._guarded_compact(session) + + # Lock should be released even after failure + session_ref = compaction_service._session_ref( + app_name, user_id, session.id + ) + snap = await session_ref.get() + assert snap.to_dict().get("compaction_lock") is None + + async def test_lock_release_failure_is_non_fatal( + self, compaction_service, mock_genai_client, app_name, user_id + ): + session = await compaction_service.create_session( + app_name=app_name, user_id=user_id + ) + + original_session_ref = compaction_service._session_ref + + def patched_session_ref(an, uid, sid): + ref = original_session_ref(an, uid, sid) + original_update = ref.update + + async def failing_update(data): + if "compaction_lock" in data: + raise RuntimeError("Firestore write failed") + return await original_update(data) + + ref.update = failing_update + return ref + + with patch.object( + compaction_service, + "_session_ref", + side_effect=patched_session_ref, + ): + # Should not raise despite lock release failure + await compaction_service._guarded_compact(session) + + +# ------------------------------------------------------------------ +# close() +# ------------------------------------------------------------------ + + +class TestClose: + async def test_close_no_tasks(self, compaction_service): + await compaction_service.close() + + async def test_close_awaits_tasks( + self, compaction_service, mock_genai_client, app_name, user_id + ): + session = await compaction_service.create_session( + app_name=app_name, user_id=user_id + ) + base = time.time() + for i in range(4): + e = Event( + author="user", + content=Content( + role="user", parts=[Part(text=f"msg {i}")] + ), + timestamp=base + i, + invocation_id=f"inv-{i}", + ) + await compaction_service.append_event(session, e) + + trigger = Event( + author=app_name, + content=Content( + role="model", parts=[Part(text="trigger")] + ), + timestamp=base + 4, + invocation_id="inv-4", + usage_metadata=GenerateContentResponseUsageMetadata( + total_token_count=200, + ), + ) + await compaction_service.append_event(session, trigger) + assert len(compaction_service._active_tasks) > 0 + + await compaction_service.close() + assert len(compaction_service._active_tasks) == 0 diff --git a/tests/test_firestore_session_service.py b/tests/test_firestore_session_service.py new file mode 100644 index 0000000..c766c1b --- /dev/null +++ b/tests/test_firestore_session_service.py @@ -0,0 +1,428 @@ +"""Tests for FirestoreSessionService against the Firestore emulator.""" + +from __future__ import annotations + +import time +import uuid + +import pytest +from google.adk.errors.already_exists_error import AlreadyExistsError +from google.adk.events.event import Event +from google.adk.events.event_actions import EventActions +from google.adk.sessions.base_session_service import GetSessionConfig +from google.genai.types import Content, Part + +pytestmark = pytest.mark.asyncio + + +# ------------------------------------------------------------------ +# create_session +# ------------------------------------------------------------------ + + +class TestCreateSession: + async def test_auto_generates_id(self, service, app_name, user_id): + session = await service.create_session( + app_name=app_name, user_id=user_id + ) + assert session.id + assert session.app_name == app_name + assert session.user_id == user_id + assert session.last_update_time > 0 + + async def test_custom_id(self, service, app_name, user_id): + sid = "my-custom-session" + session = await service.create_session( + app_name=app_name, user_id=user_id, session_id=sid + ) + assert session.id == sid + + async def test_duplicate_id_raises(self, service, app_name, user_id): + sid = "dup-session" + await service.create_session( + app_name=app_name, user_id=user_id, session_id=sid + ) + with pytest.raises(AlreadyExistsError): + await service.create_session( + app_name=app_name, user_id=user_id, session_id=sid + ) + + async def test_session_state(self, service, app_name, user_id): + session = await service.create_session( + app_name=app_name, + user_id=user_id, + state={"count": 42}, + ) + assert session.state["count"] == 42 + + async def test_scoped_state(self, service, app_name, user_id): + session = await service.create_session( + app_name=app_name, + user_id=user_id, + state={ + "app:global_flag": True, + "user:lang": "es", + "local_key": "val", + }, + ) + assert session.state["app:global_flag"] is True + assert session.state["user:lang"] == "es" + assert session.state["local_key"] == "val" + + async def test_temp_state_not_persisted(self, service, app_name, user_id): + session = await service.create_session( + app_name=app_name, + user_id=user_id, + state={"temp:scratch": "gone", "keep": "yes"}, + ) + retrieved = await service.get_session( + app_name=app_name, user_id=user_id, session_id=session.id + ) + assert "temp:scratch" not in retrieved.state + assert retrieved.state["keep"] == "yes" + + +# ------------------------------------------------------------------ +# get_session +# ------------------------------------------------------------------ + + +class TestGetSession: + async def test_nonexistent_returns_none(self, service, app_name, user_id): + result = await service.get_session( + app_name=app_name, user_id=user_id, session_id="nope" + ) + assert result is None + + async def test_roundtrip(self, service, app_name, user_id): + created = await service.create_session( + app_name=app_name, + user_id=user_id, + state={"foo": "bar"}, + ) + fetched = await service.get_session( + app_name=app_name, user_id=user_id, session_id=created.id + ) + assert fetched is not None + assert fetched.id == created.id + assert fetched.state["foo"] == "bar" + assert fetched.last_update_time == pytest.approx( + created.last_update_time, abs=0.01 + ) + + async def test_returns_events(self, service, app_name, user_id): + session = await service.create_session( + app_name=app_name, user_id=user_id + ) + event = Event( + author="user", + content=Content(parts=[Part(text="hello")]), + timestamp=time.time(), + invocation_id="inv-1", + ) + await service.append_event(session, event) + + fetched = await service.get_session( + app_name=app_name, user_id=user_id, session_id=session.id + ) + assert len(fetched.events) == 1 + assert fetched.events[0].author == "user" + + async def test_num_recent_events(self, service, app_name, user_id): + session = await service.create_session( + app_name=app_name, user_id=user_id + ) + for i in range(5): + e = Event( + author="user", + timestamp=time.time() + i, + invocation_id=f"inv-{i}", + ) + await service.append_event(session, e) + + fetched = await service.get_session( + app_name=app_name, + user_id=user_id, + session_id=session.id, + config=GetSessionConfig(num_recent_events=2), + ) + assert len(fetched.events) == 2 + + async def test_after_timestamp(self, service, app_name, user_id): + session = await service.create_session( + app_name=app_name, user_id=user_id + ) + base = time.time() + for i in range(3): + e = Event( + author="user", + timestamp=base + i, + invocation_id=f"inv-{i}", + ) + await service.append_event(session, e) + + fetched = await service.get_session( + app_name=app_name, + user_id=user_id, + session_id=session.id, + config=GetSessionConfig(after_timestamp=base + 1), + ) + assert len(fetched.events) == 2 + + +# ------------------------------------------------------------------ +# list_sessions +# ------------------------------------------------------------------ + + +class TestListSessions: + async def test_empty(self, service, app_name, user_id): + resp = await service.list_sessions( + app_name=app_name, user_id=user_id + ) + assert resp.sessions == [] or resp.sessions is None + + async def test_returns_created_sessions( + self, service, app_name, user_id + ): + s1 = await service.create_session( + app_name=app_name, user_id=user_id + ) + s2 = await service.create_session( + app_name=app_name, user_id=user_id + ) + resp = await service.list_sessions( + app_name=app_name, user_id=user_id + ) + ids = {s.id for s in resp.sessions} + assert s1.id in ids + assert s2.id in ids + + async def test_filter_by_user(self, service, app_name): + uid1 = f"user_{uuid.uuid4().hex[:8]}" + uid2 = f"user_{uuid.uuid4().hex[:8]}" + await service.create_session(app_name=app_name, user_id=uid1) + await service.create_session(app_name=app_name, user_id=uid2) + + resp = await service.list_sessions( + app_name=app_name, user_id=uid1 + ) + assert len(resp.sessions) == 1 + assert resp.sessions[0].user_id == uid1 + + async def test_sessions_have_merged_state( + self, service, app_name, user_id + ): + await service.create_session( + app_name=app_name, + user_id=user_id, + state={"app:shared": "yes", "local": "val"}, + ) + resp = await service.list_sessions( + app_name=app_name, user_id=user_id + ) + s = resp.sessions[0] + assert s.state["app:shared"] == "yes" + assert s.state["local"] == "val" + + async def test_sessions_have_no_events( + self, service, app_name, user_id + ): + session = await service.create_session( + app_name=app_name, user_id=user_id + ) + event = Event( + author="user", timestamp=time.time(), invocation_id="inv-1" + ) + await service.append_event(session, event) + + resp = await service.list_sessions( + app_name=app_name, user_id=user_id + ) + assert resp.sessions[0].events == [] + + +# ------------------------------------------------------------------ +# delete_session +# ------------------------------------------------------------------ + + +class TestDeleteSession: + async def test_delete(self, service, app_name, user_id): + session = await service.create_session( + app_name=app_name, user_id=user_id + ) + await service.delete_session( + app_name=app_name, user_id=user_id, session_id=session.id + ) + result = await service.get_session( + app_name=app_name, user_id=user_id, session_id=session.id + ) + assert result is None + + async def test_delete_removes_events(self, service, app_name, user_id): + session = await service.create_session( + app_name=app_name, user_id=user_id + ) + event = Event( + author="user", timestamp=time.time(), invocation_id="inv-1" + ) + await service.append_event(session, event) + + await service.delete_session( + app_name=app_name, user_id=user_id, session_id=session.id + ) + result = await service.get_session( + app_name=app_name, user_id=user_id, session_id=session.id + ) + assert result is None + + +# ------------------------------------------------------------------ +# append_event +# ------------------------------------------------------------------ + + +class TestAppendEvent: + async def test_basic(self, service, app_name, user_id): + session = await service.create_session( + app_name=app_name, user_id=user_id + ) + event = Event( + author="user", + content=Content(parts=[Part(text="hi")]), + timestamp=time.time(), + invocation_id="inv-1", + ) + returned = await service.append_event(session, event) + assert returned.id == event.id + assert returned.timestamp > 0 + + async def test_partial_event_not_persisted( + self, service, app_name, user_id + ): + session = await service.create_session( + app_name=app_name, user_id=user_id + ) + event = Event( + author="user", + partial=True, + timestamp=time.time(), + invocation_id="inv-1", + ) + await service.append_event(session, event) + + fetched = await service.get_session( + app_name=app_name, user_id=user_id, session_id=session.id + ) + assert len(fetched.events) == 0 + + async def test_session_state_delta(self, service, app_name, user_id): + session = await service.create_session( + app_name=app_name, user_id=user_id + ) + event = Event( + author="agent", + actions=EventActions(state_delta={"counter": 1}), + timestamp=time.time(), + invocation_id="inv-1", + ) + await service.append_event(session, event) + + fetched = await service.get_session( + app_name=app_name, user_id=user_id, session_id=session.id + ) + assert fetched.state["counter"] == 1 + + async def test_app_state_delta(self, service, app_name, user_id): + session = await service.create_session( + app_name=app_name, user_id=user_id + ) + event = Event( + author="agent", + actions=EventActions(state_delta={"app:version": "2.0"}), + timestamp=time.time(), + invocation_id="inv-1", + ) + await service.append_event(session, event) + + fetched = await service.get_session( + app_name=app_name, user_id=user_id, session_id=session.id + ) + assert fetched.state["app:version"] == "2.0" + + async def test_user_state_delta(self, service, app_name, user_id): + session = await service.create_session( + app_name=app_name, user_id=user_id + ) + event = Event( + author="agent", + actions=EventActions(state_delta={"user:pref": "dark"}), + timestamp=time.time(), + invocation_id="inv-1", + ) + await service.append_event(session, event) + + fetched = await service.get_session( + app_name=app_name, user_id=user_id, session_id=session.id + ) + assert fetched.state["user:pref"] == "dark" + + async def test_updates_last_update_time( + self, service, app_name, user_id + ): + session = await service.create_session( + app_name=app_name, user_id=user_id + ) + original_time = session.last_update_time + + event = Event( + author="user", + timestamp=time.time() + 10, + invocation_id="inv-1", + ) + await service.append_event(session, event) + + fetched = await service.get_session( + app_name=app_name, user_id=user_id, session_id=session.id + ) + assert fetched.last_update_time > original_time + + async def test_multiple_events_accumulate( + self, service, app_name, user_id + ): + session = await service.create_session( + app_name=app_name, user_id=user_id + ) + for i in range(3): + e = Event( + author="user", + content=Content(parts=[Part(text=f"msg {i}")]), + timestamp=time.time() + i, + invocation_id=f"inv-{i}", + ) + await service.append_event(session, e) + + fetched = await service.get_session( + app_name=app_name, user_id=user_id, session_id=session.id + ) + assert len(fetched.events) == 3 + + async def test_app_state_shared_across_sessions( + self, service, app_name, user_id + ): + s1 = await service.create_session( + app_name=app_name, user_id=user_id + ) + event = Event( + author="agent", + actions=EventActions(state_delta={"app:shared_val": 99}), + timestamp=time.time(), + invocation_id="inv-1", + ) + await service.append_event(s1, event) + + s2 = await service.create_session( + app_name=app_name, user_id=user_id + ) + assert s2.state["app:shared_val"] == 99 diff --git a/uv.lock b/uv.lock index 2c0fb75..2ad41b5 100644 --- a/uv.lock +++ b/uv.lock @@ -1,43 +1,6 @@ version = 1 revision = 3 requires-python = "==3.12.*" -resolution-markers = [ - "sys_platform == 'win32'", - "sys_platform != 'win32'", -] - -[[package]] -name = "ag-ui-protocol" -version = "0.1.13" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/04/b5/fc0b65b561d00d88811c8a7d98ee735833f81554be244340950e7b65820c/ag_ui_protocol-0.1.13.tar.gz", hash = "sha256:811d7d7dcce4783dec252918f40b717ebfa559399bf6b071c4ba47c0c1e21bcb", size = 5671, upload-time = "2026-02-19T18:40:38.602Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/9f/b833c1ab1999da35ebad54841ae85d2c2764c931da9a6f52d8541b6901b2/ag_ui_protocol-0.1.13-py3-none-any.whl", hash = "sha256:1393fa894c1e8416efe184168a50689e760d05b32f4646eebb8ff423dddf8e8f", size = 8053, upload-time = "2026-02-19T18:40:37.27Z" }, -] - -[[package]] -name = "aiofile" -version = "3.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "caio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/67/e2/d7cb819de8df6b5c1968a2756c3cb4122d4fa2b8fc768b53b7c9e5edb646/aiofile-3.9.0.tar.gz", hash = "sha256:e5ad718bb148b265b6df1b3752c4d1d83024b93da9bd599df74b9d9ffcf7919b", size = 17943, upload-time = "2024-10-08T10:39:35.846Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/25/da1f0b4dd970e52bf5a36c204c107e11a0c6d3ed195eba0bfbc664c312b2/aiofile-3.9.0-py3-none-any.whl", hash = "sha256:ce2f6c1571538cbdfa0143b04e16b208ecb0e9cb4148e528af8a640ed51cc8aa", size = 19539, upload-time = "2024-10-08T10:39:32.955Z" }, -] - -[[package]] -name = "aiofiles" -version = "25.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" }, -] [[package]] name = "aiohappyeyeballs" @@ -95,6 +58,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, ] +[[package]] +name = "aiosqlite" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821, upload-time = "2025-12-23T19:25:43.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" }, +] + +[[package]] +name = "alembic" +version = "1.18.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725, upload-time = "2026-02-10T16:00:47.195Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893, upload-time = "2026-02-10T16:00:49.997Z" }, +] + [[package]] name = "annotated-doc" version = "0.0.4" @@ -113,25 +99,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] -[[package]] -name = "anthropic" -version = "0.83.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "docstring-parser" }, - { name = "httpx" }, - { name = "jiter" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/db/e5/02cd2919ec327b24234abb73082e6ab84c451182cc3cc60681af700f4c63/anthropic-0.83.0.tar.gz", hash = "sha256:a8732c68b41869266c3034541a31a29d8be0f8cd0a714f9edce3128b351eceb4", size = 534058, upload-time = "2026-02-19T19:26:38.904Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/75/b9d58e4e2a4b1fc3e75ffbab978f999baf8b7c4ba9f96e60edb918ba386b/anthropic-0.83.0-py3-none-any.whl", hash = "sha256:f069ef508c73b8f9152e8850830d92bd5ef185645dbacf234bb213344a274810", size = 456991, upload-time = "2026-02-19T19:26:40.114Z" }, -] - [[package]] name = "anyio" version = "4.12.1" @@ -145,15 +112,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, ] -[[package]] -name = "argcomplete" -version = "3.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", size = 73754, upload-time = "2025-10-20T03:33:34.741Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" }, -] - [[package]] name = "attrs" version = "25.4.0" @@ -175,72 +133,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9b/73/f7084bf12755113cd535ae586782ff3a6e710bfbe6a0d13d1c2f81ffbbfa/authlib-1.6.8-py2.py3-none-any.whl", hash = "sha256:97286fd7a15e6cfefc32771c8ef9c54f0ed58028f1322de6a2a7c969c3817888", size = 244116, upload-time = "2026-02-14T04:02:15.579Z" }, ] -[[package]] -name = "backoff" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, -] - -[[package]] -name = "beartype" -version = "0.22.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/94/1009e248bbfbab11397abca7193bea6626806be9a327d399810d523a07cb/beartype-0.22.9.tar.gz", hash = "sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f", size = 1608866, upload-time = "2025-12-13T06:50:30.72Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, -] - -[[package]] -name = "boto3" -version = "1.42.53" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "botocore" }, - { name = "jmespath" }, - { name = "s3transfer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/62/ef/03460914019db52301a6084460f0dd738f3f9e89d2ddf5bd33cef8168e63/boto3-1.42.53.tar.gz", hash = "sha256:56bc79388763995852b6d3fe48023e661e63fc2e60a921273c422d0171b9fbfb", size = 112812, upload-time = "2026-02-19T20:33:58.422Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/ea/08dfba25a5822a7254b20aa905a9937177ca1532dd7f47c926875dd87299/boto3-1.42.53-py3-none-any.whl", hash = "sha256:3bd32f3508a6e9851671d0ef3b1f9e8ee7e8c095aa0488bcd9e86074aef5b7eb", size = 140555, upload-time = "2026-02-19T20:33:55.691Z" }, -] - -[[package]] -name = "botocore" -version = "1.42.53" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jmespath" }, - { name = "python-dateutil" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7a/b6/0b2ab38e422e93f28b7a394a29881a9d767b79831fa1957a3ccab996a70e/botocore-1.42.53.tar.gz", hash = "sha256:0bc1a2e1b6ae4c8397c9bede3bb9007b4f16e159ef2ca7f24837e31d5860caac", size = 14918644, upload-time = "2026-02-19T20:33:44.814Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/dc/cf3b2ec4a419b20d2cd6ba8e1961bc59b7ec9801339628e31551dac23801/botocore-1.42.53-py3-none-any.whl", hash = "sha256:1255db56bc0a284a8caa182c20966277e6c8871b6881cf816d40e993fa5da503", size = 14589472, upload-time = "2026-02-19T20:33:40.377Z" }, -] - -[[package]] -name = "cachetools" -version = "7.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d4/07/56595285564e90777d758ebd383d6b0b971b87729bbe2184a849932a3736/cachetools-7.0.1.tar.gz", hash = "sha256:e31e579d2c5b6e2944177a0397150d312888ddf4e16e12f1016068f0c03b8341", size = 36126, upload-time = "2026-02-10T22:24:05.03Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/9e/5faefbf9db1db466d633735faceda1f94aa99ce506ac450d232536266b32/cachetools-7.0.1-py3-none-any.whl", hash = "sha256:8f086515c254d5664ae2146d14fc7f65c9a4bce75152eb247e5a9c5e6d7b2ecf", size = 13484, upload-time = "2026-02-10T22:24:03.741Z" }, -] - -[[package]] -name = "caio" -version = "0.9.25" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/92/88/b8527e1b00c1811db339a1df8bd1ae49d146fcea9d6a5c40e3a80aaeb38d/caio-0.9.25.tar.gz", hash = "sha256:16498e7f81d1d0f5a4c0ad3f2540e65fe25691376e0a5bd367f558067113ed10", size = 26781, upload-time = "2025-12-26T15:21:36.501Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/25/79c98ebe12df31548ba4eaf44db11b7cad6b3e7b4203718335620939083c/caio-0.9.25-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fb7ff95af4c31ad3f03179149aab61097a71fd85e05f89b4786de0359dffd044", size = 36983, upload-time = "2025-12-26T15:21:36.075Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2b/21288691f16d479945968a0a4f2856818c1c5be56881d51d4dac9b255d26/caio-0.9.25-cp312-cp312-manylinux2010_x86_64.manylinux2014_x86_64.manylinux_2_12_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:97084e4e30dfa598449d874c4d8e0c8d5ea17d2f752ef5e48e150ff9d240cd64", size = 82012, upload-time = "2025-12-26T15:22:20.983Z" }, - { url = "https://files.pythonhosted.org/packages/86/93/1f76c8d1bafe3b0614e06b2195784a3765bbf7b0a067661af9e2dd47fc33/caio-0.9.25-py3-none-any.whl", hash = "sha256:06c0bb02d6b929119b1cfbe1ca403c768b2013a369e2db46bfa2a5761cf82e40", size = 19087, upload-time = "2025-12-26T15:22:00.221Z" }, -] - [[package]] name = "certifi" version = "2026.1.4" @@ -273,15 +165,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, ] -[[package]] -name = "chardet" -version = "5.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" }, -] - [[package]] name = "charset-normalizer" version = "3.4.4" @@ -307,18 +190,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] -[[package]] -name = "clai" -version = "1.62.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic-ai" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/be/9f/3b70f348d3e41c6786c9d719649c2d4cdaf8570fa6518ce72091a903d752/clai-1.62.0.tar.gz", hash = "sha256:a514f38aadca846b54de5884c5f834c5b86bef309b77862f61bb312961a98e56", size = 4292, upload-time = "2026-02-19T05:07:06.905Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/0b/80890641089d0e4a118c196d2300e8b873139fa97aa06721ea691e370066/clai-1.62.0-py3-none-any.whl", hash = "sha256:baa9c7acba12af8ed2569a42fd10ecc6ba913577c386c10ac3f7c715bb74247d", size = 4315, upload-time = "2026-02-19T05:06:56.822Z" }, -] - [[package]] name = "click" version = "8.3.1" @@ -332,22 +203,12 @@ wheels = [ ] [[package]] -name = "cohere" -version = "5.20.6" +name = "cloudpickle" +version = "3.1.2" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "fastavro" }, - { name = "httpx" }, - { name = "pydantic" }, - { name = "pydantic-core" }, - { name = "requests" }, - { name = "tokenizers" }, - { name = "types-requests" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9a/7c/415e9b150843d879427ad4760c2331443d3f4e6860d17a3c3b3841357898/cohere-5.20.6.tar.gz", hash = "sha256:96b53fafcca97d7345646b66caafb79d6d92fa144c44b6d7fd63fbeade2a5155", size = 185110, upload-time = "2026-02-18T15:57:38.19Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/0e/175613bbd3a16465b93e429edd3a44e2f76d67367131206baa951a82925d/cohere-5.20.6-py3-none-any.whl", hash = "sha256:f67050be06c437fb3b330f1326e4fc1974cdcddbb6c25afd57c2bd94897feaa6", size = 323373, upload-time = "2026-02-18T15:57:36.761Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, ] [[package]] @@ -398,21 +259,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, ] -[[package]] -name = "cyclopts" -version = "4.5.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "docstring-parser" }, - { name = "rich" }, - { name = "rich-rst" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3b/d2/f37df900b163f51b4faacdb01bf4895c198906d67c5b2a85c2522de85459/cyclopts-4.5.4.tar.gz", hash = "sha256:eed4d6c76d4391aa796d8fcaabd50e5aad7793261792beb19285f62c5c456c8b", size = 162438, upload-time = "2026-02-20T00:58:46.161Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/0f/119fa63fa93e0a331fbedcb27162d8f88d3ba2f38eba1567e3e44307b857/cyclopts-4.5.4-py3-none-any.whl", hash = "sha256:ad001986ec403ca1dc1ed20375c439d62ac796295ea32b451dfe25d6696bc71a", size = 200225, upload-time = "2026-02-20T00:58:47.275Z" }, -] - [[package]] name = "distro" version = "1.9.0" @@ -422,15 +268,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] -[[package]] -name = "dnspython" -version = "2.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, -] - [[package]] name = "docstring-parser" version = "0.17.0" @@ -440,61 +277,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, ] -[[package]] -name = "docutils" -version = "0.22.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, -] - -[[package]] -name = "email-validator" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "dnspython" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, -] - -[[package]] -name = "eval-type-backport" -version = "0.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fb/a3/cafafb4558fd638aadfe4121dc6cefb8d743368c085acb2f521df0f3d9d7/eval_type_backport-0.3.1.tar.gz", hash = "sha256:57e993f7b5b69d271e37482e62f74e76a0276c82490cf8e4f0dffeb6b332d5ed", size = 9445, upload-time = "2025-12-02T11:51:42.987Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/22/fdc2e30d43ff853720042fa15baa3e6122722be1a7950a98233ebb55cd71/eval_type_backport-0.3.1-py3-none-any.whl", hash = "sha256:279ab641905e9f11129f56a8a78f493518515b83402b860f6f06dd7c011fdfa8", size = 6063, upload-time = "2025-12-02T11:51:41.665Z" }, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, -] - -[[package]] -name = "executing" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, -] - [[package]] name = "fastapi" -version = "0.129.0" +version = "0.131.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -503,63 +288,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/48/47/75f6bea02e797abff1bca968d5997793898032d9923c1935ae2efdece642/fastapi-0.129.0.tar.gz", hash = "sha256:61315cebd2e65df5f97ec298c888f9de30430dd0612d59d6480beafbc10655af", size = 375450, upload-time = "2026-02-12T13:54:52.541Z" } +sdist = { url = "https://files.pythonhosted.org/packages/91/32/158cbf685b7d5a26f87131069da286bf10fc9fbf7fc968d169d48a45d689/fastapi-0.131.0.tar.gz", hash = "sha256:6531155e52bee2899a932c746c9a8250f210e3c3303a5f7b9f8a808bfe0548ff", size = 369612, upload-time = "2026-02-22T16:38:11.252Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/dd/d0ee25348ac58245ee9f90b6f3cbb666bf01f69be7e0911f9851bddbda16/fastapi-0.129.0-py3-none-any.whl", hash = "sha256:b4946880e48f462692b31c083be0432275cbfb6e2274566b1be91479cc1a84ec", size = 102950, upload-time = "2026-02-12T13:54:54.528Z" }, -] - -[[package]] -name = "fastavro" -version = "1.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/8b/fa2d3287fd2267be6261d0177c6809a7fa12c5600ddb33490c8dc29e77b2/fastavro-1.12.1.tar.gz", hash = "sha256:2f285be49e45bc047ab2f6bed040bb349da85db3f3c87880e4b92595ea093b2b", size = 1025661, upload-time = "2025-10-10T15:40:55.41Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/f0/10bd1a3d08667fa0739e2b451fe90e06df575ec8b8ba5d3135c70555c9bd/fastavro-1.12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:509818cb24b98a804fc80be9c5fed90f660310ae3d59382fc811bfa187122167", size = 1009057, upload-time = "2025-10-10T15:41:24.556Z" }, - { url = "https://files.pythonhosted.org/packages/78/ad/0d985bc99e1fa9e74c636658000ba38a5cd7f5ab2708e9c62eaf736ecf1a/fastavro-1.12.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089e155c0c76e0d418d7e79144ce000524dd345eab3bc1e9c5ae69d500f71b14", size = 3391866, upload-time = "2025-10-10T15:41:26.882Z" }, - { url = "https://files.pythonhosted.org/packages/0d/9e/b4951dc84ebc34aac69afcbfbb22ea4a91080422ec2bfd2c06076ff1d419/fastavro-1.12.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44cbff7518901c91a82aab476fcab13d102e4999499df219d481b9e15f61af34", size = 3458005, upload-time = "2025-10-10T15:41:29.017Z" }, - { url = "https://files.pythonhosted.org/packages/af/f8/5a8df450a9f55ca8441f22ea0351d8c77809fc121498b6970daaaf667a21/fastavro-1.12.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a275e48df0b1701bb764b18a8a21900b24cf882263cb03d35ecdba636bbc830b", size = 3295258, upload-time = "2025-10-10T15:41:31.564Z" }, - { url = "https://files.pythonhosted.org/packages/99/b2/40f25299111d737e58b85696e91138a66c25b7334f5357e7ac2b0e8966f8/fastavro-1.12.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2de72d786eb38be6b16d556b27232b1bf1b2797ea09599507938cdb7a9fe3e7c", size = 3430328, upload-time = "2025-10-10T15:41:33.689Z" }, - { url = "https://files.pythonhosted.org/packages/e0/07/85157a7c57c5f8b95507d7829b5946561e5ee656ff80e9dd9a757f53ddaf/fastavro-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:9090f0dee63fe022ee9cc5147483366cc4171c821644c22da020d6b48f576b4f", size = 444140, upload-time = "2025-10-10T15:41:34.902Z" }, -] - -[[package]] -name = "fastmcp" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "authlib" }, - { name = "cyclopts" }, - { name = "exceptiongroup" }, - { name = "httpx" }, - { name = "jsonref" }, - { name = "jsonschema-path" }, - { name = "mcp" }, - { name = "openapi-pydantic" }, - { name = "opentelemetry-api" }, - { name = "packaging" }, - { name = "platformdirs" }, - { name = "py-key-value-aio", extra = ["filetree", "keyring", "memory"] }, - { name = "pydantic", extra = ["email"] }, - { name = "pyperclip" }, - { name = "python-dotenv" }, - { name = "pyyaml" }, - { name = "rich" }, - { name = "uvicorn" }, - { name = "watchfiles" }, - { name = "websockets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4b/be/beb5d3e485983b9dd122f3f74772bcceeb085ca824e11c52c14ba71cf21a/fastmcp-3.0.0.tar.gz", hash = "sha256:f3b0cfa012f6b2b50b877da181431c6f9a551197f466b0bb7de7f39ceae159a1", size = 16093079, upload-time = "2026-02-18T21:25:34.461Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/14/05bebaf3764ea71ce6fa9d3fcf870610bbc8b1e7be2505e870d709375316/fastmcp-3.0.0-py3-none-any.whl", hash = "sha256:561d537cb789f995174c5591f1b54f758ce3f82da3cd951ffe51ce18609569e9", size = 603327, upload-time = "2026-02-18T21:25:36.701Z" }, -] - -[[package]] -name = "filelock" -version = "3.24.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/92/a8e2479937ff39185d20dd6a851c1a63e55849e447a55e798cc2e1f49c65/filelock-3.24.3.tar.gz", hash = "sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa", size = 37935, upload-time = "2026-02-19T00:48:20.543Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/0f/5d0c71a1aefeb08efff26272149e07ab922b64f46c63363756224bd6872e/filelock-3.24.3-py3-none-any.whl", hash = "sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d", size = 24331, upload-time = "2026-02-19T00:48:18.465Z" }, + { url = "https://files.pythonhosted.org/packages/ff/94/b58ec24c321acc2ad1327f69b033cadc005e0f26df9a73828c9e9c7db7ce/fastapi-0.131.0-py3-none-any.whl", hash = "sha256:ed0e53decccf4459de78837ce1b867cd04fa9ce4579497b842579755d20b405a", size = 103854, upload-time = "2026-02-22T16:38:09.814Z" }, ] [[package]] @@ -588,56 +319,58 @@ wheels = [ ] [[package]] -name = "fsspec" -version = "2026.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, -] - -[[package]] -name = "gcloud-aio-auth" -version = "5.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "backoff" }, - { name = "chardet" }, - { name = "cryptography" }, - { name = "pyjwt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c3/44/feaf6e52da4d98140917b0d52d86e4724b7323c13a00442fd0f8158f6370/gcloud_aio_auth-5.4.2.tar.gz", hash = "sha256:184478d081f7cfbb6eff421c22d877d48d17811fa88b269f0c016f5528b3fa31", size = 13998, upload-time = "2025-05-24T00:50:42.321Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/2c/c72ba3433002909804a36aad87ddca2aa5a36b4a25c366178631d8f97385/gcloud_aio_auth-5.4.2-py3-none-any.whl", hash = "sha256:3adfb6ee5cae4226689fd096ce127e99ee5216623577215abb02ef6722574563", size = 16560, upload-time = "2025-05-24T00:50:41.327Z" }, -] - -[[package]] -name = "gcloud-aio-storage" -version = "9.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiofiles" }, - { name = "gcloud-aio-auth" }, - { name = "pyasn1-modules" }, - { name = "rsa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/c4/fb8b9d2289b8b8758c91c2eb0a860d8fcd2e04c5e91e4708eec8a0d720a4/gcloud_aio_storage-9.6.1.tar.gz", hash = "sha256:e3e621ab5e870d29384a0a7935ac68af12dd19caf93f1c91b0bc70f939333bfb", size = 14617, upload-time = "2025-11-07T19:01:19.338Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/d3/9f7ef4954fc705df86e156d632b044001e70d5be4626ad2c4d1903e8fcf5/gcloud_aio_storage-9.6.1-py3-none-any.whl", hash = "sha256:6881f573df586821dfd456f7ded3d94f326a82197adca15d52acc789c3621df3", size = 17412, upload-time = "2025-11-07T19:01:18.218Z" }, -] - -[[package]] -name = "genai-prices" -version = "0.0.54" +name = "google-adk" +version = "1.25.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "aiosqlite" }, + { name = "anyio" }, + { name = "authlib" }, + { name = "click" }, + { name = "fastapi" }, + { name = "google-api-python-client" }, + { name = "google-auth", extra = ["pyopenssl"] }, + { name = "google-cloud-aiplatform", extra = ["agent-engines"] }, + { name = "google-cloud-bigquery" }, + { name = "google-cloud-bigquery-storage" }, + { name = "google-cloud-bigtable" }, + { name = "google-cloud-discoveryengine" }, + { name = "google-cloud-pubsub" }, + { name = "google-cloud-secret-manager" }, + { name = "google-cloud-spanner" }, + { name = "google-cloud-speech" }, + { name = "google-cloud-storage" }, + { name = "google-genai" }, + { name = "graphviz" }, { name = "httpx" }, + { name = "jsonschema" }, + { name = "mcp" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-gcp-logging" }, + { name = "opentelemetry-exporter-gcp-monitoring" }, + { name = "opentelemetry-exporter-gcp-trace" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-resourcedetector-gcp" }, + { name = "opentelemetry-sdk" }, + { name = "pyarrow" }, { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sqlalchemy" }, + { name = "sqlalchemy-spanner" }, + { name = "starlette" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "tzlocal" }, + { name = "uvicorn" }, + { name = "watchdog" }, + { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/20/d64d04c7870db01232f8f8e36dec2072281b1f594b924200aa017778eec2/genai_prices-0.0.54.tar.gz", hash = "sha256:9d985affc19d055be16613b0c5e49d182d4c2164cf1587129f9996dfb9a3cb8d", size = 59588, upload-time = "2026-02-17T20:26:06.849Z" } +sdist = { url = "https://files.pythonhosted.org/packages/45/e2/9c755a7088128cc7e2dbae99d0c512d71fc6504ed128eb489b516b7e47c4/google_adk-1.25.1.tar.gz", hash = "sha256:5f3771d9f704f04c4a6996a3d0c33fc6890641047d3f5a6128cc9b2a83b3326b", size = 2218119, upload-time = "2026-02-18T21:43:19.039Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/71/d2a941b0ca01186912fedb096d8eef7b3e1680c86fdcf8fe3dc84e76d5a9/genai_prices-0.0.54-py3-none-any.whl", hash = "sha256:5b45012b2981b7d4d42c49c8614ee95420fec244c87542542045786b36fc2235", size = 62198, upload-time = "2026-02-17T20:26:05.186Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/e7ed67abe7e928309799b4c4789b2a3b5eba4ac0eb6d4c7912f9e3e9823d/google_adk-1.25.1-py3-none-any.whl", hash = "sha256:62907f54b918a56450fc81669471f5819f41a48548ada3a521ac85728ca29001", size = 2579485, upload-time = "2026-02-18T21:43:20.839Z" }, ] [[package]] @@ -662,6 +395,22 @@ grpc = [ { name = "grpcio-status" }, ] +[[package]] +name = "google-api-python-client" +version = "2.190.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-auth-httplib2" }, + { name = "httplib2" }, + { name = "uritemplate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/4ab3e3516b93bb50ed7814738ea61d49cba3f72f4e331dc9518ae2731e92/google_api_python_client-2.190.0.tar.gz", hash = "sha256:5357f34552e3724d80d2604c8fa146766e0a9d6bb0afada886fafed9feafeef6", size = 14111143, upload-time = "2026-02-12T00:38:03.37Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/ad/223d5f4b0b987669ffeb3eadd7e9f85ece633aa7fd3246f1e2f6238e1e05/google_api_python_client-2.190.0-py3-none-any.whl", hash = "sha256:d9b5266758f96c39b8c21d9bbfeb4e58c14dbfba3c931f7c5a8d7fdcd292dd57", size = 14682070, upload-time = "2026-02-12T00:38:00.974Z" }, +] + [[package]] name = "google-auth" version = "2.48.0" @@ -677,10 +426,26 @@ wheels = [ ] [package.optional-dependencies] +pyopenssl = [ + { name = "pyopenssl" }, +] requests = [ { name = "requests" }, ] +[[package]] +name = "google-auth-httplib2" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "httplib2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/ad/c1f2b1175096a8d04cf202ad5ea6065f108d26be6fc7215876bde4a7981d/google_auth_httplib2-0.3.0.tar.gz", hash = "sha256:177898a0175252480d5ed916aeea183c2df87c1f9c26705d74ae6b951c268b0b", size = 11134, upload-time = "2025-12-15T22:13:51.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/d5/3c97526c8796d3caf5f4b3bed2b05e8a7102326f00a334e7a438237f3b22/google_auth_httplib2-0.3.0-py3-none-any.whl", hash = "sha256:426167e5df066e3f5a0fc7ea18768c08e7296046594ce4c8c409c2457dd1f776", size = 9529, upload-time = "2025-12-15T22:13:51.048Z" }, +] + [[package]] name = "google-cloud-aiplatform" version = "1.138.0" @@ -704,6 +469,50 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/a0/f9651ec6c4d62833b4482612691947f1153affc2a10b0af209420f76d53a/google_cloud_aiplatform-1.138.0-py2.py3-none-any.whl", hash = "sha256:fd92144a0f8e1370df2876ebfc3b221f4bb249a8d7cb4eaecf32e018c56676e9", size = 8207894, upload-time = "2026-02-17T22:09:59.887Z" }, ] +[package.optional-dependencies] +agent-engines = [ + { name = "cloudpickle" }, + { name = "google-cloud-iam" }, + { name = "google-cloud-logging" }, + { name = "google-cloud-trace" }, + { name = "opentelemetry-exporter-gcp-logging" }, + { name = "opentelemetry-exporter-gcp-trace" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-sdk" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "typing-extensions" }, +] + +[[package]] +name = "google-cloud-appengine-logging" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "grpcio" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/38/89317773c64b5a7e9b56b9aecb2e39ac02d8d6d09fb5b276710c6892e690/google_cloud_appengine_logging-1.8.0.tar.gz", hash = "sha256:84b705a69e4109fc2f68dfe36ce3df6a34d5c3d989eee6d0ac1b024dda0ba6f5", size = 18071, upload-time = "2026-01-15T13:14:40.024Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/66/4a9be8afb1d0bf49472478cec20fefe4f4cb3a6e67be2231f097041e7339/google_cloud_appengine_logging-1.8.0-py3-none-any.whl", hash = "sha256:a4ce9ce94a9fd8c89ed07fa0b06fcf9ea3642f9532a1be1a8c7b5f82c0a70ec6", size = 18380, upload-time = "2026-01-09T14:52:58.154Z" }, +] + +[[package]] +name = "google-cloud-audit-log" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/d2/ad96950410f8a05e921a6da2e1a6ba4aeca674bbb5dda8200c3c7296d7ad/google_cloud_audit_log-0.4.0.tar.gz", hash = "sha256:8467d4dcca9f3e6160520c24d71592e49e874838f174762272ec10e7950b6feb", size = 44682, upload-time = "2025-10-17T02:33:44.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/25/532886995f11102ad6de290496de5db227bd3a73827702445928ad32edcb/google_cloud_audit_log-0.4.0-py3-none-any.whl", hash = "sha256:6b88e2349df45f8f4cc0993b687109b1388da1571c502dc1417efa4b66ec55e0", size = 44890, upload-time = "2025-10-17T02:30:55.11Z" }, +] + [[package]] name = "google-cloud-bigquery" version = "3.40.1" @@ -722,6 +531,40 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/f5/081cf5b90adfe524ae0d671781b0d497a75a0f2601d075af518828e22d8f/google_cloud_bigquery-3.40.1-py3-none-any.whl", hash = "sha256:9082a6b8193aba87bed6a2c79cf1152b524c99bb7e7ac33a785e333c09eac868", size = 262018, upload-time = "2026-02-12T18:44:16.913Z" }, ] +[[package]] +name = "google-cloud-bigquery-storage" +version = "2.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "grpcio" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/fa/877e0059349369be38a64586b135c59ceadb87d0386084043d8c440ef929/google_cloud_bigquery_storage-2.36.2.tar.gz", hash = "sha256:ad49d8c09ad6cd82da4efe596fcfcdbc1458bf05b93915e3c5c00f1e700ae128", size = 308672, upload-time = "2026-02-19T16:03:10.544Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/07/62dbe78ef773569be0a1d2c1b845e9214889b404e506126519b4d33ee999/google_cloud_bigquery_storage-2.36.2-py3-none-any.whl", hash = "sha256:823a73db0c4564e8ad3eedcfd5049f3d5aa41775267863b5627211ec36be2dbf", size = 304398, upload-time = "2026-02-19T16:02:55.112Z" }, +] + +[[package]] +name = "google-cloud-bigtable" +version = "2.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "google-crc32c" }, + { name = "grpc-google-iam-v1" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/c9/aceae21411b1a77fb4d3cde6e6f461321ee33c65fb8dc53480d4e47e1a55/google_cloud_bigtable-2.35.0.tar.gz", hash = "sha256:f5699012c5fea4bd4bdf7e80e5e3a812a847eb8f41bf8dc2f43095d6d876b83b", size = 775613, upload-time = "2025-12-17T15:18:14.303Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/69/03eed134d71f6117ffd9efac2d1033bb2fa2522e9e82545a0828061d32f4/google_cloud_bigtable-2.35.0-py3-none-any.whl", hash = "sha256:f355bfce1f239453ec2bb3839b0f4f9937cf34ef06ef29e1ca63d58fd38d0c50", size = 540341, upload-time = "2025-12-17T15:18:12.176Z" }, +] + [[package]] name = "google-cloud-core" version = "2.5.0" @@ -735,6 +578,110 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/20/bfa472e327c8edee00f04beecc80baeddd2ab33ee0e86fd7654da49d45e9/google_cloud_core-2.5.0-py3-none-any.whl", hash = "sha256:67d977b41ae6c7211ee830c7912e41003ea8194bff15ae7d72fd6f51e57acabc", size = 29469, upload-time = "2025-10-29T23:17:38.548Z" }, ] +[[package]] +name = "google-cloud-discoveryengine" +version = "0.13.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/cd/b33bbc4b096d937abee5ebfad3908b2bdc65acd1582191aa33beaa2b70a5/google_cloud_discoveryengine-0.13.12.tar.gz", hash = "sha256:d6b9f8fadd8ad0d2f4438231c5eb7772a317e9f59cafbcbadc19b5d54c609419", size = 3582382, upload-time = "2025-09-22T16:51:14.052Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/70/607f6011648f603d35e60a16c34aee68a0b39510e4268d4859f3268684f9/google_cloud_discoveryengine-0.13.12-py3-none-any.whl", hash = "sha256:295f8c6df3fb26b90fb82c2cd6fbcf4b477661addcb19a94eea16463a5c4e041", size = 3337248, upload-time = "2025-09-22T16:50:57.375Z" }, +] + +[[package]] +name = "google-cloud-firestore" +version = "2.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/9c/ec28ca4ec88fa89e41366316cf92c037feaa2c4200aa5d9da69fe011d2f6/google_cloud_firestore-2.23.0.tar.gz", hash = "sha256:a9cffba7cdc6101111d6d54cde22d521c98f9e7d415e67486b137fa16f06aa03", size = 615238, upload-time = "2026-01-14T23:50:54.574Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/99/2a627c8ea7ae72a686dda8bf2b79747362b425c237d2729eb76bcee55a25/google_cloud_firestore-2.23.0-py3-none-any.whl", hash = "sha256:19f2326cb466b0d52aed9fabbd89758be431f6ce18c422966cfdb8326b424314", size = 411195, upload-time = "2026-01-14T23:50:52.825Z" }, +] + +[[package]] +name = "google-cloud-iam" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "grpc-google-iam-v1" }, + { name = "grpcio" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/0b/037b1e1eb601646d6f49bc06d62094c1d0996b373dcbf70c426c6c51572e/google_cloud_iam-2.21.0.tar.gz", hash = "sha256:fc560527e22b97c6cbfba0797d867cf956c727ba687b586b9aa44d78e92281a3", size = 499038, upload-time = "2026-01-15T13:15:08.243Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/44/02ac4e147ea034a3d641c11b54c9d8d0b80fc1ea6a8b7d6c1588d208d42a/google_cloud_iam-2.21.0-py3-none-any.whl", hash = "sha256:1b4a21302b186a31f3a516ccff303779638308b7c801fb61a2406b6a0c6293c4", size = 458958, upload-time = "2026-01-15T13:13:40.671Z" }, +] + +[[package]] +name = "google-cloud-logging" +version = "3.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "google-cloud-appengine-logging" }, + { name = "google-cloud-audit-log" }, + { name = "google-cloud-core" }, + { name = "grpc-google-iam-v1" }, + { name = "opentelemetry-api" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/47/31ef0261802fe8b37c221392e1d6ff01d30b03dce5e20e77fc7d57ddf8a3/google_cloud_logging-3.13.0.tar.gz", hash = "sha256:3aae0573b1a1a4f59ecdf4571f4e7881b5823bd129fe469561c1c49a7fa8a4c1", size = 290169, upload-time = "2025-12-16T14:11:07.345Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/5a/778dca2e375171af4085554cb3bc643627717a7e4e1539842ced3afd6ec4/google_cloud_logging-3.13.0-py3-none-any.whl", hash = "sha256:f215e1c76ee29239c6cacf02443dffa985663c74bf47c9818854694805c6019f", size = 230518, upload-time = "2025-12-16T14:11:05.894Z" }, +] + +[[package]] +name = "google-cloud-monitoring" +version = "2.29.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "grpcio" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/06/9fc0a34bed4221a68eef3e0373ae054de367dc42c0b689d5d917587ef61b/google_cloud_monitoring-2.29.1.tar.gz", hash = "sha256:86cac55cdd2608561819d19544fb3c129bbb7dcecc445d8de426e34cd6fa8e49", size = 404383, upload-time = "2026-02-05T18:59:13.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/97/7c27aa95eccf8b62b066295a7c4ad04284364b696d3e7d9d47152b255a24/google_cloud_monitoring-2.29.1-py3-none-any.whl", hash = "sha256:944a57031f20da38617d184d5658c1f938e019e8061f27fd944584831a1b9d5a", size = 387922, upload-time = "2026-02-05T18:58:54.964Z" }, +] + +[[package]] +name = "google-cloud-pubsub" +version = "2.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "grpc-google-iam-v1" }, + { name = "grpcio" }, + { name = "grpcio-status" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/ad/dde4c0b014247190a4df0dfa9c90de81b47909e22e2e442198f449a3593f/google_cloud_pubsub-2.35.0.tar.gz", hash = "sha256:2c0d1d7ccda52fa12fb73f34b7eb9899381e2fd931c7d47b10f724cdfac06f95", size = 396812, upload-time = "2026-02-05T22:29:14.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/cb/b783f4e910f0ec4010d279bafce0cd1ed8a10bac41970eb5c6a6416008ab/google_cloud_pubsub-2.35.0-py3-none-any.whl", hash = "sha256:c32e4eb29e532ec784b5abb5d674807715ec07895b7c022b9404871dec09970d", size = 320973, upload-time = "2026-02-05T22:29:13.096Z" }, +] + [[package]] name = "google-cloud-resource-manager" version = "1.16.0" @@ -752,6 +699,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/ff/4b28bcc791d9d7e4ac8fea00fbd90ccb236afda56746a3b4564d2ae45df3/google_cloud_resource_manager-1.16.0-py3-none-any.whl", hash = "sha256:fb9a2ad2b5053c508e1c407ac31abfd1a22e91c32876c1892830724195819a28", size = 400218, upload-time = "2026-01-15T13:02:47.378Z" }, ] +[[package]] +name = "google-cloud-secret-manager" +version = "2.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "grpc-google-iam-v1" }, + { name = "grpcio" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/9c/a6c7144bc96df77376ae3fcc916fb639c40814c2e4bba2051d31dc136cd0/google_cloud_secret_manager-2.26.0.tar.gz", hash = "sha256:0d1d6f76327685a0ed78a4cf50f289e1bfbbe56026ed0affa98663b86d6d50d6", size = 277603, upload-time = "2025-12-18T00:29:31.065Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/30/a58739dd12cec0f7f761ed1efb518aed2250a407d4ed14c5a0eeee7eaaf9/google_cloud_secret_manager-2.26.0-py3-none-any.whl", hash = "sha256:940a5447a6ec9951446fd1a0f22c81a4303fde164cd747aae152c5f5c8e6723e", size = 223623, upload-time = "2025-12-18T00:29:29.311Z" }, +] + +[[package]] +name = "google-cloud-spanner" +version = "3.63.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-cloud-core" }, + { name = "google-cloud-monitoring" }, + { name = "grpc-google-iam-v1" }, + { name = "grpc-interceptor" }, + { name = "mmh3" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-resourcedetector-gcp" }, + { name = "opentelemetry-sdk" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "sqlparse" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/ee/9ae0794d32ec271b2b2326f17d977d29801e5b960e7a0f03d721aeffe824/google_cloud_spanner-3.63.0.tar.gz", hash = "sha256:e2a4fb3bdbad4688645f455d498705d3f935b7c9011f5c94c137b77569b47a62", size = 729522, upload-time = "2026-02-13T07:35:13.593Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/72/e16c4fe5a7058c5526461ade670a4bec0922bc02c2690df27300e9955925/google_cloud_spanner-3.63.0-py3-none-any.whl", hash = "sha256:6ffae0ed589bbbd2d8831495e266198f3d069005cfe65c664448c9a727c88e7b", size = 518799, upload-time = "2026-02-13T07:35:11.993Z" }, +] + +[[package]] +name = "google-cloud-speech" +version = "2.36.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "grpcio" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/b7/b078693abc67af4cbbf60727ebd29d37f786ada8a6146ada2d5918da6a3a/google_cloud_speech-2.36.1.tar.gz", hash = "sha256:30fef3b30c1e1b5f376be3cf82a724c8629994de045935f85e4b7bceae8c2129", size = 401910, upload-time = "2026-02-05T18:59:22.411Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/13/b1437f2716ce56ca13298855929e5fb790c13c3ddee24248a3682ba392a5/google_cloud_speech-2.36.1-py3-none-any.whl", hash = "sha256:a54985b3e7c001a9feae78cec77e67e85d29b3851d00af1f805ffff3f477d8fe", size = 342457, upload-time = "2026-02-05T18:58:59.518Z" }, +] + [[package]] name = "google-cloud-storage" version = "3.9.0" @@ -769,6 +773,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/0b/816a6ae3c9fd096937d2e5f9670558908811d57d59ddf69dd4b83b326fd1/google_cloud_storage-3.9.0-py3-none-any.whl", hash = "sha256:2dce75a9e8b3387078cbbdad44757d410ecdb916101f8ba308abf202b6968066", size = 321324, upload-time = "2026-02-02T13:36:32.271Z" }, ] +[[package]] +name = "google-cloud-trace" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "grpcio" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/34/b1883f4682f1681941100df0e411cb0185013f7c349489ab1330348d7c5c/google_cloud_trace-1.18.0.tar.gz", hash = "sha256:46d42b90273da3bc4850bb0d6b9a205eb826a54561ff1b30ca33cc92174c3f37", size = 103347, upload-time = "2026-01-15T13:04:56.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/15/366fd8b028a50a9018c933270d220a4e53dca8022ce9086618b72978ab90/google_cloud_trace-1.18.0-py3-none-any.whl", hash = "sha256:52c002d8d3da802e031fee62cd49a1baf899932d4f548a150f685af6815b5554", size = 107488, upload-time = "2026-01-15T12:17:21.519Z" }, +] + [[package]] name = "google-crc32c" version = "1.8.0" @@ -834,28 +854,28 @@ grpc = [ ] [[package]] -name = "griffelib" -version = "2.0.0" +name = "graphviz" +version = "0.21" source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/b3/3ac91e9be6b761a4b30d66ff165e54439dcd48b83f4e20d644867215f6ca/graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78", size = 200434, upload-time = "2025-06-15T09:35:05.824Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl", hash = "sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f", size = 142004, upload-time = "2026-02-09T19:09:40.561Z" }, + { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" }, ] [[package]] -name = "groq" -version = "1.0.0" +name = "greenlet" +version = "3.3.2" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3f/12/f4099a141677fcd2ed79dcc1fcec431e60c52e0e90c9c5d935f0ffaf8c0e/groq-1.0.0.tar.gz", hash = "sha256:66cb7bb729e6eb644daac7ce8efe945e99e4eb33657f733ee6f13059ef0c25a9", size = 146068, upload-time = "2025-12-17T23:34:23.115Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2", size = 188267, upload-time = "2026-02-20T20:54:15.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/88/3175759d2ef30406ea721f4d837bfa1ba4339fde3b81ba8c5640a96ed231/groq-1.0.0-py3-none-any.whl", hash = "sha256:6e22bf92ffad988f01d2d4df7729add66b8fd5dbfb2154b5bbf3af245b72c731", size = 138292, upload-time = "2025-12-17T23:34:21.957Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, + { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, + { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, + { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, + { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, + { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/cc802e067d02af8b60b6771cea7d57e21ef5e6659912814babb42b864713/greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f", size = 231081, upload-time = "2026-02-20T20:17:28.121Z" }, + { url = "https://files.pythonhosted.org/packages/58/2e/fe7f36ff1982d6b10a60d5e0740c759259a7d6d2e1dc41da6d96de32fff6/greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643", size = 230331, upload-time = "2026-02-20T20:17:23.34Z" }, ] [[package]] @@ -872,6 +892,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4a/bd/330a1bbdb1afe0b96311249e699b6dc9cfc17916394fd4503ac5aca2514b/grpc_google_iam_v1-0.14.3-py3-none-any.whl", hash = "sha256:7a7f697e017a067206a3dfef44e4c634a34d3dee135fe7d7a4613fe3e59217e6", size = 32690, upload-time = "2025-10-15T21:14:51.72Z" }, ] +[[package]] +name = "grpc-interceptor" +version = "0.15.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "grpcio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/28/57449d5567adf4c1d3e216aaca545913fbc21a915f2da6790d6734aac76e/grpc-interceptor-0.15.4.tar.gz", hash = "sha256:1f45c0bcb58b6f332f37c637632247c9b02bc6af0fdceb7ba7ce8d2ebbfb0926", size = 19322, upload-time = "2023-11-16T02:05:42.459Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/ac/8d53f230a7443401ce81791ec50a3b0e54924bf615ad287654fa4a2f5cdc/grpc_interceptor-0.15.4-py3-none-any.whl", hash = "sha256:0035f33228693ed3767ee49d937bac424318db173fef4d2d0170b3215f254d9d", size = 20848, upload-time = "2023-11-16T02:05:40.913Z" }, +] + [[package]] name = "grpcio" version = "1.78.1" @@ -916,21 +948,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] -[[package]] -name = "hf-xet" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/6e/0f11bacf08a67f7fb5ee09740f2ca54163863b07b70d579356e9222ce5d8/hf_xet-1.2.0.tar.gz", hash = "sha256:a8c27070ca547293b6890c4bf389f713f80e8c478631432962bb7f4bc0bd7d7f", size = 506020, upload-time = "2025-10-24T19:04:32.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/2d/22338486473df5923a9ab7107d375dbef9173c338ebef5098ef593d2b560/hf_xet-1.2.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:46740d4ac024a7ca9b22bebf77460ff43332868b661186a8e46c227fdae01848", size = 2866099, upload-time = "2025-10-24T19:04:15.366Z" }, - { url = "https://files.pythonhosted.org/packages/7f/8c/c5becfa53234299bc2210ba314eaaae36c2875e0045809b82e40a9544f0c/hf_xet-1.2.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:27df617a076420d8845bea087f59303da8be17ed7ec0cd7ee3b9b9f579dff0e4", size = 2722178, upload-time = "2025-10-24T19:04:13.695Z" }, - { url = "https://files.pythonhosted.org/packages/9a/92/cf3ab0b652b082e66876d08da57fcc6fa2f0e6c70dfbbafbd470bb73eb47/hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3651fd5bfe0281951b988c0facbe726aa5e347b103a675f49a3fa8144c7968fd", size = 3320214, upload-time = "2025-10-24T19:04:03.596Z" }, - { url = "https://files.pythonhosted.org/packages/46/92/3f7ec4a1b6a65bf45b059b6d4a5d38988f63e193056de2f420137e3c3244/hf_xet-1.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d06fa97c8562fb3ee7a378dd9b51e343bc5bc8190254202c9771029152f5e08c", size = 3229054, upload-time = "2025-10-24T19:04:01.949Z" }, - { url = "https://files.pythonhosted.org/packages/0b/dd/7ac658d54b9fb7999a0ccb07ad863b413cbaf5cf172f48ebcd9497ec7263/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4c1428c9ae73ec0939410ec73023c4f842927f39db09b063b9482dac5a3bb737", size = 3413812, upload-time = "2025-10-24T19:04:24.585Z" }, - { url = "https://files.pythonhosted.org/packages/92/68/89ac4e5b12a9ff6286a12174c8538a5930e2ed662091dd2572bbe0a18c8a/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a55558084c16b09b5ed32ab9ed38421e2d87cf3f1f89815764d1177081b99865", size = 3508920, upload-time = "2025-10-24T19:04:26.927Z" }, - { url = "https://files.pythonhosted.org/packages/cb/44/870d44b30e1dcfb6a65932e3e1506c103a8a5aea9103c337e7a53180322c/hf_xet-1.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:e6584a52253f72c9f52f9e549d5895ca7a471608495c4ecaa6cc73dba2b24d69", size = 2905735, upload-time = "2025-10-24T19:04:35.928Z" }, -] - [[package]] name = "httpcore" version = "1.0.9" @@ -944,6 +961,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] +[[package]] +name = "httplib2" +version = "0.31.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/1f/e86365613582c027dda5ddb64e1010e57a3d53e99ab8a72093fa13d565ec/httplib2-0.31.2.tar.gz", hash = "sha256:385e0869d7397484f4eab426197a4c020b606edd43372492337c0b4010ae5d24", size = 250800, upload-time = "2026-01-23T11:04:44.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/90/fd509079dfcab01102c0fdd87f3a9506894bc70afcf9e9785ef6b2b3aff6/httplib2-0.31.2-py3-none-any.whl", hash = "sha256:dbf0c2fa3862acf3c55c078ea9c0bc4481d7dc5117cae71be9514912cf9f8349", size = 91099, upload-time = "2026-01-23T11:04:42.78Z" }, +] + [[package]] name = "httpx" version = "0.28.1" @@ -968,27 +997,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, ] -[[package]] -name = "huggingface-hub" -version = "1.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "fsspec" }, - { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, - { name = "httpx" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "shellingham" }, - { name = "tqdm" }, - { name = "typer-slim" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c4/fc/eb9bc06130e8bbda6a616e1b80a7aa127681c448d6b49806f61db2670b61/huggingface_hub-1.4.1.tar.gz", hash = "sha256:b41131ec35e631e7383ab26d6146b8d8972abc8b6309b963b306fbcca87f5ed5", size = 642156, upload-time = "2026-02-06T09:20:03.013Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/ae/2f6d96b4e6c5478d87d606a1934b5d436c4a2bce6bb7c6fdece891c128e3/huggingface_hub-1.4.1-py3-none-any.whl", hash = "sha256:9931d075fb7a79af5abc487106414ec5fba2c0ae86104c0c62fd6cae38873d18", size = 553326, upload-time = "2026-02-06T09:20:00.728Z" }, -] - [[package]] name = "idna" version = "3.11" @@ -1019,100 +1027,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] -[[package]] -name = "invoke" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/de/bd/b461d3424a24c80490313fd77feeb666ca4f6a28c7e72713e3d9095719b4/invoke-2.2.1.tar.gz", hash = "sha256:515bf49b4a48932b79b024590348da22f39c4942dff991ad1fb8b8baea1be707", size = 304762, upload-time = "2025-10-11T00:36:35.172Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/4b/b99e37f88336009971405cbb7630610322ed6fbfa31e1d7ab3fbf3049a2d/invoke-2.2.1-py3-none-any.whl", hash = "sha256:2413bc441b376e5cd3f55bb5d364f973ad8bdd7bf87e53c79de3c11bf3feecc8", size = 160287, upload-time = "2025-10-11T00:36:33.703Z" }, -] - -[[package]] -name = "jaraco-classes" -version = "3.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "more-itertools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, -] - -[[package]] -name = "jaraco-context" -version = "6.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/9c/a788f5bb29c61e456b8ee52ce76dbdd32fd72cd73dd67bc95f42c7a8d13c/jaraco_context-6.1.0.tar.gz", hash = "sha256:129a341b0a85a7db7879e22acd66902fda67882db771754574338898b2d5d86f", size = 15850, upload-time = "2026-01-13T02:53:53.847Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/48/aa685dbf1024c7bd82bede569e3a85f82c32fd3d79ba5fea578f0159571a/jaraco_context-6.1.0-py3-none-any.whl", hash = "sha256:a43b5ed85815223d0d3cfdb6d7ca0d2bc8946f28f30b6f3216bda070f68badda", size = 7065, upload-time = "2026-01-13T02:53:53.031Z" }, -] - -[[package]] -name = "jaraco-functools" -version = "4.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "more-itertools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, -] - -[[package]] -name = "jeepney" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, -] - -[[package]] -name = "jiter" -version = "0.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847, upload-time = "2026-02-02T12:37:56.441Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958, upload-time = "2026-02-02T12:35:57.165Z" }, - { url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597, upload-time = "2026-02-02T12:35:58.591Z" }, - { url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821, upload-time = "2026-02-02T12:36:00.093Z" }, - { url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163, upload-time = "2026-02-02T12:36:01.937Z" }, - { url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709, upload-time = "2026-02-02T12:36:03.41Z" }, - { url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480, upload-time = "2026-02-02T12:36:04.791Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735, upload-time = "2026-02-02T12:36:06.994Z" }, - { url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814, upload-time = "2026-02-02T12:36:08.368Z" }, - { url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990, upload-time = "2026-02-02T12:36:09.993Z" }, - { url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021, upload-time = "2026-02-02T12:36:11.376Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024, upload-time = "2026-02-02T12:36:12.682Z" }, - { url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424, upload-time = "2026-02-02T12:36:13.93Z" }, - { url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818, upload-time = "2026-02-02T12:36:15.308Z" }, - { url = "https://files.pythonhosted.org/packages/80/60/e50fa45dd7e2eae049f0ce964663849e897300433921198aef94b6ffa23a/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a", size = 305169, upload-time = "2026-02-02T12:37:50.376Z" }, - { url = "https://files.pythonhosted.org/packages/d2/73/a009f41c5eed71c49bec53036c4b33555afcdee70682a18c6f66e396c039/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f", size = 303808, upload-time = "2026-02-02T12:37:52.092Z" }, - { url = "https://files.pythonhosted.org/packages/c4/10/528b439290763bff3d939268085d03382471b442f212dca4ff5f12802d43/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59", size = 337384, upload-time = "2026-02-02T12:37:53.582Z" }, - { url = "https://files.pythonhosted.org/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" }, -] - -[[package]] -name = "jmespath" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, -] - -[[package]] -name = "jsonref" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814, upload-time = "2023-01-16T16:10:04.455Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425, upload-time = "2023-01-16T16:10:02.255Z" }, -] - [[package]] name = "jsonschema" version = "4.26.0" @@ -1128,20 +1042,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, ] -[[package]] -name = "jsonschema-path" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pathable" }, - { name = "pyyaml" }, - { name = "referencing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a8/8d/4b2e648cf643d19e1f76260d9cb002d242e38b4298d6da110bd3c3d8d0d2/jsonschema_path-0.4.1.tar.gz", hash = "sha256:ffca3bd37f66364ae3afeaa2804d6078a9ab3b9359ade4dd9923aabbbd475e71", size = 13450, upload-time = "2026-02-20T10:09:41.611Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/5a/5a735fd9c889fd7ee214525620ead725861f9f4ddd27097408b63e596b06/jsonschema_path-0.4.1-py3-none-any.whl", hash = "sha256:727d8714158c41327908677e6119f9db9d5e0f486d4cc79ca4b4016eee2f33e8", size = 16745, upload-time = "2026-02-20T10:09:40.03Z" }, -] - [[package]] name = "jsonschema-specifications" version = "2025.9.1" @@ -1155,64 +1055,34 @@ wheels = [ ] [[package]] -name = "keyring" -version = "25.7.0" +name = "mako" +version = "1.3.10" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jaraco-classes" }, - { name = "jaraco-context" }, - { name = "jaraco-functools" }, - { name = "jeepney", marker = "sys_platform == 'linux'" }, - { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, - { name = "secretstorage", marker = "sys_platform == 'linux'" }, + { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, ] [[package]] -name = "logfire" -version = "4.25.0" +name = "markupsafe" +version = "3.0.3" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "executing" }, - { name = "opentelemetry-exporter-otlp-proto-http" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-sdk" }, - { name = "protobuf" }, - { name = "rich" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d8/43/374fc0e6ebe95209414cf743cc693f4ff2ad391fd0712445ed1f63245395/logfire-4.25.0.tar.gz", hash = "sha256:f9a6bf6d40fd3e2c2a86a364617246cadecbde620b4ecccb17c499140f1ebc13", size = 1049745, upload-time = "2026-02-19T15:27:28Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/cc/a3eb3a5fff27a6bfe2f626624c7c781322151f3228d4ea98c31003dc2d4c/logfire-4.25.0-py3-none-any.whl", hash = "sha256:1865b832e08c58a3fb0d21b24460ee9c6cbeff12db6038c508fb966699ce81c2", size = 298186, upload-time = "2026-02-19T15:27:23.324Z" }, -] - -[package.optional-dependencies] -httpx = [ - { name = "opentelemetry-instrumentation-httpx" }, -] - -[[package]] -name = "logfire-api" -version = "4.25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/5c/026cec30d85394aec8f5f12d70edbe2d706837bc9a411bd71a542cedae50/logfire_api-4.25.0.tar.gz", hash = "sha256:7562d5adfe3987291039dddb21947c86cb9d832d068c87d9aa23db86ef07095b", size = 75853, upload-time = "2026-02-19T15:27:29.518Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/39/83414c0fadb4f11f90e6b80b631aa79f62a605664f0c4693e2ebc7ee73f3/logfire_api-4.25.0-py3-none-any.whl", hash = "sha256:0d607eb09ef5426e26f376ff277a8d401bc5b7b4178ea66db404e13c368494cf", size = 120473, upload-time = "2026-02-19T15:27:25.832Z" }, -] - -[[package]] -name = "markdown-it-py" -version = "4.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, ] [[package]] @@ -1241,42 +1111,27 @@ wheels = [ ] [[package]] -name = "mdurl" -version = "0.1.2" +name = "mmh3" +version = "5.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/af/f28c2c2f51f31abb4725f9a64bc7863d5f491f6539bd26aee2a1d21a649e/mmh3-5.2.0.tar.gz", hash = "sha256:1efc8fec8478e9243a78bb993422cf79f8ff85cb4cf6b79647480a31e0d950a8", size = 33582, upload-time = "2025-07-29T07:43:48.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, -] - -[[package]] -name = "mistralai" -version = "1.12.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "eval-type-backport" }, - { name = "httpx" }, - { name = "invoke" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-exporter-otlp-proto-http" }, - { name = "opentelemetry-sdk" }, - { name = "pydantic" }, - { name = "python-dateutil" }, - { name = "pyyaml" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/46/ad/3d3b17a768f641ab428bbe7c4a75283db029737778d4f56cb4a9145ba54f/mistralai-1.12.3.tar.gz", hash = "sha256:d59a788e82c16fd7d340f9f2e722ed0897fe15ccce797278b836d65fa671ef6e", size = 242961, upload-time = "2026-02-17T15:41:29Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/3c/d17250578195b90b0a7f2500c4d587996c9b79470dbcfe7fb9d24feb9d1b/mistralai-1.12.3-py3-none-any.whl", hash = "sha256:e164e070011dd7759ad5d969c44359939d7d73f7fec787667317b7e81ffc5a8b", size = 502976, upload-time = "2026-02-17T15:41:30.648Z" }, -] - -[[package]] -name = "more-itertools" -version = "10.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, + { url = "https://files.pythonhosted.org/packages/bf/6a/d5aa7edb5c08e0bd24286c7d08341a0446f9a2fbbb97d96a8a6dd81935ee/mmh3-5.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:384eda9361a7bf83a85e09447e1feafe081034af9dd428893701b959230d84be", size = 56141, upload-time = "2025-07-29T07:42:13.456Z" }, + { url = "https://files.pythonhosted.org/packages/08/49/131d0fae6447bc4a7299ebdb1a6fb9d08c9f8dcf97d75ea93e8152ddf7ab/mmh3-5.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c9da0d568569cc87315cb063486d761e38458b8ad513fedd3dc9263e1b81bcd", size = 40681, upload-time = "2025-07-29T07:42:14.306Z" }, + { url = "https://files.pythonhosted.org/packages/8f/6f/9221445a6bcc962b7f5ff3ba18ad55bba624bacdc7aa3fc0a518db7da8ec/mmh3-5.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86d1be5d63232e6eb93c50881aea55ff06eb86d8e08f9b5417c8c9b10db9db96", size = 40062, upload-time = "2025-07-29T07:42:15.08Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d4/6bb2d0fef81401e0bb4c297d1eb568b767de4ce6fc00890bc14d7b51ecc4/mmh3-5.2.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bf7bee43e17e81671c447e9c83499f53d99bf440bc6d9dc26a841e21acfbe094", size = 97333, upload-time = "2025-07-29T07:42:16.436Z" }, + { url = "https://files.pythonhosted.org/packages/44/e0/ccf0daff8134efbb4fbc10a945ab53302e358c4b016ada9bf97a6bdd50c1/mmh3-5.2.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7aa18cdb58983ee660c9c400b46272e14fa253c675ed963d3812487f8ca42037", size = 103310, upload-time = "2025-07-29T07:42:17.796Z" }, + { url = "https://files.pythonhosted.org/packages/02/63/1965cb08a46533faca0e420e06aff8bbaf9690a6f0ac6ae6e5b2e4544687/mmh3-5.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9d032488fcec32d22be6542d1a836f00247f40f320844dbb361393b5b22773", size = 106178, upload-time = "2025-07-29T07:42:19.281Z" }, + { url = "https://files.pythonhosted.org/packages/c2/41/c883ad8e2c234013f27f92061200afc11554ea55edd1bcf5e1accd803a85/mmh3-5.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1861fb6b1d0453ed7293200139c0a9011eeb1376632e048e3766945b13313c5", size = 113035, upload-time = "2025-07-29T07:42:20.356Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/1ccade8b1fa625d634a18bab7bf08a87457e09d5ec8cf83ca07cbea9d400/mmh3-5.2.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:99bb6a4d809aa4e528ddfe2c85dd5239b78b9dd14be62cca0329db78505e7b50", size = 120784, upload-time = "2025-07-29T07:42:21.377Z" }, + { url = "https://files.pythonhosted.org/packages/77/1c/919d9171fcbdcdab242e06394464ccf546f7d0f3b31e0d1e3a630398782e/mmh3-5.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1f8d8b627799f4e2fcc7c034fed8f5f24dc7724ff52f69838a3d6d15f1ad4765", size = 99137, upload-time = "2025-07-29T07:42:22.344Z" }, + { url = "https://files.pythonhosted.org/packages/66/8a/1eebef5bd6633d36281d9fc83cf2e9ba1ba0e1a77dff92aacab83001cee4/mmh3-5.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b5995088dd7023d2d9f310a0c67de5a2b2e06a570ecfd00f9ff4ab94a67cde43", size = 98664, upload-time = "2025-07-29T07:42:23.269Z" }, + { url = "https://files.pythonhosted.org/packages/13/41/a5d981563e2ee682b21fb65e29cc0f517a6734a02b581359edd67f9d0360/mmh3-5.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1a5f4d2e59d6bba8ef01b013c472741835ad961e7c28f50c82b27c57748744a4", size = 106459, upload-time = "2025-07-29T07:42:24.238Z" }, + { url = "https://files.pythonhosted.org/packages/24/31/342494cd6ab792d81e083680875a2c50fa0c5df475ebf0b67784f13e4647/mmh3-5.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fd6e6c3d90660d085f7e73710eab6f5545d4854b81b0135a3526e797009dbda3", size = 110038, upload-time = "2025-07-29T07:42:25.629Z" }, + { url = "https://files.pythonhosted.org/packages/28/44/efda282170a46bb4f19c3e2b90536513b1d821c414c28469a227ca5a1789/mmh3-5.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c4a2f3d83879e3de2eb8cbf562e71563a8ed15ee9b9c2e77ca5d9f73072ac15c", size = 97545, upload-time = "2025-07-29T07:42:27.04Z" }, + { url = "https://files.pythonhosted.org/packages/68/8f/534ae319c6e05d714f437e7206f78c17e66daca88164dff70286b0e8ea0c/mmh3-5.2.0-cp312-cp312-win32.whl", hash = "sha256:2421b9d665a0b1ad724ec7332fb5a98d075f50bc51a6ff854f3a1882bd650d49", size = 40805, upload-time = "2025-07-29T07:42:28.032Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f6/f6abdcfefcedab3c964868048cfe472764ed358c2bf6819a70dd4ed4ed3a/mmh3-5.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d80005b7634a3a2220f81fbeb94775ebd12794623bb2e1451701ea732b4aa3", size = 41597, upload-time = "2025-07-29T07:42:28.894Z" }, + { url = "https://files.pythonhosted.org/packages/15/fd/f7420e8cbce45c259c770cac5718badf907b302d3a99ec587ba5ce030237/mmh3-5.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:3d6bfd9662a20c054bc216f861fa330c2dac7c81e7fb8307b5e32ab5b9b4d2e0", size = 39350, upload-time = "2025-07-29T07:42:29.794Z" }, ] [[package]] @@ -1306,77 +1161,79 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] -[[package]] -name = "nexus-rpc" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/50/95d7bc91f900da5e22662c82d9bf0f72a4b01f2a552708bf2f43807707a1/nexus_rpc-1.2.0.tar.gz", hash = "sha256:b4ddaffa4d3996aaeadf49b80dfcdfbca48fe4cb616defaf3b3c5c2c8fc61890", size = 74142, upload-time = "2025-11-17T19:17:06.798Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/04/eaac430d0e6bf21265ae989427d37e94be5e41dc216879f1fbb6c5339942/nexus_rpc-1.2.0-py3-none-any.whl", hash = "sha256:977876f3af811ad1a09b2961d3d1ac9233bda43ff0febbb0c9906483b9d9f8a3", size = 28166, upload-time = "2025-11-17T19:17:05.64Z" }, -] - -[[package]] -name = "openai" -version = "2.21.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, - { name = "jiter" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "tqdm" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/92/e5/3d197a0947a166649f566706d7a4c8f7fe38f1fa7b24c9bcffe4c7591d44/openai-2.21.0.tar.gz", hash = "sha256:81b48ce4b8bbb2cc3af02047ceb19561f7b1dc0d4e52d1de7f02abfd15aa59b7", size = 644374, upload-time = "2026-02-14T00:12:01.577Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/56/0a89092a453bb2c676d66abee44f863e742b2110d4dbb1dbcca3f7e5fc33/openai-2.21.0-py3-none-any.whl", hash = "sha256:0bc1c775e5b1536c294eded39ee08f8407656537ccc71b1004104fe1602e267c", size = 1103065, upload-time = "2026-02-14T00:11:59.603Z" }, -] - -[[package]] -name = "openapi-pydantic" -version = "0.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/02/2e/58d83848dd1a79cb92ed8e63f6ba901ca282c5f09d04af9423ec26c56fd7/openapi_pydantic-0.5.1.tar.gz", hash = "sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d", size = 60892, upload-time = "2025-01-08T19:29:27.083Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381, upload-time = "2025-01-08T19:29:25.275Z" }, -] - [[package]] name = "opentelemetry-api" -version = "1.39.1" +version = "1.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/d8/0f354c375628e048bd0570645b310797299754730079853095bf000fba69/opentelemetry_api-1.38.0.tar.gz", hash = "sha256:f4c193b5e8acb0912b06ac5b16321908dd0843d75049c091487322284a3eea12", size = 65242, upload-time = "2025-10-16T08:35:50.25Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" }, + { url = "https://files.pythonhosted.org/packages/ae/a2/d86e01c28300bd41bab8f18afd613676e2bd63515417b77636fc1add426f/opentelemetry_api-1.38.0-py3-none-any.whl", hash = "sha256:2891b0197f47124454ab9f0cf58f3be33faca394457ac3e09daba13ff50aa582", size = 65947, upload-time = "2025-10-16T08:35:30.23Z" }, +] + +[[package]] +name = "opentelemetry-exporter-gcp-logging" +version = "1.11.0a0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-cloud-logging" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-resourcedetector-gcp" }, + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/2d/6aa7063b009768d8f9415b36a29ae9b3eb1e2c5eff70f58ca15e104c245f/opentelemetry_exporter_gcp_logging-1.11.0a0.tar.gz", hash = "sha256:58496f11b930c84570060ffbd4343cd0b597ea13c7bc5c879df01163dd552f14", size = 22400, upload-time = "2025-11-04T19:32:13.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/b7/2d3df53fa39bfd52f88c78a60367d45a7b1adbf8a756cce62d6ac149d49a/opentelemetry_exporter_gcp_logging-1.11.0a0-py3-none-any.whl", hash = "sha256:f8357c552947cb9c0101c4575a7702b8d3268e28bdeefdd1405cf838e128c6ef", size = 14168, upload-time = "2025-11-04T19:32:07.073Z" }, +] + +[[package]] +name = "opentelemetry-exporter-gcp-monitoring" +version = "1.11.0a0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-cloud-monitoring" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-resourcedetector-gcp" }, + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/48/d1c7d2380bb1754d1eb6a011a2e0de08c6868cb6c0f34bcda0444fa0d614/opentelemetry_exporter_gcp_monitoring-1.11.0a0.tar.gz", hash = "sha256:386276eddbbd978a6f30fafd3397975beeb02a1302bdad554185242a8e2c343c", size = 20828, upload-time = "2025-11-04T19:32:14.522Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/8c/03a6e73e270a9c890dbd6cc1c47c83d86b8a8a974a9168d92e043c6277cc/opentelemetry_exporter_gcp_monitoring-1.11.0a0-py3-none-any.whl", hash = "sha256:b6740cba61b2f9555274829fe87a58447b64d0378f1067a4faebb4f5b364ca22", size = 13611, upload-time = "2025-11-04T19:32:08.212Z" }, +] + +[[package]] +name = "opentelemetry-exporter-gcp-trace" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-cloud-trace" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-resourcedetector-gcp" }, + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/9c/4c3b26e5494f8b53c7873732a2317df905abe2b8ab33e9edfcbd5a8ff79b/opentelemetry_exporter_gcp_trace-1.11.0.tar.gz", hash = "sha256:c947ab4ab53e16517ade23d6fe71fe88cf7ca3f57a42c9f0e4162d2b929fecb6", size = 18770, upload-time = "2025-11-04T19:32:15.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/4a/876703e8c5845198d95cd4006c8d1b2e3b129a9e288558e33133360f8d5d/opentelemetry_exporter_gcp_trace-1.11.0-py3-none-any.whl", hash = "sha256:b3dcb314e1a9985e9185cb7720b693eb393886fde98ae4c095ffc0893de6cefa", size = 14016, upload-time = "2025-11-04T19:32:09.009Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.39.1" +version = "1.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/9d/22d241b66f7bbde88a3bfa6847a351d2c46b84de23e71222c6aae25c7050/opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz", hash = "sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464", size = 20409, upload-time = "2025-12-11T13:32:40.885Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/83/dd4660f2956ff88ed071e9e0e36e830df14b8c5dc06722dbde1841accbe8/opentelemetry_exporter_otlp_proto_common-1.38.0.tar.gz", hash = "sha256:e333278afab4695aa8114eeb7bf4e44e65c6607d54968271a249c180b2cb605c", size = 20431, upload-time = "2025-10-16T08:35:53.285Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/02/ffc3e143d89a27ac21fd557365b98bd0653b98de8a101151d5805b5d4c33/opentelemetry_exporter_otlp_proto_common-1.39.1-py3-none-any.whl", hash = "sha256:08f8a5862d64cc3435105686d0216c1365dc5701f86844a8cd56597d0c764fde", size = 18366, upload-time = "2025-12-11T13:32:20.2Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9e/55a41c9601191e8cd8eb626b54ee6827b9c9d4a46d736f32abc80d8039fc/opentelemetry_exporter_otlp_proto_common-1.38.0-py3-none-any.whl", hash = "sha256:03cb76ab213300fe4f4c62b7d8f17d97fcfd21b89f0b5ce38ea156327ddda74a", size = 18359, upload-time = "2025-10-16T08:35:34.099Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.39.1" +version = "1.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, @@ -1387,115 +1244,72 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/80/04/2a08fa9c0214ae38880df01e8bfae12b067ec0793446578575e5080d6545/opentelemetry_exporter_otlp_proto_http-1.39.1.tar.gz", hash = "sha256:31bdab9745c709ce90a49a0624c2bd445d31a28ba34275951a6a362d16a0b9cb", size = 17288, upload-time = "2025-12-11T13:32:42.029Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/0a/debcdfb029fbd1ccd1563f7c287b89a6f7bef3b2902ade56797bfd020854/opentelemetry_exporter_otlp_proto_http-1.38.0.tar.gz", hash = "sha256:f16bd44baf15cbe07633c5112ffc68229d0edbeac7b37610be0b2def4e21e90b", size = 17282, upload-time = "2025-10-16T08:35:54.422Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/f1/b27d3e2e003cd9a3592c43d099d2ed8d0a947c15281bf8463a256db0b46c/opentelemetry_exporter_otlp_proto_http-1.39.1-py3-none-any.whl", hash = "sha256:d9f5207183dd752a412c4cd564ca8875ececba13be6e9c6c370ffb752fd59985", size = 19641, upload-time = "2025-12-11T13:32:22.248Z" }, -] - -[[package]] -name = "opentelemetry-instrumentation" -version = "0.60b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "packaging" }, - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/41/0f/7e6b713ac117c1f5e4e3300748af699b9902a2e5e34c9cf443dde25a01fa/opentelemetry_instrumentation-0.60b1.tar.gz", hash = "sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a", size = 31706, upload-time = "2025-12-11T13:36:42.515Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/d2/6788e83c5c86a2690101681aeef27eeb2a6bf22df52d3f263a22cee20915/opentelemetry_instrumentation-0.60b1-py3-none-any.whl", hash = "sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d", size = 33096, upload-time = "2025-12-11T13:35:33.067Z" }, -] - -[[package]] -name = "opentelemetry-instrumentation-httpx" -version = "0.60b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "opentelemetry-util-http" }, - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/86/08/11208bcfcab4fc2023252c3f322aa397fd9ad948355fea60f5fc98648603/opentelemetry_instrumentation_httpx-0.60b1.tar.gz", hash = "sha256:a506ebaf28c60112cbe70ad4f0338f8603f148938cb7b6794ce1051cd2b270ae", size = 20611, upload-time = "2025-12-11T13:37:01.661Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/59/b98e84eebf745ffc75397eaad4763795bff8a30cbf2373a50ed4e70646c5/opentelemetry_instrumentation_httpx-0.60b1-py3-none-any.whl", hash = "sha256:f37636dd742ad2af83d896ba69601ed28da51fa4e25d1ab62fde89ce413e275b", size = 15701, upload-time = "2025-12-11T13:36:04.56Z" }, + { url = "https://files.pythonhosted.org/packages/e5/77/154004c99fb9f291f74aa0822a2f5bbf565a72d8126b3a1b63ed8e5f83c7/opentelemetry_exporter_otlp_proto_http-1.38.0-py3-none-any.whl", hash = "sha256:84b937305edfc563f08ec69b9cb2298be8188371217e867c1854d77198d0825b", size = 19579, upload-time = "2025-10-16T08:35:36.269Z" }, ] [[package]] name = "opentelemetry-proto" -version = "1.39.1" +version = "1.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/1d/f25d76d8260c156c40c97c9ed4511ec0f9ce353f8108ca6e7561f82a06b2/opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8", size = 46152, upload-time = "2025-12-11T13:32:48.681Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/14/f0c4f0f6371b9cb7f9fa9ee8918bfd59ac7040c7791f1e6da32a1839780d/opentelemetry_proto-1.38.0.tar.gz", hash = "sha256:88b161e89d9d372ce723da289b7da74c3a8354a8e5359992be813942969ed468", size = 46152, upload-time = "2025-10-16T08:36:01.612Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/95/b40c96a7b5203005a0b03d8ce8cd212ff23f1793d5ba289c87a097571b18/opentelemetry_proto-1.39.1-py3-none-any.whl", hash = "sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007", size = 72535, upload-time = "2025-12-11T13:32:33.866Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6a/82b68b14efca5150b2632f3692d627afa76b77378c4999f2648979409528/opentelemetry_proto-1.38.0-py3-none-any.whl", hash = "sha256:b6ebe54d3217c42e45462e2a1ae28c3e2bf2ec5a5645236a490f55f45f1a0a18", size = 72535, upload-time = "2025-10-16T08:35:45.749Z" }, +] + +[[package]] +name = "opentelemetry-resourcedetector-gcp" +version = "1.11.0a0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/5d/2b3240d914b87b6dd9cd5ca2ef1ccaf1d0626b897d4c06877e22c8c10fcf/opentelemetry_resourcedetector_gcp-1.11.0a0.tar.gz", hash = "sha256:915a1d6fd15daca9eedd3fc52b0f705375054f2ef140e2e7a6b4cca95a47cdb1", size = 18796, upload-time = "2025-11-04T19:32:16.59Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/6c/1e13fe142a7ca3dc6489167203a1209d32430cca12775e1df9c9a41c54b2/opentelemetry_resourcedetector_gcp-1.11.0a0-py3-none-any.whl", hash = "sha256:5d65a2a039b1d40c6f41421dbb08d5f441368275ac6de6e76a8fccd1f6acb67e", size = 18798, upload-time = "2025-11-04T19:32:10.915Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.39.1" +version = "1.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/cb/f0eee1445161faf4c9af3ba7b848cc22a50a3d3e2515051ad8628c35ff80/opentelemetry_sdk-1.38.0.tar.gz", hash = "sha256:93df5d4d871ed09cb4272305be4d996236eedb232253e3ab864c8620f051cebe", size = 171942, upload-time = "2025-10-16T08:36:02.257Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c", size = 132565, upload-time = "2025-12-11T13:32:35.069Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/e93777a95d7d9c40d270a371392b6d6f1ff170c2a3cb32d6176741b5b723/opentelemetry_sdk-1.38.0-py3-none-any.whl", hash = "sha256:1c66af6564ecc1553d72d811a01df063ff097cdc82ce188da9951f93b8d10f6b", size = 132349, upload-time = "2025-10-16T08:35:46.995Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.60b1" +version = "0.59b0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/bc/8b9ad3802cd8ac6583a4eb7de7e5d7db004e89cb7efe7008f9c8a537ee75/opentelemetry_semantic_conventions-0.59b0.tar.gz", hash = "sha256:7a6db3f30d70202d5bf9fa4b69bc866ca6a30437287de6c510fb594878aed6b0", size = 129861, upload-time = "2025-10-16T08:36:03.346Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb", size = 219982, upload-time = "2025-12-11T13:32:36.955Z" }, -] - -[[package]] -name = "opentelemetry-util-http" -version = "0.60b1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/50/fc/c47bb04a1d8a941a4061307e1eddfa331ed4d0ab13d8a9781e6db256940a/opentelemetry_util_http-0.60b1.tar.gz", hash = "sha256:0d97152ca8c8a41ced7172d29d3622a219317f74ae6bb3027cfbdcf22c3cc0d6", size = 11053, upload-time = "2025-12-11T13:37:25.115Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/5c/d3f1733665f7cd582ef0842fb1d2ed0bc1fba10875160593342d22bba375/opentelemetry_util_http-0.60b1-py3-none-any.whl", hash = "sha256:66381ba28550c91bee14dcba8979ace443444af1ed609226634596b4b0faf199", size = 8947, upload-time = "2025-12-11T13:36:37.151Z" }, + { url = "https://files.pythonhosted.org/packages/24/7d/c88d7b15ba8fe5c6b8f93be50fc11795e9fc05386c44afaf6b76fe191f9b/opentelemetry_semantic_conventions-0.59b0-py3-none-any.whl", hash = "sha256:35d3b8833ef97d614136e253c1da9342b4c3c083bbaf29ce31d572a1c3825eed", size = 207954, upload-time = "2025-10-16T08:35:48.054Z" }, ] [[package]] name = "packaging" -version = "25.0" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, -] - -[[package]] -name = "pathable" -version = "0.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/55/b748445cb4ea6b125626f15379be7c96d1035d4fa3e8fee362fa92298abf/pathable-0.5.0.tar.gz", hash = "sha256:d81938348a1cacb525e7c75166270644782c0fb9c8cecc16be033e71427e0ef1", size = 16655, upload-time = "2026-02-20T08:47:00.748Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/96/5a770e5c461462575474468e5af931cff9de036e7c2b4fea23c1c58d2cbe/pathable-0.5.0-py3-none-any.whl", hash = "sha256:646e3d09491a6351a0c82632a09c02cdf70a252e73196b36d8a15ba0a114f0a6", size = 16867, upload-time = "2026-02-20T08:46:59.536Z" }, -] - -[[package]] -name = "platformdirs" -version = "4.9.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] @@ -1507,18 +1321,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] -[[package]] -name = "prompt-toolkit" -version = "3.0.52" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wcwidth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, -] - [[package]] name = "propcache" version = "0.4.1" @@ -1571,28 +1373,18 @@ wheels = [ ] [[package]] -name = "py-key-value-aio" -version = "0.4.4" +name = "pyarrow" +version = "23.0.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "beartype" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/04/3c/0397c072a38d4bc580994b42e0c90c5f44f679303489e4376289534735e5/py_key_value_aio-0.4.4.tar.gz", hash = "sha256:e3012e6243ed7cc09bb05457bd4d03b1ba5c2b1ca8700096b3927db79ffbbe55", size = 92300, upload-time = "2026-02-16T21:21:43.245Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/22/134986a4cc224d593c1afde5494d18ff629393d74cc2eddb176669f234a4/pyarrow-23.0.1.tar.gz", hash = "sha256:b8c5873e33440b2bc2f4a79d2b47017a89c5a24116c055625e6f2ee50523f019", size = 1167336, upload-time = "2026-02-16T10:14:12.39Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/69/f1b537ee70b7def42d63124a539ed3026a11a3ffc3086947a1ca6e861868/py_key_value_aio-0.4.4-py3-none-any.whl", hash = "sha256:18e17564ecae61b987f909fc2cd41ee2012c84b4b1dcb8c055cf8b4bc1bf3f5d", size = 152291, upload-time = "2026-02-16T21:21:44.241Z" }, -] - -[package.optional-dependencies] -filetree = [ - { name = "aiofile" }, - { name = "anyio" }, -] -keyring = [ - { name = "keyring" }, -] -memory = [ - { name = "cachetools" }, + { url = "https://files.pythonhosted.org/packages/9a/4b/4166bb5abbfe6f750fc60ad337c43ecf61340fa52ab386da6e8dbf9e63c4/pyarrow-23.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f4b0dbfa124c0bb161f8b5ebb40f1a680b70279aa0c9901d44a2b5a20806039f", size = 34214575, upload-time = "2026-02-16T10:09:56.225Z" }, + { url = "https://files.pythonhosted.org/packages/e1/da/3f941e3734ac8088ea588b53e860baeddac8323ea40ce22e3d0baa865cc9/pyarrow-23.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:7707d2b6673f7de054e2e83d59f9e805939038eebe1763fe811ee8fa5c0cd1a7", size = 35832540, upload-time = "2026-02-16T10:10:03.428Z" }, + { url = "https://files.pythonhosted.org/packages/88/7c/3d841c366620e906d54430817531b877ba646310296df42ef697308c2705/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:86ff03fb9f1a320266e0de855dee4b17da6794c595d207f89bba40d16b5c78b9", size = 44470940, upload-time = "2026-02-16T10:10:10.704Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a5/da83046273d990f256cb79796a190bbf7ec999269705ddc609403f8c6b06/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:813d99f31275919c383aab17f0f455a04f5a429c261cc411b1e9a8f5e4aaaa05", size = 47586063, upload-time = "2026-02-16T10:10:17.95Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/b7d2ebcff47a514f47f9da1e74b7949138c58cfeb108cdd4ee62f43f0cf3/pyarrow-23.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bf5842f960cddd2ef757d486041d57c96483efc295a8c4a0e20e704cbbf39c67", size = 48173045, upload-time = "2026-02-16T10:10:25.363Z" }, + { url = "https://files.pythonhosted.org/packages/43/b2/b40961262213beaba6acfc88698eb773dfce32ecdf34d19291db94c2bd73/pyarrow-23.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564baf97c858ecc03ec01a41062e8f4698abc3e6e2acd79c01c2e97880a19730", size = 50621741, upload-time = "2026-02-16T10:10:33.477Z" }, + { url = "https://files.pythonhosted.org/packages/f6/70/1fdda42d65b28b078e93d75d371b2185a61da89dda4def8ba6ba41ebdeb4/pyarrow-23.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:07deae7783782ac7250989a7b2ecde9b3c343a643f82e8a4df03d93b633006f0", size = 27620678, upload-time = "2026-02-16T10:10:39.31Z" }, ] [[package]] @@ -1640,106 +1432,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, ] -[package.optional-dependencies] -email = [ - { name = "email-validator" }, -] - -[[package]] -name = "pydantic-ai" -version = "1.62.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic-ai-slim", extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "fastmcp", "google", "groq", "huggingface", "logfire", "mcp", "mistral", "openai", "retries", "temporal", "ui", "vertexai", "xai"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/20/97/e3158fa976a29e9580ba1c59601590424bbb81179c359fd29de0dc23aa09/pydantic_ai-1.62.0.tar.gz", hash = "sha256:d6ae517e365ea3ea162ca8ae643f319e105b71b0b6218b83dcad1d1eb2e38c9b", size = 12130, upload-time = "2026-02-19T05:07:07.853Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/7a/053aebfab576603e95fcfce1139de4a87e12bd5a2ef1ba00007a931c3ff0/pydantic_ai-1.62.0-py3-none-any.whl", hash = "sha256:1eb88f745ae045e63da41ad68966e8876c964d0f023fbf5d6a3f5d243370bd04", size = 7227, upload-time = "2026-02-19T05:06:58.341Z" }, -] - -[[package]] -name = "pydantic-ai-slim" -version = "1.62.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "genai-prices" }, - { name = "griffelib" }, - { name = "httpx" }, - { name = "opentelemetry-api" }, - { name = "pydantic" }, - { name = "pydantic-graph" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cc/8d/6350a49f2e4b636efbcfc233221420ab576e4ba4edba38254cb84ae4a1e6/pydantic_ai_slim-1.62.0.tar.gz", hash = "sha256:00d84f659107bbbd88823a3d3dbe7348385935a9870b9d7d4ba799256f6b6983", size = 422452, upload-time = "2026-02-19T05:07:10.292Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/67/21e9b3b0944568662e3790c936226bd48a9f27c6b5f27b5916f5857bc4d8/pydantic_ai_slim-1.62.0-py3-none-any.whl", hash = "sha256:5210073fadd46f65859a67da67845093c487f025fa430ed027151f22ec684ab2", size = 549296, upload-time = "2026-02-19T05:07:01.624Z" }, -] - -[package.optional-dependencies] -ag-ui = [ - { name = "ag-ui-protocol" }, - { name = "starlette" }, -] -anthropic = [ - { name = "anthropic" }, -] -bedrock = [ - { name = "boto3" }, -] -cli = [ - { name = "argcomplete" }, - { name = "prompt-toolkit" }, - { name = "pyperclip" }, - { name = "rich" }, -] -cohere = [ - { name = "cohere", marker = "sys_platform != 'emscripten'" }, -] -evals = [ - { name = "pydantic-evals" }, -] -fastmcp = [ - { name = "fastmcp" }, -] -google = [ - { name = "google-genai" }, -] -groq = [ - { name = "groq" }, -] -huggingface = [ - { name = "huggingface-hub" }, -] -logfire = [ - { name = "logfire", extra = ["httpx"] }, -] -mcp = [ - { name = "mcp" }, -] -mistral = [ - { name = "mistralai" }, -] -openai = [ - { name = "openai" }, - { name = "tiktoken" }, -] -retries = [ - { name = "tenacity" }, -] -temporal = [ - { name = "temporalio" }, -] -ui = [ - { name = "starlette" }, -] -vertexai = [ - { name = "google-auth" }, - { name = "requests" }, -] -xai = [ - { name = "xai-sdk" }, -] - [[package]] name = "pydantic-core" version = "2.41.5" @@ -1769,38 +1461,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, ] -[[package]] -name = "pydantic-evals" -version = "1.62.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "logfire-api" }, - { name = "pydantic" }, - { name = "pydantic-ai-slim" }, - { name = "pyyaml" }, - { name = "rich" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/23/90/080f6722412263395d1d6d066ee90fa8bc2722ce097844220c2d9c946877/pydantic_evals-1.62.0.tar.gz", hash = "sha256:198c4bee936718a4acf6f504056b113e60b34eb49021df8889a394e14c803693", size = 56434, upload-time = "2026-02-19T05:07:11.793Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/b9/dc8dba744ec02b16c6fd1abe3fd8ef1b00fd05c72feef5069851b811952f/pydantic_evals-1.62.0-py3-none-any.whl", hash = "sha256:0ca7e10037ed90393c54b6cff41370d6d4bac63f8c878715599c58863c303db1", size = 67341, upload-time = "2026-02-19T05:07:03.83Z" }, -] - -[[package]] -name = "pydantic-graph" -version = "1.62.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "httpx" }, - { name = "logfire-api" }, - { name = "pydantic" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3b/b6/0b084c847ecd99624f4fbc5c8ecd3f67a2388a282a32612b2a68c3b3595f/pydantic_graph-1.62.0.tar.gz", hash = "sha256:efe56bee3a8ca35b11a3be6a5f7352419fe182ef1e1323a3267ee12dec95f3c7", size = 58529, upload-time = "2026-02-19T05:07:12.947Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/12/1a9cbcd59fd070ba72b0fe544caa6ca97758518643523ec2bf1162084e0d/pydantic_graph-1.62.0-py3-none-any.whl", hash = "sha256:abe0e7b356b4d3202b069ec020d8dd1f647f55e9a0e85cd272dab48250bde87d", size = 72350, upload-time = "2026-02-19T05:07:05.305Z" }, -] - [[package]] name = "pydantic-settings" version = "2.13.1" @@ -1844,12 +1504,25 @@ crypto = [ ] [[package]] -name = "pyperclip" -version = "1.11.0" +name = "pyopenssl" +version = "25.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/52/d87eba7cb129b81563019d1679026e7a112ef76855d6159d24754dbd2a51/pyperclip-1.11.0.tar.gz", hash = "sha256:244035963e4428530d9e3a6101a1ef97209c6825edab1567beac148ccc1db1b6", size = 12185, upload-time = "2025-09-26T14:40:37.245Z" } +dependencies = [ + { name = "cryptography" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/be/97b83a464498a79103036bc74d1038df4a7ef0e402cfaf4d5e113fb14759/pyopenssl-25.3.0.tar.gz", hash = "sha256:c981cb0a3fd84e8602d7afc209522773b94c1c2446a3c710a75b06fe1beae329", size = 184073, upload-time = "2025-09-17T00:32:21.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, + { url = "https://files.pythonhosted.org/packages/d1/81/ef2b1dfd1862567d573a4fdbc9f969067621764fbb74338496840a1d2977/pyopenssl-25.3.0-py3-none-any.whl", hash = "sha256:1fda6fc034d5e3d179d39e59c1895c9faeaf40a79de5fc4cbbfbe0d36f4a77b6", size = 57268, upload-time = "2025-09-17T00:32:19.474Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] [[package]] @@ -1868,6 +1541,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, +] + +[[package]] +name = "pytest-sugar" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "termcolor" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/4e/60fed105549297ba1a700e1ea7b828044842ea27d72c898990510b79b0e2/pytest-sugar-1.1.1.tar.gz", hash = "sha256:73b8b65163ebf10f9f671efab9eed3d56f20d2ca68bda83fa64740a92c08f65d", size = 16533, upload-time = "2025-08-23T12:19:35.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/d5/81d38a91c1fdafb6711f053f5a9b92ff788013b19821257c2c38c1e132df/pytest_sugar-1.1.1-py3-none-any.whl", hash = "sha256:2f8319b907548d5b9d03a171515c1d43d2e38e32bd8182a1781eb20b43344cc8", size = 11440, upload-time = "2025-08-23T12:19:34.894Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1908,15 +1607,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, ] -[[package]] -name = "pywin32-ctypes" -version = "0.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, -] - [[package]] name = "pyyaml" version = "6.0.3" @@ -1935,53 +1625,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, ] -[[package]] -name = "rag-eval" -version = "0.1.0" -source = { editable = "." } -dependencies = [ - { name = "aiohttp" }, - { name = "fastapi" }, - { name = "gcloud-aio-auth" }, - { name = "gcloud-aio-storage" }, - { name = "google-cloud-aiplatform" }, - { name = "google-cloud-storage" }, - { name = "pydantic-ai-slim", extra = ["google"] }, - { name = "pydantic-settings", extra = ["yaml"] }, - { name = "structlog" }, - { name = "uvicorn" }, -] - -[package.dev-dependencies] -dev = [ - { name = "clai" }, - { name = "pytest" }, - { name = "ruff" }, - { name = "ty" }, -] - -[package.metadata] -requires-dist = [ - { name = "aiohttp", specifier = ">=3.13.3" }, - { name = "fastapi", specifier = ">=0.129.0" }, - { name = "gcloud-aio-auth", specifier = ">=5.4.2" }, - { name = "gcloud-aio-storage", specifier = ">=9.6.1" }, - { name = "google-cloud-aiplatform", specifier = ">=1.138.0" }, - { name = "google-cloud-storage", specifier = ">=3.9.0" }, - { name = "pydantic-ai-slim", extras = ["google"], specifier = ">=1.62.0" }, - { name = "pydantic-settings", extras = ["yaml"], specifier = ">=2.10.1" }, - { name = "structlog", specifier = ">=25.5.0" }, - { name = "uvicorn", specifier = ">=0.41.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "clai", specifier = ">=1.62.0" }, - { name = "pytest", specifier = ">=8.4.1" }, - { name = "ruff", specifier = ">=0.12.10" }, - { name = "ty", specifier = ">=0.0.1a19" }, -] - [[package]] name = "referencing" version = "0.37.0" @@ -1996,30 +1639,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, ] -[[package]] -name = "regex" -version = "2026.2.19" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ff/c0/d8079d4f6342e4cec5c3e7d7415b5cd3e633d5f4124f7a4626908dbe84c7/regex-2026.2.19.tar.gz", hash = "sha256:6fb8cb09b10e38f3ae17cc6dc04a1df77762bd0351b6ba9041438e7cc85ec310", size = 414973, upload-time = "2026-02-19T19:03:47.899Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/73/13b39c7c9356f333e564ab4790b6cb0df125b8e64e8d6474e73da49b1955/regex-2026.2.19-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c1665138776e4ac1aa75146669236f7a8a696433ec4e525abf092ca9189247cc", size = 489541, upload-time = "2026-02-19T19:00:52.728Z" }, - { url = "https://files.pythonhosted.org/packages/15/77/fcc7bd9a67000d07fbcc11ed226077287a40d5c84544e62171d29d3ef59c/regex-2026.2.19-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d792b84709021945597e05656aac059526df4e0c9ef60a0eaebb306f8fafcaa8", size = 291414, upload-time = "2026-02-19T19:00:54.51Z" }, - { url = "https://files.pythonhosted.org/packages/f9/87/3997fc72dc59233426ef2e18dfdd105bb123812fff740ee9cc348f1a3243/regex-2026.2.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db970bcce4d63b37b3f9eb8c893f0db980bbf1d404a1d8d2b17aa8189de92c53", size = 289140, upload-time = "2026-02-19T19:00:56.841Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d0/b7dd3883ed1cff8ee0c0c9462d828aaf12be63bf5dc55453cbf423523b13/regex-2026.2.19-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03d706fbe7dfec503c8c3cb76f9352b3e3b53b623672aa49f18a251a6c71b8e6", size = 798767, upload-time = "2026-02-19T19:00:59.014Z" }, - { url = "https://files.pythonhosted.org/packages/4a/7e/8e2d09103832891b2b735a2515abf377db21144c6dd5ede1fb03c619bf09/regex-2026.2.19-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8dbff048c042beef60aa1848961384572c5afb9e8b290b0f1203a5c42cf5af65", size = 864436, upload-time = "2026-02-19T19:01:00.772Z" }, - { url = "https://files.pythonhosted.org/packages/8a/2e/afea8d23a6db1f67f45e3a0da3057104ce32e154f57dd0c8997274d45fcd/regex-2026.2.19-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccaaf9b907ea6b4223d5cbf5fa5dff5f33dc66f4907a25b967b8a81339a6e332", size = 912391, upload-time = "2026-02-19T19:01:02.865Z" }, - { url = "https://files.pythonhosted.org/packages/59/3c/ea5a4687adaba5e125b9bd6190153d0037325a0ba3757cc1537cc2c8dd90/regex-2026.2.19-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:75472631eee7898e16a8a20998d15106cb31cfde21cdf96ab40b432a7082af06", size = 803702, upload-time = "2026-02-19T19:01:05.298Z" }, - { url = "https://files.pythonhosted.org/packages/dc/c5/624a0705e8473a26488ec1a3a4e0b8763ecfc682a185c302dfec71daea35/regex-2026.2.19-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d89f85a5ccc0cec125c24be75610d433d65295827ebaf0d884cbe56df82d4774", size = 775980, upload-time = "2026-02-19T19:01:07.047Z" }, - { url = "https://files.pythonhosted.org/packages/4d/4b/ed776642533232b5599b7c1f9d817fe11faf597e8a92b7a44b841daaae76/regex-2026.2.19-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0d9f81806abdca3234c3dd582b8a97492e93de3602c8772013cb4affa12d1668", size = 788122, upload-time = "2026-02-19T19:01:08.744Z" }, - { url = "https://files.pythonhosted.org/packages/8c/58/e93e093921d13b9784b4f69896b6e2a9e09580a265c59d9eb95e87d288f2/regex-2026.2.19-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9dadc10d1c2bbb1326e572a226d2ec56474ab8aab26fdb8cf19419b372c349a9", size = 858910, upload-time = "2026-02-19T19:01:10.488Z" }, - { url = "https://files.pythonhosted.org/packages/85/77/ff1d25a0c56cd546e0455cbc93235beb33474899690e6a361fa6b52d265b/regex-2026.2.19-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6bc25d7e15f80c9dc7853cbb490b91c1ec7310808b09d56bd278fe03d776f4f6", size = 764153, upload-time = "2026-02-19T19:01:12.156Z" }, - { url = "https://files.pythonhosted.org/packages/cd/ef/8ec58df26d52d04443b1dc56f9be4b409f43ed5ae6c0248a287f52311fc4/regex-2026.2.19-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:965d59792f5037d9138da6fed50ba943162160443b43d4895b182551805aff9c", size = 850348, upload-time = "2026-02-19T19:01:14.147Z" }, - { url = "https://files.pythonhosted.org/packages/f5/b3/c42fd5ed91639ce5a4225b9df909180fc95586db071f2bf7c68d2ccbfbe6/regex-2026.2.19-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:38d88c6ed4a09ed61403dbdf515d969ccba34669af3961ceb7311ecd0cef504a", size = 789977, upload-time = "2026-02-19T19:01:15.838Z" }, - { url = "https://files.pythonhosted.org/packages/b6/22/bc3b58ebddbfd6ca5633e71fd41829ee931963aad1ebeec55aad0c23044e/regex-2026.2.19-cp312-cp312-win32.whl", hash = "sha256:5df947cabab4b643d4791af5e28aecf6bf62e6160e525651a12eba3d03755e6b", size = 266381, upload-time = "2026-02-19T19:01:17.952Z" }, - { url = "https://files.pythonhosted.org/packages/fc/4a/6ff550b63e67603ee60e69dc6bd2d5694e85046a558f663b2434bdaeb285/regex-2026.2.19-cp312-cp312-win_amd64.whl", hash = "sha256:4146dc576ea99634ae9c15587d0c43273b4023a10702998edf0fa68ccb60237a", size = 277274, upload-time = "2026-02-19T19:01:19.826Z" }, - { url = "https://files.pythonhosted.org/packages/cc/29/9ec48b679b1e87e7bc8517dff45351eab38f74fbbda1fbcf0e9e6d4e8174/regex-2026.2.19-cp312-cp312-win_arm64.whl", hash = "sha256:cdc0a80f679353bd68450d2a42996090c30b2e15ca90ded6156c31f1a3b63f3b", size = 270509, upload-time = "2026-02-19T19:01:22.075Z" }, -] - [[package]] name = "requests" version = "2.32.5" @@ -2035,32 +1654,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] -[[package]] -name = "rich" -version = "14.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, -] - -[[package]] -name = "rich-rst" -version = "1.3.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docutils" }, - { name = "rich" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bc/6d/a506aaa4a9eaa945ed8ab2b7347859f53593864289853c5d6d62b77246e0/rich_rst-1.3.2.tar.gz", hash = "sha256:a1196fdddf1e364b02ec68a05e8ff8f6914fee10fbca2e6b6735f166bb0da8d4", size = 14936, upload-time = "2025-10-14T16:49:45.332Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl", hash = "sha256:a99b4907cbe118cf9d18b0b44de272efa61f15117c61e39ebdc431baf5df722a", size = 12567, upload-time = "2025-10-14T16:49:42.953Z" }, -] - [[package]] name = "rpds-py" version = "0.30.0" @@ -2121,40 +1714,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6d/78/097c0798b1dab9f8affe73da9642bb4500e098cb27fd8dc9724816ac747b/ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e", size = 10941649, upload-time = "2026-02-19T22:32:18.108Z" }, ] -[[package]] -name = "s3transfer" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "botocore" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827, upload-time = "2025-12-01T02:30:59.114Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" }, -] - -[[package]] -name = "secretstorage" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography", marker = "sys_platform != 'win32'" }, - { name = "jeepney", marker = "sys_platform != 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, -] - -[[package]] -name = "shellingham" -version = "1.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, -] - [[package]] name = "six" version = "1.17.0" @@ -2173,6 +1732,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "sqlalchemy" +version = "2.0.46" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/aa/9ce0f3e7a9829ead5c8ce549392f33a12c4555a6c0609bb27d882e9c7ddf/sqlalchemy-2.0.46.tar.gz", hash = "sha256:cf36851ee7219c170bb0793dbc3da3e80c582e04a5437bc601bfe8c85c9216d7", size = 9865393, upload-time = "2026-01-21T18:03:45.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/35/d16bfa235c8b7caba3730bba43e20b1e376d2224f407c178fbf59559f23e/sqlalchemy-2.0.46-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a9a72b0da8387f15d5810f1facca8f879de9b85af8c645138cba61ea147968c", size = 2153405, upload-time = "2026-01-21T19:05:54.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/6c/3192e24486749862f495ddc6584ed730c0c994a67550ec395d872a2ad650/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2347c3f0efc4de367ba00218e0ae5c4ba2306e47216ef80d6e31761ac97cb0b9", size = 3334702, upload-time = "2026-01-21T18:46:45.384Z" }, + { url = "https://files.pythonhosted.org/packages/ea/a2/b9f33c8d68a3747d972a0bb758c6b63691f8fb8a49014bc3379ba15d4274/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9094c8b3197db12aa6f05c51c05daaad0a92b8c9af5388569847b03b1007fb1b", size = 3347664, upload-time = "2026-01-21T18:40:09.979Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d2/3e59e2a91eaec9db7e8dc6b37b91489b5caeb054f670f32c95bcba98940f/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37fee2164cf21417478b6a906adc1a91d69ae9aba8f9533e67ce882f4bb1de53", size = 3277372, upload-time = "2026-01-21T18:46:47.168Z" }, + { url = "https://files.pythonhosted.org/packages/dd/dd/67bc2e368b524e2192c3927b423798deda72c003e73a1e94c21e74b20a85/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b1e14b2f6965a685c7128bd315e27387205429c2e339eeec55cb75ca4ab0ea2e", size = 3312425, upload-time = "2026-01-21T18:40:11.548Z" }, + { url = "https://files.pythonhosted.org/packages/43/82/0ecd68e172bfe62247e96cb47867c2d68752566811a4e8c9d8f6e7c38a65/sqlalchemy-2.0.46-cp312-cp312-win32.whl", hash = "sha256:412f26bb4ba942d52016edc8d12fb15d91d3cd46b0047ba46e424213ad407bcb", size = 2113155, upload-time = "2026-01-21T18:42:49.748Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2a/2821a45742073fc0331dc132552b30de68ba9563230853437cac54b2b53e/sqlalchemy-2.0.46-cp312-cp312-win_amd64.whl", hash = "sha256:ea3cd46b6713a10216323cda3333514944e510aa691c945334713fca6b5279ff", size = 2140078, upload-time = "2026-01-21T18:42:51.197Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a1/9c4efa03300926601c19c18582531b45aededfb961ab3c3585f1e24f120b/sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e", size = 1937882, upload-time = "2026-01-21T18:22:10.456Z" }, +] + +[[package]] +name = "sqlalchemy-spanner" +version = "1.17.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic" }, + { name = "google-cloud-spanner" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/29/21698bb83e542f32e3581886671f39d94b1f7e8b190c24a8bfa994e62fd6/sqlalchemy_spanner-1.17.2.tar.gz", hash = "sha256:56ce4da7168a27442d80ffd71c29ed639b5056d7e69b1e69bb9c1e10190b67c4", size = 82745, upload-time = "2025-12-15T23:30:08.622Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/87/05be45a086116cea32cfa00fa0059d31b5345360dba7902ee640a1db793b/sqlalchemy_spanner-1.17.2-py3-none-any.whl", hash = "sha256:18713d4d78e0bf048eda0f7a5c80733e08a7b678b34349496415f37652efb12f", size = 31917, upload-time = "2025-12-15T23:30:07.356Z" }, +] + +[[package]] +name = "sqlparse" +version = "0.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" }, +] + [[package]] name = "sse-starlette" version = "3.2.0" @@ -2199,34 +1801,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, ] -[[package]] -name = "structlog" -version = "25.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ef/52/9ba0f43b686e7f3ddfeaa78ac3af750292662284b3661e91ad5494f21dbc/structlog-25.5.0.tar.gz", hash = "sha256:098522a3bebed9153d4570c6d0288abf80a031dfdb2048d59a49e9dc2190fc98", size = 1460830, upload-time = "2025-10-27T08:28:23.028Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/45/a132b9074aa18e799b891b91ad72133c98d8042c70f6240e4c5f9dabee2f/structlog-25.5.0-py3-none-any.whl", hash = "sha256:a8453e9b9e636ec59bd9e79bbd4a72f025981b3ba0f5837aebf48f02f37a7f9f", size = 72510, upload-time = "2025-10-27T08:28:21.535Z" }, -] - -[[package]] -name = "temporalio" -version = "1.20.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nexus-rpc" }, - { name = "protobuf" }, - { name = "types-protobuf" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/21/db/7d5118d28b0918888e1ec98f56f659fdb006351e06d95f30f4274962a76f/temporalio-1.20.0.tar.gz", hash = "sha256:5a6a85b7d298b7359bffa30025f7deac83c74ac095a4c6952fbf06c249a2a67c", size = 1850498, upload-time = "2025-11-25T21:25:20.225Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/1b/e69052aa6003eafe595529485d9c62d1382dd5e671108f1bddf544fb6032/temporalio-1.20.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:fba70314b4068f8b1994bddfa0e2ad742483f0ae714d2ef52e63013ccfd7042e", size = 12061638, upload-time = "2025-11-25T21:24:57.918Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3b/3e8c67ed7f23bedfa231c6ac29a7a9c12b89881da7694732270f3ecd6b0c/temporalio-1.20.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffc5bb6cabc6ae67f0bfba44de6a9c121603134ae18784a2ff3a7f230ad99080", size = 11562603, upload-time = "2025-11-25T21:25:01.721Z" }, - { url = "https://files.pythonhosted.org/packages/6d/be/ed0cc11702210522a79e09703267ebeca06eb45832b873a58de3ca76b9d0/temporalio-1.20.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1e80c1e4cdf88fa8277177f563edc91466fe4dc13c0322f26e55c76b6a219e6", size = 11824016, upload-time = "2025-11-25T21:25:06.771Z" }, - { url = "https://files.pythonhosted.org/packages/9d/97/09c5cafabc80139d97338a2bdd8ec22e08817dfd2949ab3e5b73565006eb/temporalio-1.20.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba92d909188930860c9d89ca6d7a753bc5a67e4e9eac6cea351477c967355eed", size = 12189521, upload-time = "2025-11-25T21:25:12.091Z" }, - { url = "https://files.pythonhosted.org/packages/11/23/5689c014a76aff3b744b3ee0d80815f63b1362637814f5fbb105244df09b/temporalio-1.20.0-cp310-abi3-win_amd64.whl", hash = "sha256:eacfd571b653e0a0f4aa6593f4d06fc628797898f0900d400e833a1f40cad03a", size = 12745027, upload-time = "2025-11-25T21:25:16.827Z" }, -] - [[package]] name = "tenacity" version = "9.1.4" @@ -2237,132 +1811,36 @@ wheels = [ ] [[package]] -name = "tiktoken" -version = "0.12.0" +name = "termcolor" +version = "3.3.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "regex" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806, upload-time = "2025-10-06T20:22:45.419Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/79/cf31d7a93a8fdc6aa0fbb665be84426a8c5a557d9240b6239e9e11e35fc5/termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5", size = 14434, upload-time = "2025-12-29T12:55:21.882Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/85/be65d39d6b647c79800fd9d29241d081d4eeb06271f383bb87200d74cf76/tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8", size = 1050728, upload-time = "2025-10-06T20:21:52.756Z" }, - { url = "https://files.pythonhosted.org/packages/4a/42/6573e9129bc55c9bf7300b3a35bef2c6b9117018acca0dc760ac2d93dffe/tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b", size = 994049, upload-time = "2025-10-06T20:21:53.782Z" }, - { url = "https://files.pythonhosted.org/packages/66/c5/ed88504d2f4a5fd6856990b230b56d85a777feab84e6129af0822f5d0f70/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37", size = 1129008, upload-time = "2025-10-06T20:21:54.832Z" }, - { url = "https://files.pythonhosted.org/packages/f4/90/3dae6cc5436137ebd38944d396b5849e167896fc2073da643a49f372dc4f/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad", size = 1152665, upload-time = "2025-10-06T20:21:56.129Z" }, - { url = "https://files.pythonhosted.org/packages/a3/fe/26df24ce53ffde419a42f5f53d755b995c9318908288c17ec3f3448313a3/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5", size = 1194230, upload-time = "2025-10-06T20:21:57.546Z" }, - { url = "https://files.pythonhosted.org/packages/20/cc/b064cae1a0e9fac84b0d2c46b89f4e57051a5f41324e385d10225a984c24/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3", size = 1254688, upload-time = "2025-10-06T20:21:58.619Z" }, - { url = "https://files.pythonhosted.org/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd", size = 878694, upload-time = "2025-10-06T20:21:59.876Z" }, -] - -[[package]] -name = "tokenizers" -version = "0.22.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, - { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, - { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, - { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, - { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, - { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, - { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, - { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, - { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, - { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, - { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, - { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, - { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, - { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, -] - -[[package]] -name = "tqdm" -version = "4.67.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, + { url = "https://files.pythonhosted.org/packages/33/d1/8bb87d21e9aeb323cc03034f5eaf2c8f69841e40e4853c2627edf8111ed3/termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5", size = 7734, upload-time = "2025-12-29T12:55:20.718Z" }, ] [[package]] name = "ty" -version = "0.0.17" +version = "0.0.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/c3/41ae6346443eedb65b96761abfab890a48ce2aa5a8a27af69c5c5d99064d/ty-0.0.17.tar.gz", hash = "sha256:847ed6c120913e280bf9b54d8eaa7a1049708acb8824ad234e71498e8ad09f97", size = 5167209, upload-time = "2026-02-13T13:26:36.835Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/15/9682700d8d60fdca7afa4febc83a2354b29cdcd56e66e19c92b521db3b39/ty-0.0.18.tar.gz", hash = "sha256:04ab7c3db5dcbcdac6ce62e48940d3a0124f377c05499d3f3e004e264ae94b83", size = 5214774, upload-time = "2026-02-20T21:51:31.173Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/01/0ef15c22a1c54b0f728ceff3f62d478dbf8b0dcf8ff7b80b954f79584f3e/ty-0.0.17-py3-none-linux_armv6l.whl", hash = "sha256:64a9a16555cc8867d35c2647c2f1afbd3cae55f68fd95283a574d1bb04fe93e0", size = 10192793, upload-time = "2026-02-13T13:27:13.943Z" }, - { url = "https://files.pythonhosted.org/packages/0f/2c/f4c322d9cded56edc016b1092c14b95cf58c8a33b4787316ea752bb9418e/ty-0.0.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:eb2dbd8acd5c5a55f4af0d479523e7c7265a88542efe73ed3d696eb1ba7b6454", size = 10051977, upload-time = "2026-02-13T13:26:57.741Z" }, - { url = "https://files.pythonhosted.org/packages/4c/a5/43746c1ff81e784f5fc303afc61fe5bcd85d0fcf3ef65cb2cef78c7486c7/ty-0.0.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f18f5fd927bc628deb9ea2df40f06b5f79c5ccf355db732025a3e8e7152801f6", size = 9564639, upload-time = "2026-02-13T13:26:42.781Z" }, - { url = "https://files.pythonhosted.org/packages/d6/b8/280b04e14a9c0474af574f929fba2398b5e1c123c1e7735893b4cd73d13c/ty-0.0.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5383814d1d7a5cc53b3b07661856bab04bb2aac7a677c8d33c55169acdaa83df", size = 10061204, upload-time = "2026-02-13T13:27:00.152Z" }, - { url = "https://files.pythonhosted.org/packages/2a/d7/493e1607d8dfe48288d8a768a2adc38ee27ef50e57f0af41ff273987cda0/ty-0.0.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c20423b8744b484f93e7bf2ef8a9724bca2657873593f9f41d08bd9f83444c9", size = 10013116, upload-time = "2026-02-13T13:26:34.543Z" }, - { url = "https://files.pythonhosted.org/packages/80/ef/22f3ed401520afac90dbdf1f9b8b7755d85b0d5c35c1cb35cf5bd11b59c2/ty-0.0.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6f5b1aba97db9af86517b911674b02f5bc310750485dc47603a105bd0e83ddd", size = 10533623, upload-time = "2026-02-13T13:26:31.449Z" }, - { url = "https://files.pythonhosted.org/packages/75/ce/744b15279a11ac7138832e3a55595706b4a8a209c9f878e3ab8e571d9032/ty-0.0.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:488bce1a9bea80b851a97cd34c4d2ffcd69593d6c3f54a72ae02e5c6e47f3d0c", size = 11069750, upload-time = "2026-02-13T13:26:48.638Z" }, - { url = "https://files.pythonhosted.org/packages/f2/be/1133c91f15a0e00d466c24f80df486d630d95d1b2af63296941f7473812f/ty-0.0.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8df66b91ec84239420985ec215e7f7549bfda2ac036a3b3c065f119d1c06825a", size = 10870862, upload-time = "2026-02-13T13:26:54.715Z" }, - { url = "https://files.pythonhosted.org/packages/3e/4a/a2ed209ef215b62b2d3246e07e833081e07d913adf7e0448fc204be443d6/ty-0.0.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:002139e807c53002790dfefe6e2f45ab0e04012e76db3d7c8286f96ec121af8f", size = 10628118, upload-time = "2026-02-13T13:26:45.439Z" }, - { url = "https://files.pythonhosted.org/packages/b3/0c/87476004cb5228e9719b98afffad82c3ef1f84334bde8527bcacba7b18cb/ty-0.0.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6c4e01f05ce82e5d489ab3900ca0899a56c4ccb52659453780c83e5b19e2b64c", size = 10038185, upload-time = "2026-02-13T13:27:02.693Z" }, - { url = "https://files.pythonhosted.org/packages/46/4b/98f0b3ba9aef53c1f0305519536967a4aa793a69ed72677b0a625c5313ac/ty-0.0.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2b226dd1e99c0d2152d218c7e440150d1a47ce3c431871f0efa073bbf899e881", size = 10047644, upload-time = "2026-02-13T13:27:05.474Z" }, - { url = "https://files.pythonhosted.org/packages/93/e0/06737bb80aa1a9103b8651d2eb691a7e53f1ed54111152be25f4a02745db/ty-0.0.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8b11f1da7859e0ad69e84b3c5ef9a7b055ceed376a432fad44231bdfc48061c2", size = 10231140, upload-time = "2026-02-13T13:27:10.844Z" }, - { url = "https://files.pythonhosted.org/packages/7c/79/e2a606bd8852383ba9abfdd578f4a227bd18504145381a10a5f886b4e751/ty-0.0.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c04e196809ff570559054d3e011425fd7c04161529eb551b3625654e5f2434cb", size = 10718344, upload-time = "2026-02-13T13:26:51.66Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2d/2663984ac11de6d78f74432b8b14ba64d170b45194312852b7543cf7fd56/ty-0.0.17-py3-none-win32.whl", hash = "sha256:305b6ed150b2740d00a817b193373d21f0767e10f94ac47abfc3b2e5a5aec809", size = 9672932, upload-time = "2026-02-13T13:27:08.522Z" }, - { url = "https://files.pythonhosted.org/packages/de/b5/39be78f30b31ee9f5a585969930c7248354db90494ff5e3d0756560fb731/ty-0.0.17-py3-none-win_amd64.whl", hash = "sha256:531828267527aee7a63e972f54e5eee21d9281b72baf18e5c2850c6b862add83", size = 10542138, upload-time = "2026-02-13T13:27:17.084Z" }, - { url = "https://files.pythonhosted.org/packages/40/b7/f875c729c5d0079640c75bad2c7e5d43edc90f16ba242f28a11966df8f65/ty-0.0.17-py3-none-win_arm64.whl", hash = "sha256:de9810234c0c8d75073457e10a84825b9cd72e6629826b7f01c7a0b266ae25b1", size = 10023068, upload-time = "2026-02-13T13:26:39.637Z" }, -] - -[[package]] -name = "typer" -version = "0.24.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-doc" }, - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5a/b6/3e681d3b6bb22647509bdbfdd18055d5adc0dce5c5585359fa46ff805fdc/typer-0.24.0.tar.gz", hash = "sha256:f9373dc4eff901350694f519f783c29b6d7a110fc0dcc11b1d7e353b85ca6504", size = 118380, upload-time = "2026-02-16T22:08:48.496Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/85/d0/4da85c2a45054bb661993c93524138ace4956cb075a7ae0c9d1deadc331b/typer-0.24.0-py3-none-any.whl", hash = "sha256:5fc435a9c8356f6160ed6e85a6301fdd6e3d8b2851da502050d1f92c5e9eddc8", size = 56441, upload-time = "2026-02-16T22:08:47.535Z" }, -] - -[[package]] -name = "typer-slim" -version = "0.24.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a7/a7/e6aecc4b4eb59598829a3b5076a93aff291b4fdaa2ded25efc4e1f4d219c/typer_slim-0.24.0.tar.gz", hash = "sha256:f0ed36127183f52ae6ced2ecb2521789995992c521a46083bfcdbb652d22ad34", size = 4776, upload-time = "2026-02-16T22:08:51.2Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/24/5480c20380dfd18cf33d14784096dca45a24eae6102e91d49a718d3b6855/typer_slim-0.24.0-py3-none-any.whl", hash = "sha256:d5d7ee1ee2834d5020c7c616ed5e0d0f29b9a4b1dd283bdebae198ec09778d0e", size = 3394, upload-time = "2026-02-16T22:08:49.92Z" }, -] - -[[package]] -name = "types-protobuf" -version = "6.32.1.20251210" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/59/c743a842911887cd96d56aa8936522b0cd5f7a7f228c96e81b59fced45be/types_protobuf-6.32.1.20251210.tar.gz", hash = "sha256:c698bb3f020274b1a2798ae09dc773728ce3f75209a35187bd11916ebfde6763", size = 63900, upload-time = "2025-12-10T03:14:25.451Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/43/58e75bac4219cbafee83179505ff44cae3153ec279be0e30583a73b8f108/types_protobuf-6.32.1.20251210-py3-none-any.whl", hash = "sha256:2641f78f3696822a048cfb8d0ff42ccd85c25f12f871fbebe86da63793692140", size = 77921, upload-time = "2025-12-10T03:14:24.477Z" }, -] - -[[package]] -name = "types-requests" -version = "2.32.4.20260107" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/a0663907082280664d745929205a89d41dffb29e89a50f753af7d57d0a96/types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f", size = 23165, upload-time = "2026-01-07T03:20:54.091Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d8/920460d4c22ea68fcdeb0b2fb53ea2aeb9c6d7875bde9278d84f2ac767b6/ty-0.0.18-py3-none-linux_armv6l.whl", hash = "sha256:4e5e91b0a79857316ef893c5068afc4b9872f9d257627d9bc8ac4d2715750d88", size = 10280825, upload-time = "2026-02-20T21:51:25.03Z" }, + { url = "https://files.pythonhosted.org/packages/83/56/62587de582d3d20d78fcdddd0594a73822ac5a399a12ef512085eb7a4de6/ty-0.0.18-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee0e578b3f8416e2d5416da9553b78fd33857868aa1384cb7fefeceee5ff102d", size = 10118324, upload-time = "2026-02-20T21:51:22.27Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2d/dbdace8d432a0755a7417f659bfd5b8a4261938ecbdfd7b42f4c454f5aa9/ty-0.0.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3f7a0487d36b939546a91d141f7fc3dbea32fab4982f618d5b04dc9d5b6da21e", size = 9605861, upload-time = "2026-02-20T21:51:16.066Z" }, + { url = "https://files.pythonhosted.org/packages/6b/d9/de11c0280f778d5fc571393aada7fe9b8bc1dd6a738f2e2c45702b8b3150/ty-0.0.18-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5e2fa8d45f57ca487a470e4bf66319c09b561150e98ae2a6b1a97ef04c1a4eb", size = 10092701, upload-time = "2026-02-20T21:51:26.862Z" }, + { url = "https://files.pythonhosted.org/packages/0f/94/068d4d591d791041732171e7b63c37a54494b2e7d28e88d2167eaa9ad875/ty-0.0.18-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d75652e9e937f7044b1aca16091193e7ef11dac1c7ec952b7fb8292b7ba1f5f2", size = 10109203, upload-time = "2026-02-20T21:51:11.59Z" }, + { url = "https://files.pythonhosted.org/packages/34/e4/526a4aa56dc0ca2569aaa16880a1ab105c3b416dd70e87e25a05688999f3/ty-0.0.18-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:563c868edceb8f6ddd5e91113c17d3676b028f0ed380bdb3829b06d9beb90e58", size = 10614200, upload-time = "2026-02-20T21:51:20.298Z" }, + { url = "https://files.pythonhosted.org/packages/fd/3d/b68ab20a34122a395880922587fbfc3adf090d22e0fb546d4d20fe8c2621/ty-0.0.18-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:502e2a1f948bec563a0454fc25b074bf5cf041744adba8794d024277e151d3b0", size = 11153232, upload-time = "2026-02-20T21:51:14.121Z" }, + { url = "https://files.pythonhosted.org/packages/68/ea/678243c042343fcda7e6af36036c18676c355878dcdcd517639586d2cf9e/ty-0.0.18-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc881dea97021a3aa29134a476937fd8054775c4177d01b94db27fcfb7aab65b", size = 10832934, upload-time = "2026-02-20T21:51:32.92Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bd/7f8d647cef8b7b346c0163230a37e903c7461c7248574840b977045c77df/ty-0.0.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:421fcc3bc64cab56f48edb863c7c1c43649ec4d78ff71a1acb5366ad723b6021", size = 10700888, upload-time = "2026-02-20T21:51:09.673Z" }, + { url = "https://files.pythonhosted.org/packages/6e/06/cb3620dc48c5d335ba7876edfef636b2f4498eff4a262ff90033b9e88408/ty-0.0.18-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0fe5038a7136a0e638a2fb1ad06e3d3c4045314c6ba165c9c303b9aeb4623d6c", size = 10078965, upload-time = "2026-02-20T21:51:07.678Z" }, + { url = "https://files.pythonhosted.org/packages/60/27/c77a5a84533fa3b685d592de7b4b108eb1f38851c40fac4e79cc56ec7350/ty-0.0.18-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d123600a52372677613a719bbb780adeb9b68f47fb5f25acb09171de390e0035", size = 10134659, upload-time = "2026-02-20T21:51:18.311Z" }, + { url = "https://files.pythonhosted.org/packages/43/6e/60af6b88c73469e628ba5253a296da6984e0aa746206f3034c31f1a04ed1/ty-0.0.18-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb4bc11d32a1bf96a829bf6b9696545a30a196ac77bbc07cc8d3dfee35e03723", size = 10297494, upload-time = "2026-02-20T21:51:39.631Z" }, + { url = "https://files.pythonhosted.org/packages/33/90/612dc0b68224c723faed6adac2bd3f930a750685db76dfe17e6b9e534a83/ty-0.0.18-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dda2efbf374ba4cd704053d04e32f2f784e85c2ddc2400006b0f96f5f7e4b667", size = 10791944, upload-time = "2026-02-20T21:51:37.13Z" }, + { url = "https://files.pythonhosted.org/packages/0d/da/f4ada0fd08a9e4138fe3fd2bcd3797753593f423f19b1634a814b9b2a401/ty-0.0.18-py3-none-win32.whl", hash = "sha256:c5768607c94977dacddc2f459ace6a11a408a0f57888dd59abb62d28d4fee4f7", size = 9677964, upload-time = "2026-02-20T21:51:42.039Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fa/090ed9746e5c59fc26d8f5f96dc8441825171f1f47752f1778dad690b08b/ty-0.0.18-py3-none-win_amd64.whl", hash = "sha256:b78d0fa1103d36fc2fce92f2092adace52a74654ab7884d54cdaec8eb5016a4d", size = 10636576, upload-time = "2026-02-20T21:51:29.159Z" }, + { url = "https://files.pythonhosted.org/packages/92/4f/5dd60904c8105cda4d0be34d3a446c180933c76b84ae0742e58f02133713/ty-0.0.18-py3-none-win_arm64.whl", hash = "sha256:01770c3c82137c6b216aa3251478f0b197e181054ee92243772de553d3586398", size = 10095449, upload-time = "2026-02-20T21:51:34.914Z" }, ] [[package]] @@ -2386,6 +1864,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + +[[package]] +name = "tzlocal" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, +] + +[[package]] +name = "uritemplate" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267, upload-time = "2025-06-02T15:12:06.318Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488, upload-time = "2025-06-02T15:12:03.405Z" }, +] + [[package]] name = "urllib3" version = "2.6.3" @@ -2409,36 +1917,59 @@ wheels = [ ] [[package]] -name = "watchfiles" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } +name = "va-agent" +version = "0.1.0" +source = { editable = "." } dependencies = [ - { name = "anyio" }, + { name = "google-adk" }, + { name = "google-cloud-firestore" }, + { name = "pydantic-settings", extra = ["yaml"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, - { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, - { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, - { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, - { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, - { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, - { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, - { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, - { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, - { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, - { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-sugar" }, + { name = "ruff" }, + { name = "ty" }, +] + +[package.metadata] +requires-dist = [ + { name = "google-adk", specifier = ">=1.14.1" }, + { name = "google-cloud-firestore", specifier = ">=2.23.0" }, + { name = "pydantic-settings", extras = ["yaml"], specifier = ">=2.13.1" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.4.1" }, + { name = "pytest-asyncio", specifier = ">=1.3.0" }, + { name = "pytest-sugar", specifier = ">=1.1.1" }, + { name = "ruff", specifier = ">=0.12.10" }, + { name = "ty", specifier = ">=0.0.1a19" }, ] [[package]] -name = "wcwidth" -version = "0.6.0" +name = "watchdog" +version = "6.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, ] [[package]] @@ -2461,44 +1992,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, ] -[[package]] -name = "wrapt" -version = "1.17.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, - { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, - { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, - { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, - { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, - { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, - { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, - { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, - { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, - { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, -] - -[[package]] -name = "xai-sdk" -version = "1.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "googleapis-common-protos" }, - { name = "grpcio" }, - { name = "opentelemetry-sdk" }, - { name = "packaging" }, - { name = "protobuf" }, - { name = "pydantic" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1c/3e/cf9ffdf444ec3d495b8f4a6b3934c86088900aeb131cf2d81535b218e615/xai_sdk-1.7.0.tar.gz", hash = "sha256:649e7c3bff01510326f13a709dff824256fcdfd2850e21606d25870208989bd1", size = 390582, upload-time = "2026-02-18T20:53:23.352Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/e9/e0fb57dc5b2580aa6390a836cf81d507f8017888ab8197600489118cc677/xai_sdk-1.7.0-py3-none-any.whl", hash = "sha256:332672c46ee73a7aad9b05e82db5a5044174bb3f5f038a7acf703f0b0ce6bad0", size = 241650, upload-time = "2026-02-18T20:53:22.016Z" }, -] - [[package]] name = "yarl" version = "1.22.0"